diff --git a/pkgbuilds/linux-omarchy-bore/.omarchy/package.json b/pkgbuilds/linux-omarchy-bore/.omarchy/package.json new file mode 100644 index 0000000..c3ed389 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/.omarchy/package.json @@ -0,0 +1,4 @@ +{ + "source": "local", + "skip_build": true +} diff --git a/pkgbuilds/linux-omarchy-bore/0010-archlinux-base.patch b/pkgbuilds/linux-omarchy-bore/0010-archlinux-base.patch new file mode 100644 index 0000000..55e9379 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0010-archlinux-base.patch @@ -0,0 +1,56 @@ +diff --git a/kernel/fork.c b/kernel/fork.c +index f0e2e131a9a5..7b611da9a27a 100644 +--- a/kernel/fork.c ++++ b/kernel/fork.c +@@ -127,6 +127,12 @@ + + #include + ++#ifdef CONFIG_USER_NS ++static int unprivileged_userns_clone = 1; ++#else ++#define unprivileged_userns_clone 1 ++#endif ++ + /* + * Minimum number of threads to boot the kernel + */ +@@ -2093,6 +2099,11 @@ __latent_entropy struct task_struct *copy_process( + return ERR_PTR(-EPERM); + } + ++ if ((clone_flags & CLONE_NEWUSER) && !unprivileged_userns_clone) { ++ if (!capable(CAP_SYS_ADMIN)) ++ return ERR_PTR(-EPERM); ++ } ++ + /* + * Force any signals received before this point to be delivered + * before the fork happens. Collect up signals sent to multiple +@@ -3163,6 +3174,10 @@ static int check_unshare_flags(unsigned long unshare_flags) + if (!current_is_single_threaded()) + return -EINVAL; + } ++ if ((unshare_flags & CLONE_NEWUSER) && !unprivileged_userns_clone) { ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ } + + return 0; + } +@@ -3398,6 +3413,15 @@ static const struct ctl_table fork_sysctl_table[] = { + .mode = 0644, + .proc_handler = sysctl_max_threads, + }, ++#ifdef CONFIG_USER_NS ++ { ++ .procname = "unprivileged_userns_clone", ++ .data = &unprivileged_userns_clone, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec, ++ }, ++#endif + }; + + static int __init init_fork_sysctl(void) diff --git a/pkgbuilds/linux-omarchy-bore/0110-bore-6.8.0.patch b/pkgbuilds/linux-omarchy-bore/0110-bore-6.8.0.patch new file mode 100644 index 0000000..1d049bd --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0110-bore-6.8.0.patch @@ -0,0 +1,1241 @@ +diff --git a/include/linux/sched.h b/include/linux/sched.h +--- a/include/linux/sched.h ++++ b/include/linux/sched.h +@@ -823,6 +823,31 @@ struct kmap_ctrl { + #endif + }; + ++#ifdef CONFIG_SCHED_BORE ++#define BORE_BC_TIMESTAMP_SHIFT 16 ++ ++struct bore_bc { ++ union { ++ struct { ++ u64 timestamp: 48; ++ u64 penalty: 16; ++ }; ++ u64 value; ++ }; ++}; ++ ++struct bore_ctx { ++ u64 burst_time; ++ u16 prev_penalty; ++ u16 curr_penalty; ++ u16 penalty; ++ bool stop_update; ++ bool futex_waiting; ++ struct bore_bc subtree; ++ struct bore_bc group; ++}; ++#endif /* CONFIG_SCHED_BORE */ ++ + struct task_struct { + #ifdef CONFIG_THREAD_INFO_IN_TASK + /* +@@ -884,6 +909,9 @@ struct task_struct { + #ifdef CONFIG_SCHED_CLASS_EXT + struct sched_ext_entity scx; + #endif ++#ifdef CONFIG_SCHED_BORE ++ struct bore_ctx bore; ++#endif /* CONFIG_SCHED_BORE */ + const struct sched_class *sched_class; + + #ifdef CONFIG_SCHED_CORE +diff --git a/include/linux/sched/bore.h b/include/linux/sched/bore.h +new file mode 100644 +--- /dev/null ++++ b/include/linux/sched/bore.h +@@ -0,0 +1,49 @@ ++#ifndef _KERNEL_SCHED_BORE_H ++#define _KERNEL_SCHED_BORE_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define SCHED_BORE_AUTHOR "Masahito Suzuki" ++#define SCHED_BORE_PROGNAME "BORE CPU Scheduler modification" ++ ++#define SCHED_BORE_VERSION "6.8.0" ++ ++extern u8 __read_mostly sched_bore; ++DECLARE_STATIC_KEY_TRUE(sched_bore_key); ++extern u8 __read_mostly sched_burst_inherit_type; ++extern u8 __read_mostly sched_burst_smoothness; ++extern u8 __read_mostly sched_burst_penalty_offset; ++extern uint __read_mostly sched_burst_penalty_scale; ++extern uint __read_mostly sched_burst_cache_lifetime; ++extern u8 __read_mostly sched_burst_protect_slice_lv; ++DECLARE_STATIC_KEY_TRUE(sched_burst_protect_slice_cond_key); ++DECLARE_STATIC_KEY_FALSE(sched_burst_protect_slice_prefer_key); ++ ++static inline u8 bore_score(struct task_struct *p) ++{ return p->bore.penalty >> 8; } ++ ++extern u8 effective_prio_bore(struct task_struct *p); ++extern void update_curr_bore(struct task_struct *p, u64 delta_exec); ++extern void restart_burst_bore(struct task_struct *p); ++extern void restart_burst_rescale_deadline_bore(struct task_struct *p); ++extern void task_fork_bore(struct task_struct *p, struct task_struct *parent, ++ u64 clone_flags, u64 now); ++extern void sched_init_bore(void); ++extern void reset_task_bore(struct task_struct *p); ++ ++extern int sched_bore_update_handler(const struct ctl_table *table, ++ int write, void __user *buffer, size_t *lenp, loff_t *ppos); ++extern int sched_burst_inherit_type_update_handler(const struct ctl_table *table, ++ int write, void __user *buffer, size_t *lenp, loff_t *ppos); ++extern int sched_burst_protect_slice_lv_update_handler(const struct ctl_table *table, ++ int write, void __user *buffer, size_t *lenp, loff_t *ppos); ++ ++extern void reweight_entity( ++ struct cfs_rq *cfs_rq, struct sched_entity *se, unsigned long weight); ++ ++#endif /* _KERNEL_SCHED_BORE_H */ +diff --git a/init/Kconfig b/init/Kconfig +--- a/init/Kconfig ++++ b/init/Kconfig +@@ -1480,6 +1480,23 @@ config CHECKPOINT_RESTORE + + If unsure, say N here. + ++config SCHED_BORE ++ bool "Burst-Oriented Response Enhancer" ++ default y ++ help ++ In Desktop and Mobile computing, one might prefer interactive ++ tasks to keep responsive no matter what they run in the background. ++ ++ Enabling this kernel feature modifies the scheduler to discriminate ++ tasks by their burst time (runtime since it last went sleeping or ++ yielding state) and prioritize those that run less bursty. ++ Such tasks usually include window compositor, widgets backend, ++ terminal emulator, video playback, games and so on. ++ With a little impact to scheduling fairness, it may improve ++ responsiveness especially under heavy background workload. ++ ++ If unsure, say Y here. ++ + config SCHED_AUTOGROUP + bool "Automatic process group scheduling" + select CGROUPS +diff --git a/kernel/Kconfig.hz b/kernel/Kconfig.hz +--- a/kernel/Kconfig.hz ++++ b/kernel/Kconfig.hz +@@ -57,3 +57,20 @@ config HZ + + config SCHED_HRTICK + def_bool HIGH_RES_TIMERS ++ ++config MIN_BASE_SLICE_NS ++ int "Default value for min_base_slice_ns" ++ default 2000000 ++ help ++ The BORE Scheduler automatically calculates the optimal base ++ slice for the configured HZ using the following equation: ++ ++ base_slice_ns = ++ 1000000000/HZ * DIV_ROUNDUP(min_base_slice_ns, 1000000000/HZ) ++ ++ This option sets the default lower bound limit of the base slice ++ to prevent the loss of task throughput due to overscheduling. ++ ++ Setting this value too high can cause the system to boot with ++ an unnecessarily large base slice, resulting in high scheduling ++ latency and poor system responsiveness. +diff --git a/kernel/exit.c b/kernel/exit.c +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -147,7 +147,11 @@ static void __unhash_process(struct release_task_post *post, struct task_struct + detach_pid(post->pids, p, PIDTYPE_SID); + + list_del_rcu(&p->tasks); ++#ifdef CONFIG_SCHED_BORE ++ list_del_rcu(&p->sibling); ++#else /* !CONFIG_SCHED_BORE */ + list_del_init(&p->sibling); ++#endif /* CONFIG_SCHED_BORE */ + __this_cpu_dec(process_counts); + } + list_del_rcu(&p->thread_node); +diff --git a/kernel/fork.c b/kernel/fork.c +--- a/kernel/fork.c ++++ b/kernel/fork.c +@@ -120,6 +120,10 @@ + /* For dup_mmap(). */ + #include "../mm/internal.h" + ++#ifdef CONFIG_SCHED_BORE ++#include ++#endif /* CONFIG_SCHED_BORE */ ++ + #include + + #define CREATE_TRACE_POINTS +@@ -2441,6 +2445,11 @@ __latent_entropy struct task_struct *copy_process( + p->start_time = ktime_get_ns(); + p->start_boottime = ktime_get_boottime_ns(); + ++#ifdef CONFIG_SCHED_BORE ++ if (likely(p->pid)) ++ task_fork_bore(p, current, clone_flags, p->start_time); ++#endif /* CONFIG_SCHED_BORE */ ++ + /* + * Make it visible to the rest of the system, but dont wake it up yet. + * Need tasklist lock for parent etc handling! +@@ -2526,7 +2535,11 @@ __latent_entropy struct task_struct *copy_process( + p->real_parent->signal->is_child_subreaper; + if (clone_flags & CLONE_AUTOREAP) + p->signal->autoreap = 1; ++#ifdef CONFIG_SCHED_BORE ++ list_add_tail_rcu(&p->sibling, &p->real_parent->children); ++#else /* !CONFIG_SCHED_BORE */ + list_add_tail(&p->sibling, &p->real_parent->children); ++#endif /* CONFIG_SCHED_BORE */ + list_add_tail_rcu(&p->tasks, &init_task.tasks); + attach_pid(p, PIDTYPE_TGID); + attach_pid(p, PIDTYPE_PGID); +diff --git a/kernel/futex/waitwake.c b/kernel/futex/waitwake.c +--- a/kernel/futex/waitwake.c ++++ b/kernel/futex/waitwake.c +@@ -4,6 +4,9 @@ + #include + #include + #include ++#ifdef CONFIG_SCHED_BORE ++#include ++#endif /* CONFIG_SCHED_BORE */ + + #include "futex.h" + +@@ -384,7 +387,15 @@ void futex_do_wait(struct futex_q *q, struct hrtimer_sleeper *timeout) + * is no timeout, or if it has yet to expire. + */ + if (!timeout || timeout->task) ++#ifdef CONFIG_SCHED_BORE ++ { ++ current->bore.futex_waiting = true; ++#endif /* CONFIG_SCHED_BORE */ + schedule(); ++#ifdef CONFIG_SCHED_BORE ++ current->bore.futex_waiting = false; ++ } ++#endif /* CONFIG_SCHED_BORE */ + } + __set_current_state(TASK_RUNNING); + } +diff --git a/kernel/sched/Makefile b/kernel/sched/Makefile +--- a/kernel/sched/Makefile ++++ b/kernel/sched/Makefile +@@ -40,3 +40,4 @@ obj-y += core.o + obj-y += fair.o + obj-y += build_policy.o + obj-y += build_utility.o ++obj-$(CONFIG_SCHED_BORE) += bore.o +diff --git a/kernel/sched/bore.c b/kernel/sched/bore.c +new file mode 100644 +--- /dev/null ++++ b/kernel/sched/bore.c +@@ -0,0 +1,466 @@ ++/* ++ * Burst-Oriented Response Enhancer (BORE) CPU Scheduler ++ * Copyright (C) 2021-2025 Masahito Suzuki ++ */ ++#include ++#include ++#include ++#include "sched.h" ++ ++#ifdef CONFIG_SCHED_BORE ++DEFINE_STATIC_KEY_TRUE(sched_bore_key); ++u8 __read_mostly sched_bore = 1; ++u8 __read_mostly sched_burst_inherit_type = 2; ++u8 __read_mostly sched_burst_protect_slice_lv = 1; ++u8 __read_mostly sched_burst_smoothness = 1; ++u8 __read_mostly sched_burst_penalty_offset = 24; ++uint __read_mostly sched_burst_penalty_scale = 1536; ++uint __read_mostly sched_burst_cache_lifetime = 75000000; ++static int __maybe_unused maxval_prio = 39; ++static int __maybe_unused maxval_6_bits = 63; ++static int __maybe_unused maxval_8_bits = 255; ++static int __maybe_unused maxval_12_bits = 4095; ++ ++#define MAX_BURST_PENALTY ((40U << 8) - 1) ++#define BURST_CACHE_SAMPLE_LIMIT 63 ++#define BURST_CACHE_SCAN_LIMIT (BURST_CACHE_SAMPLE_LIMIT * 2) ++ ++static u32 bore_reciprocal_lut[BURST_CACHE_SAMPLE_LIMIT + 1]; ++ ++DEFINE_STATIC_KEY_TRUE(sched_burst_inherit_key); ++DEFINE_STATIC_KEY_TRUE(sched_burst_ancestor_key); ++DEFINE_STATIC_KEY_TRUE (sched_burst_protect_slice_cond_key); ++DEFINE_STATIC_KEY_FALSE(sched_burst_protect_slice_prefer_key); ++ ++static inline u32 log2p1_u64_u32fp(u64 v, u8 fp) { ++ if (unlikely(!v)) return 0; ++ int clz = __builtin_clzll(v); ++ int exponent = 64 - clz; ++ u32 mantissa = (u32)((v << clz) << 1 >> (64 - fp)); ++ return exponent << fp | mantissa; ++} ++ ++static inline u32 calc_burst_penalty(u64 burst_time) { ++ u32 greed = log2p1_u64_u32fp(burst_time, 8), ++ tolerance = sched_burst_penalty_offset << 8; ++ s32 diff = (s32)(greed - tolerance); ++ u32 penalty = diff & ~(diff >> 31); ++ u32 scaled_penalty = penalty * sched_burst_penalty_scale >> 10; ++ s32 overflow = scaled_penalty - MAX_BURST_PENALTY; ++ return scaled_penalty - (overflow & ~(overflow >> 31)); ++} ++ ++static inline u64 rescale_slice(u64 delta, u8 old_prio, u8 new_prio) { ++ u64 unscaled, rescaled; ++ unscaled = mul_u64_u32_shr(delta , sched_prio_to_weight[old_prio], 10); ++ rescaled = mul_u64_u32_shr(unscaled, sched_prio_to_wmult [new_prio], 22); ++ return rescaled; ++} ++ ++static inline u32 binary_smooth(u32 new, u32 old) { ++ u32 is_growing = (new > old); ++ u32 increment = (new - old) * is_growing; ++ u32 shift = sched_burst_smoothness; ++ u32 smoothed = old + ((increment + (1U << shift) - 1) >> shift); ++ return (new & ~(-is_growing)) | (smoothed & (-is_growing)); ++} ++ ++static void reweight_task_by_prio(struct task_struct *p, int prio) { ++ if (task_has_idle_policy(p)) return; ++ ++ struct sched_entity *se = &p->se; ++ unsigned long weight = scale_load(sched_prio_to_weight[prio]); ++ ++ if (se->on_rq) { ++ p->bore.stop_update = true; ++ reweight_entity(cfs_rq_of(se), se, weight); ++ p->bore.stop_update = false; ++ } else ++ se->load.weight = weight; ++ se->load.inv_weight = sched_prio_to_wmult[prio]; ++} ++ ++u8 effective_prio_bore(struct task_struct *p) { ++ int prio = p->static_prio - MAX_RT_PRIO; ++ if (static_branch_likely(&sched_bore_key)) ++ prio += bore_score(p); ++ prio &= ~(prio >> 31); ++ s32 diff = prio - maxval_prio; ++ prio -= (diff & ~(diff >> 31)); ++ return (u8)prio; ++} ++ ++static void update_penalty(struct task_struct *p) { ++ struct bore_ctx *ctx = &p->bore; ++ ++ u8 prev_prio = effective_prio_bore(p); ++ ++ s32 diff = (s32)ctx->curr_penalty - (s32)ctx->prev_penalty; ++ u16 max_val = ctx->curr_penalty - (diff & (diff >> 31)); ++ u32 is_kthread = !!(p->flags & PF_KTHREAD); ++ ctx->penalty = max_val & -(s32)(!is_kthread); ++ ++ u8 new_prio = effective_prio_bore(p); ++ if (new_prio != prev_prio) ++ reweight_task_by_prio(p, new_prio); ++} ++ ++void update_curr_bore(struct task_struct *p, u64 delta_exec) { ++ struct bore_ctx *ctx = &p->bore; ++ if (ctx->stop_update) return; ++ ++ ctx->burst_time += delta_exec; ++ u32 curr_penalty = ctx->curr_penalty = calc_burst_penalty(ctx->burst_time); ++ ++ if (curr_penalty <= ctx->prev_penalty) return; ++ update_penalty(p); ++} ++ ++void restart_burst_bore(struct task_struct *p) { ++ struct bore_ctx *ctx = &p->bore; ++ u32 new_penalty = binary_smooth(ctx->curr_penalty, ctx->prev_penalty); ++ ctx->prev_penalty = new_penalty; ++ ctx->curr_penalty = 0; ++ ctx->burst_time = 0; ++ update_penalty(p); ++} ++ ++void restart_burst_rescale_deadline_bore(struct task_struct *p) { ++ struct sched_entity *se = &p->se; ++ s64 vscaled, vremain = se->deadline - se->vruntime; ++ ++ u8 old_prio = effective_prio_bore(p); ++ restart_burst_bore(p); ++ u8 new_prio = effective_prio_bore(p); ++ ++ if (old_prio > new_prio) { ++ vscaled = rescale_slice(abs(vremain), old_prio, new_prio); ++ if (unlikely(vremain < 0)) ++ vscaled = -vscaled; ++ se->deadline = se->vruntime + vscaled; ++ } ++} ++ ++static inline bool task_is_bore_eligible(struct task_struct *p) ++{return p && p->sched_class == &fair_sched_class && !p->exit_state;} ++ ++#ifndef for_each_child_task ++#define for_each_child_task(p, t) \ ++ list_for_each_entry_rcu(t, &(p)->children, sibling) ++#endif ++ ++static inline u32 count_children_upto2(struct task_struct *p) { ++ struct list_head *head = &p->children; ++ struct list_head *first = READ_ONCE(head->next); ++ struct list_head *second = READ_ONCE(first->next); ++ return (first != head) + (second != head); ++} ++ ++static inline bool burst_cache_expired(struct bore_bc *bc, u64 now) { ++ struct bore_bc bc_val = { .value = READ_ONCE(bc->value) }; ++ u64 timestamp = (u64)bc_val.timestamp << BORE_BC_TIMESTAMP_SHIFT; ++ return now - timestamp > (u64)sched_burst_cache_lifetime; ++} ++ ++static void update_burst_cache(struct bore_bc *bc, ++ struct task_struct *p, u32 count, u32 total, u64 now) { ++ u32 average = (count == 1) ? total : ++ (u32)(((u64)total * bore_reciprocal_lut[count]) >> 32); ++ ++ struct bore_bc new_bc = { ++ .penalty = max(average, p->bore.penalty), ++ .timestamp = now >> BORE_BC_TIMESTAMP_SHIFT ++ }; ++ WRITE_ONCE(bc->value, new_bc.value); ++} ++ ++static u32 inherit_from_parent(struct task_struct *parent, ++ u64 clone_flags, u64 now) { ++ struct bore_bc bc_val; ++ ++ if (clone_flags & CLONE_PARENT) ++ parent = rcu_dereference(parent->real_parent); ++ ++ struct bore_bc *bc = &parent->bore.subtree; ++ ++ if (burst_cache_expired(bc, now)) { ++ struct task_struct *child; ++ u32 count = 0, total = 0, scan_count = 0; ++ for_each_child_task(parent, child) { ++ if (count >= BURST_CACHE_SAMPLE_LIMIT) break; ++ if (scan_count++ >= BURST_CACHE_SCAN_LIMIT) break; ++ ++ if (!task_is_bore_eligible(child)) continue; ++ count++; ++ total += child->bore.penalty; ++ } ++ ++ update_burst_cache(bc, parent, count, total, now); ++ } ++ ++ bc_val.value = READ_ONCE(bc->value); ++ return (u32)bc_val.penalty; ++} ++ ++static u32 inherit_from_ancestor_hub(struct task_struct *parent, ++ u64 clone_flags, u64 now) { ++ struct bore_bc bc_val; ++ struct task_struct *ancestor = parent; ++ u32 sole_child_count = 0; ++ ++ if (clone_flags & CLONE_PARENT) { ++ ancestor = rcu_dereference(ancestor->real_parent); ++ sole_child_count = 1; ++ } ++ ++ for (struct task_struct *next; ++ (next = rcu_dereference(ancestor->real_parent)) != ancestor && ++ count_children_upto2(ancestor) <= sole_child_count; ++ ancestor = next, sole_child_count = 1) {} ++ ++ struct bore_bc *bc = &ancestor->bore.subtree; ++ ++ if (burst_cache_expired(bc, now)) { ++ struct task_struct *direct_child; ++ u32 count = 0, total = 0, scan_count = 0; ++ for_each_child_task(ancestor, direct_child) { ++ if (count >= BURST_CACHE_SAMPLE_LIMIT) break; ++ if (scan_count++ >= BURST_CACHE_SCAN_LIMIT) break; ++ ++ struct task_struct *descendant = direct_child; ++ while (count_children_upto2(descendant) == 1) { ++ struct task_struct *next_descendant = ++ list_first_or_null_rcu(&descendant->children, ++ struct task_struct, sibling); ++ if (!next_descendant) break; ++ descendant = next_descendant; ++ } ++ ++ if (!task_is_bore_eligible(descendant)) continue; ++ count++; ++ total += descendant->bore.penalty; ++ } ++ ++ update_burst_cache(bc, ancestor, count, total, now); ++ } ++ ++ bc_val.value = READ_ONCE(bc->value); ++ return (u32)bc_val.penalty; ++} ++ ++static u32 inherit_from_thread_group(struct task_struct *p, u64 now) { ++ struct bore_bc bc_val; ++ struct task_struct *leader = p->group_leader; ++ struct bore_bc *bc = &leader->bore.group; ++ ++ if (burst_cache_expired(bc, now)) { ++ struct task_struct *sibling; ++ u32 count = 0, total = 0, scan_count = 0; ++ ++ for_each_thread(leader, sibling) { ++ if (count >= BURST_CACHE_SAMPLE_LIMIT) break; ++ if (scan_count++ >= BURST_CACHE_SCAN_LIMIT) break; ++ ++ if (!task_is_bore_eligible(sibling)) continue; ++ count++; ++ total += sibling->bore.penalty; ++ } ++ ++ update_burst_cache(bc, leader, count, total, now); ++ } ++ ++ bc_val.value = READ_ONCE(bc->value); ++ return (u32)bc_val.penalty; ++} ++ ++void task_fork_bore(struct task_struct *p, ++ struct task_struct *parent, u64 clone_flags, u64 now) { ++ if (!static_branch_likely(&sched_bore_key) || !task_is_bore_eligible(p)) return; ++ ++ rcu_read_lock(); ++ struct bore_ctx *ctx = &p->bore; ++ u32 inherited_penalty; ++ if (clone_flags & CLONE_THREAD) ++ inherited_penalty = inherit_from_thread_group(parent, now); ++ else if (static_branch_likely(&sched_burst_inherit_key)) ++ inherited_penalty = static_branch_likely(&sched_burst_ancestor_key)? ++ inherit_from_ancestor_hub(parent, clone_flags, now): ++ inherit_from_parent(parent, clone_flags, now); ++ else ++ inherited_penalty = 0; ++ ++ if (ctx->prev_penalty < inherited_penalty) ++ ctx->prev_penalty = inherited_penalty; ++ ctx->curr_penalty = 0; ++ ctx->burst_time = 0; ++ ctx->stop_update = false; ++ ctx->futex_waiting = false; ++ update_penalty(p); ++ rcu_read_unlock(); ++} ++ ++void reset_task_bore(struct task_struct *p) ++{ memset(&p->bore, 0, sizeof(struct bore_ctx)); } ++ ++static void update_inherit_type(void) { ++ switch(sched_burst_inherit_type) { ++ case 1: ++ static_branch_enable(&sched_burst_inherit_key); ++ static_branch_disable(&sched_burst_ancestor_key); ++ break; ++ case 2: ++ static_branch_enable(&sched_burst_inherit_key); ++ static_branch_enable(&sched_burst_ancestor_key); ++ break; ++ default: ++ static_branch_disable(&sched_burst_inherit_key); ++ break; ++ } ++} ++ ++void __init sched_init_bore(void) { ++ printk(KERN_INFO "%s %s by %s\n", ++ SCHED_BORE_PROGNAME, SCHED_BORE_VERSION, SCHED_BORE_AUTHOR); ++ ++ for (int i = 1; i <= BURST_CACHE_SAMPLE_LIMIT; i++) ++ bore_reciprocal_lut[i] = (u32)div64_u64(0xffffffffULL + i, i); ++ ++ reset_task_bore(&init_task); ++ update_inherit_type(); ++} ++ ++static void readjust_all_task_weights(void) { ++ struct task_struct *task; ++ struct rq *rq; ++ struct rq_flags rf; ++ ++ scoped_guard(write_lock_irq, &tasklist_lock) ++ for_each_process(task) { ++ if (!task_is_bore_eligible(task)) continue; ++ rq = task_rq_lock(task, &rf); ++ update_rq_clock(rq); ++ reweight_task_by_prio(task, effective_prio_bore(task)); ++ task_rq_unlock(rq, task, &rf); ++ } ++} ++ ++int sched_bore_update_handler(const struct ctl_table *table, ++ int write, void __user *buffer, size_t *lenp, loff_t *ppos) { ++ int ret = proc_dou8vec_minmax(table, write, buffer, lenp, ppos); ++ if (ret || !write) ++ return ret; ++ ++ if (sched_bore) ++ static_branch_enable(&sched_bore_key); ++ else ++ static_branch_disable(&sched_bore_key); ++ ++ readjust_all_task_weights(); ++ ++ return 0; ++} ++ ++int sched_burst_inherit_type_update_handler(const struct ctl_table *table, ++ int write, void __user *buffer, size_t *lenp, loff_t *ppos) { ++ int ret = proc_dou8vec_minmax(table, write, buffer, lenp, ppos); ++ if (ret || !write) ++ return ret; ++ ++ update_inherit_type(); ++ ++ return 0; ++} ++ ++int sched_burst_protect_slice_lv_update_handler(const struct ctl_table *table, ++ int write, void __user *buffer, size_t *lenp, loff_t *ppos) { ++ int ret = proc_dou8vec_minmax(table, write, buffer, lenp, ppos); ++ if (ret || !write) ++ return ret; ++ ++ if (sched_burst_protect_slice_lv == 1 || ++ sched_burst_protect_slice_lv == 2) ++ static_branch_enable(&sched_burst_protect_slice_cond_key); ++ else ++ static_branch_disable(&sched_burst_protect_slice_cond_key); ++ ++ if (sched_burst_protect_slice_lv >= 2) ++ static_branch_enable(&sched_burst_protect_slice_prefer_key); ++ else ++ static_branch_disable(&sched_burst_protect_slice_prefer_key); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_SYSCTL ++static struct ctl_table sched_bore_sysctls[] = { ++ { ++ .procname = "sched_bore", ++ .data = &sched_bore, ++ .maxlen = sizeof(u8), ++ .mode = 0644, ++ .proc_handler = sched_bore_update_handler, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_ONE, ++ }, ++ { ++ .procname = "sched_burst_inherit_type", ++ .data = &sched_burst_inherit_type, ++ .maxlen = sizeof(u8), ++ .mode = 0644, ++ .proc_handler = sched_burst_inherit_type_update_handler, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_TWO, ++ }, ++ { ++ .procname = "sched_burst_smoothness", ++ .data = &sched_burst_smoothness, ++ .maxlen = sizeof(u8), ++ .mode = 0644, ++ .proc_handler = proc_dou8vec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_THREE, ++ }, ++ { ++ .procname = "sched_burst_penalty_offset", ++ .data = &sched_burst_penalty_offset, ++ .maxlen = sizeof(u8), ++ .mode = 0644, ++ .proc_handler = proc_dou8vec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = &maxval_6_bits, ++ }, ++ { ++ .procname = "sched_burst_penalty_scale", ++ .data = &sched_burst_penalty_scale, ++ .maxlen = sizeof(uint), ++ .mode = 0644, ++ .proc_handler = proc_douintvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = &maxval_12_bits, ++ }, ++ { ++ .procname = "sched_burst_cache_lifetime", ++ .data = &sched_burst_cache_lifetime, ++ .maxlen = sizeof(uint), ++ .mode = 0644, ++ .proc_handler = proc_douintvec, ++ }, ++ { ++ .procname = "sched_burst_protect_slice_lv", ++ .data = &sched_burst_protect_slice_lv, ++ .maxlen = sizeof(u8), ++ .mode = 0644, ++ .proc_handler = sched_burst_protect_slice_lv_update_handler, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_THREE, ++ }, ++}; ++ ++static int __init sched_bore_sysctl_init(void) { ++ register_sysctl_init("kernel", sched_bore_sysctls); ++ return 0; ++} ++late_initcall(sched_bore_sysctl_init); ++ ++#endif // CONFIG_SYSCTL ++#endif /* CONFIG_SCHED_BORE */ +diff --git a/kernel/sched/build_utility.c b/kernel/sched/build_utility.c +--- a/kernel/sched/build_utility.c ++++ b/kernel/sched/build_utility.c +@@ -54,6 +54,10 @@ + #include "stats.h" + #include "autogroup.h" + ++#ifdef CONFIG_SCHED_BORE ++#include ++#endif /* CONFIG_SCHED_BORE */ ++ + #include "clock.c" + + #ifdef CONFIG_CGROUP_CPUACCT +diff --git a/kernel/sched/core.c b/kernel/sched/core.c +--- a/kernel/sched/core.c ++++ b/kernel/sched/core.c +@@ -100,6 +100,10 @@ + #include "../smpboot.h" + #include "../locking/mutex.h" + ++#ifdef CONFIG_SCHED_BORE ++#include ++#endif /* CONFIG_SCHED_BORE */ ++ + EXPORT_TRACEPOINT_SYMBOL_GPL(ipi_send_cpu); + EXPORT_TRACEPOINT_SYMBOL_GPL(ipi_send_cpumask); + +@@ -1534,7 +1538,11 @@ int tg_nop(struct task_group *tg, void *data) + + void set_load_weight(struct task_struct *p, bool update_load) + { ++#ifdef CONFIG_SCHED_BORE ++ int prio = effective_prio_bore(p); ++#else /* !CONFIG_SCHED_BORE */ + int prio = p->static_prio - MAX_RT_PRIO; ++#endif /* CONFIG_SCHED_BORE */ + struct load_weight lw; + + if (task_has_idle_policy(p)) { +@@ -8975,6 +8983,10 @@ void __init sched_init(void) + BUG_ON(!sched_class_above(&ext_sched_class, &idle_sched_class)); + #endif + ++#ifdef CONFIG_SCHED_BORE ++ sched_init_bore(); ++#endif /* CONFIG_SCHED_BORE */ ++ + wait_bit_init(); + + #ifdef CONFIG_FAIR_GROUP_SCHED +diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c +--- a/kernel/sched/debug.c ++++ b/kernel/sched/debug.c +@@ -170,6 +170,53 @@ static const struct file_operations sched_feat_fops = { + .release = single_release, + }; + ++#ifdef CONFIG_SCHED_BORE ++#define DEFINE_SYSCTL_SCHED_FUNC(name, update_func) \ ++static ssize_t sched_##name##_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) \ ++{ \ ++ char buf[16]; \ ++ unsigned int value; \ ++\ ++ if (cnt > 15) \ ++ cnt = 15; \ ++\ ++ if (copy_from_user(&buf, ubuf, cnt)) \ ++ return -EFAULT; \ ++ buf[cnt] = '\0'; \ ++\ ++ if (kstrtouint(buf, 10, &value)) \ ++ return -EINVAL; \ ++\ ++ sysctl_sched_##name = value; \ ++ sched_update_##update_func(); \ ++\ ++ *ppos += cnt; \ ++ return cnt; \ ++} \ ++\ ++static int sched_##name##_show(struct seq_file *m, void *v) \ ++{ \ ++ seq_printf(m, "%d\n", sysctl_sched_##name); \ ++ return 0; \ ++} \ ++\ ++static int sched_##name##_open(struct inode *inode, struct file *filp) \ ++{ \ ++ return single_open(filp, sched_##name##_show, NULL); \ ++} \ ++\ ++static const struct file_operations sched_##name##_fops = { \ ++ .open = sched_##name##_open, \ ++ .write = sched_##name##_write, \ ++ .read = seq_read, \ ++ .llseek = seq_lseek, \ ++ .release = single_release, \ ++}; ++ ++DEFINE_SYSCTL_SCHED_FUNC(min_base_slice, min_base_slice) ++ ++#undef DEFINE_SYSCTL_SCHED_FUNC ++#else /* !CONFIG_SCHED_BORE */ + static ssize_t sched_scaling_write(struct file *filp, const char __user *ubuf, + size_t cnt, loff_t *ppos) + { +@@ -209,6 +256,7 @@ static const struct file_operations sched_scaling_fops = { + .llseek = seq_lseek, + .release = single_release, + }; ++#endif /* CONFIG_SCHED_BORE */ + + #ifdef CONFIG_SCHED_CACHE + static ssize_t +@@ -645,12 +693,19 @@ static __init int sched_init_debug(void) + debugfs_create_file("preempt", 0644, debugfs_sched, NULL, &sched_dynamic_fops); + #endif + ++#ifdef CONFIG_SCHED_BORE ++ debugfs_create_file("min_base_slice_ns", 0644, debugfs_sched, NULL, &sched_min_base_slice_fops); ++ debugfs_create_u32("base_slice_ns", 0444, debugfs_sched, &sysctl_sched_base_slice); ++#else /* !CONFIG_SCHED_BORE */ + debugfs_create_u32("base_slice_ns", 0644, debugfs_sched, &sysctl_sched_base_slice); ++#endif /* CONFIG_SCHED_BORE */ + + debugfs_create_u32("latency_warn_ms", 0644, debugfs_sched, &sysctl_resched_latency_warn_ms); + debugfs_create_u32("latency_warn_once", 0644, debugfs_sched, &sysctl_resched_latency_warn_once); + ++#if !defined(CONFIG_SCHED_BORE) + debugfs_create_file("tunable_scaling", 0644, debugfs_sched, NULL, &sched_scaling_fops); ++#endif /* CONFIG_SCHED_BORE */ + debugfs_create_u32("migration_cost_ns", 0644, debugfs_sched, &sysctl_sched_migration_cost); + debugfs_create_u32("nr_migrate", 0644, debugfs_sched, &sysctl_sched_nr_migrate); + +@@ -911,6 +966,9 @@ print_task(struct seq_file *m, struct rq *rq, struct task_struct *p) + SPLIT_NS(schedstat_val_or_zero(p->stats.sum_sleep_runtime)), + SPLIT_NS(schedstat_val_or_zero(p->stats.sum_block_runtime))); + ++#ifdef CONFIG_SCHED_BORE ++ SEQ_printf(m, " %2d", bore_score(p)); ++#endif /* CONFIG_SCHED_BORE */ + #ifdef CONFIG_NUMA_BALANCING + SEQ_printf(m, " %d %d", task_node(p), task_numa_group_id(p)); + #endif +@@ -1401,6 +1459,9 @@ void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, + __PS("nr_involuntary_switches", p->nivcsw); + + P(se.load.weight); ++#ifdef CONFIG_SCHED_BORE ++ __PS("bore.score", bore_score(p)); ++#endif /* CONFIG_SCHED_BORE */ + P(se.avg.load_sum); + P(se.avg.runnable_sum); + P(se.avg.util_sum); +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -58,6 +58,10 @@ + #include "stats.h" + #include "autogroup.h" + ++#ifdef CONFIG_SCHED_BORE ++#include ++#endif /* CONFIG_SCHED_BORE */ ++ + /* + * The initial- and re-scaling of tunables is configurable + * +@@ -67,17 +71,30 @@ + * SCHED_TUNABLESCALING_LOG - scaled logarithmically, *1+ilog(ncpus) + * SCHED_TUNABLESCALING_LINEAR - scaled linear, *ncpus + * +- * (default SCHED_TUNABLESCALING_LOG = *(1+ilog(ncpus)) ++ * BORE : default SCHED_TUNABLESCALING_NONE = *1 constant ++ * EEVDF: default SCHED_TUNABLESCALING_LOG = *(1+ilog(ncpus)) + */ ++#ifdef CONFIG_SCHED_BORE ++unsigned int sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_NONE; ++#else /* !CONFIG_SCHED_BORE */ + unsigned int sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_LOG; ++#endif /* CONFIG_SCHED_BORE */ + + /* + * Minimal preemption granularity for CPU-bound tasks: + * +- * (default: 0.70 msec * (1 + ilog(ncpus)), units: nanoseconds) ++ * BORE : base_slice = minimum multiple of nsecs_per_tick >= min_base_slice ++ * (default min_base_slice = 2000000 constant, units: nanoseconds) ++ * EEVDF: default 0.70 msec * (1 + ilog(ncpus)), units: nanoseconds + */ ++#ifdef CONFIG_SCHED_BORE ++static const unsigned int nsecs_per_tick = 1000000000ULL / HZ; ++unsigned int sysctl_sched_min_base_slice = CONFIG_MIN_BASE_SLICE_NS; ++__read_mostly uint sysctl_sched_base_slice = nsecs_per_tick; ++#else /* !CONFIG_SCHED_BORE */ + unsigned int sysctl_sched_base_slice = 700000ULL; + static unsigned int normalized_sysctl_sched_base_slice = 700000ULL; ++#endif /* CONFIG_SCHED_BORE */ + + __read_mostly unsigned int sysctl_sched_migration_cost = 500000UL; + +@@ -189,6 +206,13 @@ static inline void update_load_set(struct load_weight *lw, unsigned long w) + * + * This idea comes from the SD scheduler of Con Kolivas: + */ ++#ifdef CONFIG_SCHED_BORE ++static void update_sysctl(void) { ++ sysctl_sched_base_slice = nsecs_per_tick * ++ max(1UL, DIV_ROUND_UP(sysctl_sched_min_base_slice, nsecs_per_tick)); ++} ++void sched_update_min_base_slice(void) { update_sysctl(); } ++#else /* !CONFIG_SCHED_BORE */ + static unsigned int get_update_sysctl_factor(void) + { + unsigned int cpus = min_t(unsigned int, num_online_cpus(), 8); +@@ -219,6 +243,7 @@ static void update_sysctl(void) + SET_SYSCTL(sched_base_slice); + #undef SET_SYSCTL + } ++#endif /* CONFIG_SCHED_BORE */ + + void __init sched_init_granularity(void) + { +@@ -1083,7 +1108,11 @@ struct sched_entity *__pick_first_entity(struct cfs_rq *cfs_rq) + */ + static inline void set_protect_slice(struct cfs_rq *cfs_rq, struct sched_entity *se) + { ++#ifdef CONFIG_SCHED_BORE ++ u64 slice = sysctl_sched_base_slice; ++#else /* CONFIG_SCHED_BORE */ + u64 slice = normalized_sysctl_sched_base_slice; ++#endif /* CONFIG_SCHED_BORE */ + u64 vprot = se->deadline; + + if (sched_feat(RUN_TO_PARITY)) +@@ -1160,8 +1189,17 @@ static struct sched_entity *pick_eevdf(struct cfs_rq *cfs_rq, bool protect) + if (curr && (!curr->on_rq || !entity_eligible(cfs_rq, curr))) + curr = NULL; + +- if (curr && protect && protect_slice(curr)) +- return curr; ++ if (curr && protect && protect_slice(curr)) { ++#ifdef CONFIG_SCHED_BORE ++ if (static_branch_likely(&sched_bore_key)) { ++ if ((static_branch_likely(&sched_burst_protect_slice_cond_key) || ++ static_branch_unlikely(&sched_burst_protect_slice_prefer_key)) && ++ (!entity_is_task(curr) || !task_of(curr)->bore.futex_waiting)) ++ return curr; ++ } else ++#endif /* CONFIG_SCHED_BORE */ ++ return curr; ++ } + + /* Pick the leftmost entity if it's eligible */ + if (se && entity_eligible(cfs_rq, se)) { +@@ -1217,6 +1255,7 @@ struct sched_entity *__pick_last_entity(struct cfs_rq *cfs_rq) + /************************************************************** + * Scheduling class statistics methods: + */ ++#if !defined(CONFIG_SCHED_BORE) + int sched_update_scaling(void) + { + unsigned int factor = get_update_sysctl_factor(); +@@ -1228,6 +1267,7 @@ int sched_update_scaling(void) + + return 0; + } ++#endif /* CONFIG_SCHED_BORE */ + + static void clear_buddies(struct cfs_rq *cfs_rq, struct sched_entity *se); + +@@ -1971,6 +2011,38 @@ static void account_llc_dequeue(struct rq *rq, struct task_struct *p) {} + + #endif /* CONFIG_SCHED_CACHE */ + ++#ifdef CONFIG_SCHED_BORE ++static inline bool do_preempt_weight(struct cfs_rq *cfs_rq, ++ struct sched_entity *pse, struct sched_entity *se) ++{ ++ /* ++ * Companion to the PREEMPT_SHORT path: where that lets a shorter-slice ++ * waker cancel current's slice protection, this lets a heavier (under ++ * BORE: more interactive) waker do so. No RUN_TO_PARITY gate -- in 7.0 ++ * the slice protection (vprot) exists regardless of RUN_TO_PARITY, so ++ * the weight bypass must apply either way. ++ */ ++ if (!static_branch_likely(&sched_burst_protect_slice_cond_key)) ++ return false; ++ ++ if (static_branch_unlikely(&sched_burst_protect_slice_prefer_key) ++ ? (pse->load.weight <= se->load.weight) ++ : (pse->load.weight < se->load.weight)) ++ return false; ++ ++ if (!entity_eligible(cfs_rq, pse)) ++ return false; ++ ++ if (entity_before(pse, se)) ++ return true; ++ ++ if (!entity_eligible(cfs_rq, se)) ++ return true; ++ ++ return false; ++} ++#endif /* CONFIG_SCHED_BORE */ ++ + /* + * Used by other classes to account runtime. + */ +@@ -2006,6 +2078,11 @@ static void update_curr(struct cfs_rq *cfs_rq) + resched = update_deadline(cfs_rq, curr); + + if (entity_is_task(curr)) { ++#ifdef CONFIG_SCHED_BORE ++ struct task_struct *p = task_of(curr); ++ update_curr_bore(p, delta_exec); ++#endif /* CONFIG_SCHED_BORE */ ++ + /* + * If the fair_server is active, we need to account for the + * fair_server time whether or not the task is running on +@@ -4663,8 +4740,8 @@ rescale_entity(struct sched_entity *se, unsigned long weight, bool rel_vprot) + se->vprot = div64_long(se->vprot * old_weight, weight); + } + +-static void reweight_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, +- unsigned long weight) ++void reweight_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, ++ unsigned long weight) + { + bool curr = cfs_rq->curr == se; + bool rel_vprot = false; +@@ -5967,13 +6044,12 @@ void __setparam_fair(struct task_struct *p, const struct sched_attr *attr) + static void + place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) + { +- u64 vslice, vruntime = avg_vruntime(cfs_rq); ++ u64 vslice = 0, vruntime = avg_vruntime(cfs_rq); + bool update_zero = false; + s64 lag = 0; + + if (!se->custom_slice) + se->slice = sysctl_sched_base_slice; +- vslice = calc_delta_fair(se->slice, se); + + /* + * Due to how V is constructed as the weighted average of entities, +@@ -6085,7 +6161,18 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) + se->rel_deadline = 0; + return; + } +- ++#ifdef CONFIG_SCHED_BORE ++ if (static_branch_likely(&sched_bore_key) && ++ entity_is_task(se) && ++ task_of(se)->bore.futex_waiting) ++ goto vslice_found; ++#endif /* !CONFIG_SCHED_BORE */ ++ vslice = calc_delta_fair(se->slice, se); ++#ifdef CONFIG_SCHED_BORE ++ if (static_branch_likely(&sched_bore_key)) ++ vslice >>= !!(flags & (ENQUEUE_INITIAL | ENQUEUE_WAKEUP)); ++ else ++#endif /* CONFIG_SCHED_BORE */ + /* + * When joining the competition; the existing tasks will be, + * on average, halfway through their slice, as such start tasks +@@ -6094,6 +6181,9 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) + if (sched_feat(PLACE_DEADLINE_INITIAL) && (flags & ENQUEUE_INITIAL)) + vslice /= 2; + ++#ifdef CONFIG_SCHED_BORE ++vslice_found: ++#endif /* CONFIG_SCHED_BORE */ + /* + * EEVDF: vd_i = ve_i + r_i/w_i + */ +@@ -6104,7 +6194,7 @@ static void check_enqueue_throttle(struct cfs_rq *cfs_rq); + static inline int cfs_rq_throttled(struct cfs_rq *cfs_rq); + + static void +-requeue_delayed_entity(struct sched_entity *se); ++requeue_delayed_entity(struct sched_entity *se, int flags); + + static void + enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) +@@ -7767,7 +7857,7 @@ static int choose_idle_cpu(int cpu, struct task_struct *p) + } + + static void +-requeue_delayed_entity(struct sched_entity *se) ++requeue_delayed_entity(struct sched_entity *se, int flags) + { + struct cfs_rq *cfs_rq = cfs_rq_of(se); + +@@ -7785,7 +7875,13 @@ requeue_delayed_entity(struct sched_entity *se) + cfs_rq->nr_queued--; + if (se != cfs_rq->curr) + __dequeue_entity(cfs_rq, se); +- place_entity(cfs_rq, se, 0); ++#ifdef CONFIG_SCHED_BORE ++ if (static_branch_likely(&sched_bore_key)) ++ flags |= ENQUEUE_WAKEUP; ++ else ++#endif /* CONFIG_SCHED_BORE */ ++ flags = 0; ++ place_entity(cfs_rq, se, flags); + if (se != cfs_rq->curr) + __enqueue_entity(cfs_rq, se); + cfs_rq->nr_queued++; +@@ -7824,7 +7920,7 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags) + util_est_enqueue(&rq->cfs, p); + + if (flags & ENQUEUE_DELAYED) { +- requeue_delayed_entity(se); ++ requeue_delayed_entity(se, flags); + return; + } + +@@ -7842,7 +7938,7 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags) + for_each_sched_entity(se) { + if (se->on_rq) { + if (se->sched_delayed) +- requeue_delayed_entity(se); ++ requeue_delayed_entity(se, flags); + break; + } + cfs_rq = cfs_rq_of(se); +@@ -8045,6 +8141,15 @@ static bool dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags) + if (!p->se.sched_delayed) + util_est_dequeue(&rq->cfs, p); + ++#ifdef CONFIG_SCHED_BORE ++ struct cfs_rq *cfs_rq = cfs_rq_of(&p->se); ++ struct sched_entity *se = &p->se; ++ if ((flags & DEQUEUE_SLEEP) && entity_is_task(se)) { ++ if (cfs_rq->curr == se) ++ update_curr(cfs_rq); ++ restart_burst_bore(p); ++ } ++#endif /* CONFIG_SCHED_BORE */ + if (dequeue_entities(rq, &p->se, flags) < 0) + return false; + +@@ -9850,6 +9955,18 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f + goto pick; + } + ++#ifdef CONFIG_SCHED_BORE ++ /* ++ * Likewise, if @p is heavier than current (under BORE: more ++ * interactive), per kernel.sched_burst_protect_slice_lv, override ++ * current's slice protection to allow preemption. ++ */ ++ if (do_preempt_weight(cfs_rq, pse, se)) { ++ preempt_action = PREEMPT_WAKEUP_SHORT; ++ goto pick; ++ } ++#endif ++ + /* + * Ignore wakee preemption on WF_FORK as it is less likely that + * there is shared data as exec often follow fork. Do not +@@ -10020,16 +10137,25 @@ static void yield_task_fair(struct rq *rq) + /* + * Are we the only task in the tree? + */ ++#if !defined(CONFIG_SCHED_BORE) + if (unlikely(rq->nr_running == 1)) + return; + + clear_buddies(cfs_rq, se); ++#endif /* CONFIG_SCHED_BORE */ + + update_rq_clock(rq); + /* + * Update run-time statistics of the 'current'. + */ + update_curr(cfs_rq); ++#ifdef CONFIG_SCHED_BORE ++ restart_burst_rescale_deadline_bore(curr); ++ if (unlikely(rq->nr_running == 1)) ++ return; ++ ++ clear_buddies(cfs_rq, se); ++#endif /* CONFIG_SCHED_BORE */ + /* + * Tell update_rq_clock() that we've just updated, + * so we don't do microscopic update in schedule() +@@ -15010,6 +15136,9 @@ static void switched_to_fair(struct rq *rq, struct task_struct *p) + WARN_ON_ONCE(p->se.sched_delayed); + + attach_task_cfs_rq(p); ++#ifdef CONFIG_SCHED_BORE ++ reset_task_bore(p); ++#endif /* CONFIG_SCHED_BORE */ + + set_task_max_allowed_capacity(p); + +diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h +--- a/kernel/sched/sched.h ++++ b/kernel/sched/sched.h +@@ -2267,7 +2267,11 @@ extern int group_balance_cpu(struct sched_group *sg); + extern void update_sched_domain_debugfs(void); + extern void dirty_sched_domain_sysctl(int cpu); + ++#ifdef CONFIG_SCHED_BORE ++extern void sched_update_min_base_slice(void); ++#else /* !CONFIG_SCHED_BORE */ + extern int sched_update_scaling(void); ++#endif /* CONFIG_SCHED_BORE */ + + static inline const struct cpumask *task_user_cpus(struct task_struct *p) + { +@@ -3112,7 +3116,12 @@ static inline void attach_one_task(struct rq *rq, struct task_struct *p) + extern __read_mostly unsigned int sysctl_sched_nr_migrate; + extern __read_mostly unsigned int sysctl_sched_migration_cost; + ++#ifdef CONFIG_SCHED_BORE ++extern unsigned int sysctl_sched_min_base_slice; ++extern __read_mostly uint sysctl_sched_base_slice; ++#else /* !CONFIG_SCHED_BORE */ + extern unsigned int sysctl_sched_base_slice; ++#endif /* CONFIG_SCHED_BORE */ + + extern int sysctl_resched_latency_warn_ms; + extern int sysctl_resched_latency_warn_once; diff --git a/pkgbuilds/linux-omarchy-bore/0120-tlbpull.patch b/pkgbuilds/linux-omarchy-bore/0120-tlbpull.patch new file mode 100644 index 0000000..712b9b7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0120-tlbpull.patch @@ -0,0 +1,763 @@ +diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h +--- a/arch/x86/include/asm/pgtable.h ++++ b/arch/x86/include/asm/pgtable.h +@@ -50,7 +50,7 @@ void ptdump_walk_user_pgd_level_checkwx(void); + extern spinlock_t pgd_lock; + extern struct list_head pgd_list; + +-extern struct mm_struct *pgd_page_get_mm(struct page *page); ++struct mm_struct *pgd_page_get_mm(struct ptdesc *pt); + + extern pmdval_t early_pmd_flags; + +diff --git a/arch/x86/include/asm/pgtable_types.h b/arch/x86/include/asm/pgtable_types.h +--- a/arch/x86/include/asm/pgtable_types.h ++++ b/arch/x86/include/asm/pgtable_types.h +@@ -512,7 +512,7 @@ static inline pgprot_t pgprot_large_2_4k(pgprot_t pgprot) + return __pgprot(protval_large_2_4k(pgprot_val(pgprot))); + } + +- ++struct ptdesc; + typedef struct page *pgtable_t; + + extern pteval_t __supported_pte_mask; +diff --git a/arch/x86/include/asm/pkeys.h b/arch/x86/include/asm/pkeys.h +--- a/arch/x86/include/asm/pkeys.h ++++ b/arch/x86/include/asm/pkeys.h +@@ -88,6 +88,9 @@ int mm_pkey_alloc(struct mm_struct *mm) + u16 all_pkeys_mask = ((1U << arch_max_pkey()) - 1); + int ret; + ++ if (!arch_pkeys_enabled()) ++ return -1; ++ + /* + * Are we out of pkeys? We must handle this specially + * because ffz() behavior is undefined if there are no +diff --git a/arch/x86/include/asm/tlbflush.h b/arch/x86/include/asm/tlbflush.h +--- a/arch/x86/include/asm/tlbflush.h ++++ b/arch/x86/include/asm/tlbflush.h +@@ -4,6 +4,7 @@ + + #include + #include ++#include + #include + + #include +@@ -211,6 +212,12 @@ extern u16 invlpgb_count_max; + + extern void initialize_tlbstate_and_flush(void); + ++/* ++ * Keep stack-allocated flush_tlb_info cacheline aligned, but cap the ++ * alignment to avoid excessive stack usage on large-cacheline systems. ++ */ ++#define FLUSH_TLB_INFO_ALIGN MIN(SMP_CACHE_BYTES, 64) ++ + /* + * TLB flushing: + * +@@ -249,7 +256,7 @@ struct flush_tlb_info { + u8 stride_shift; + u8 freed_tables; + u8 trim_cpumask; +-}; ++} __aligned(FLUSH_TLB_INFO_ALIGN); + + void flush_tlb_local(void); + void flush_tlb_one_user(unsigned long addr); +diff --git a/arch/x86/kernel/kvm.c b/arch/x86/kernel/kvm.c +--- a/arch/x86/kernel/kvm.c ++++ b/arch/x86/kernel/kvm.c +@@ -663,8 +663,10 @@ static void kvm_flush_tlb_multi(const struct cpumask *cpumask, + u8 state; + int cpu; + struct kvm_steal_time *src; +- struct cpumask *flushmask = this_cpu_cpumask_var_ptr(__pv_cpu_mask); ++ struct cpumask *flushmask; + ++ guard(preempt)(); ++ flushmask = this_cpu_cpumask_var_ptr(__pv_cpu_mask); + cpumask_copy(flushmask, cpumask); + /* + * We have to call flush only on online vCPUs. And +diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c +--- a/arch/x86/mm/fault.c ++++ b/arch/x86/mm/fault.c +@@ -275,17 +275,17 @@ void arch_sync_kernel_mappings(unsigned long start, unsigned long end) + for (addr = start & PMD_MASK; + addr >= TASK_SIZE_MAX && addr < VMALLOC_END; + addr += PMD_SIZE) { +- struct page *page; ++ struct ptdesc *ptdesc; + + spin_lock(&pgd_lock); +- list_for_each_entry(page, &pgd_list, lru) { ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + spinlock_t *pgt_lock; + + /* the pgt_lock only for Xen */ +- pgt_lock = &pgd_page_get_mm(page)->page_table_lock; ++ pgt_lock = &pgd_page_get_mm(ptdesc)->page_table_lock; + + spin_lock(pgt_lock); +- vmalloc_sync_one(page_address(page), addr); ++ vmalloc_sync_one(ptdesc_address(ptdesc), addr); + spin_unlock(pgt_lock); + } + spin_unlock(&pgd_lock); +diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c +--- a/arch/x86/mm/init_64.c ++++ b/arch/x86/mm/init_64.c +@@ -136,7 +136,7 @@ static void sync_global_pgds_l5(unsigned long start, unsigned long end) + + for (addr = start; addr <= end; addr = ALIGN(addr + 1, PGDIR_SIZE)) { + const pgd_t *pgd_ref = pgd_offset_k(addr); +- struct page *page; ++ struct ptdesc *ptdesc; + + /* Check for overflow */ + if (addr < start) +@@ -146,13 +146,13 @@ static void sync_global_pgds_l5(unsigned long start, unsigned long end) + continue; + + spin_lock(&pgd_lock); +- list_for_each_entry(page, &pgd_list, lru) { ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + pgd_t *pgd; + spinlock_t *pgt_lock; + +- pgd = (pgd_t *)page_address(page) + pgd_index(addr); ++ pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(addr); + /* the pgt_lock only for Xen */ +- pgt_lock = &pgd_page_get_mm(page)->page_table_lock; ++ pgt_lock = &pgd_page_get_mm(ptdesc)->page_table_lock; + spin_lock(pgt_lock); + + if (!pgd_none(*pgd_ref) && !pgd_none(*pgd)) +@@ -174,7 +174,7 @@ static void sync_global_pgds_l4(unsigned long start, unsigned long end) + for (addr = start; addr <= end; addr = ALIGN(addr + 1, PGDIR_SIZE)) { + pgd_t *pgd_ref = pgd_offset_k(addr); + const p4d_t *p4d_ref; +- struct page *page; ++ struct ptdesc *ptdesc; + + /* + * With folded p4d, pgd_none() is always false, we need to +@@ -187,15 +187,15 @@ static void sync_global_pgds_l4(unsigned long start, unsigned long end) + continue; + + spin_lock(&pgd_lock); +- list_for_each_entry(page, &pgd_list, lru) { ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + pgd_t *pgd; + p4d_t *p4d; + spinlock_t *pgt_lock; + +- pgd = (pgd_t *)page_address(page) + pgd_index(addr); ++ pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(addr); + p4d = p4d_offset(pgd, addr); + /* the pgt_lock only for Xen */ +- pgt_lock = &pgd_page_get_mm(page)->page_table_lock; ++ pgt_lock = &pgd_page_get_mm(ptdesc)->page_table_lock; + spin_lock(pgt_lock); + + if (!p4d_none(*p4d_ref) && !p4d_none(*p4d)) +diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c +--- a/arch/x86/mm/pat/set_memory.c ++++ b/arch/x86/mm/pat/set_memory.c +@@ -62,18 +62,18 @@ enum cpa_warn { + static const int cpa_warn_level = CPA_PROTECT; + + /* +- * Serialize cpa() (for !DEBUG_PAGEALLOC which uses large identity mappings) +- * using cpa_lock. So that we don't allow any other cpu, with stale large tlb +- * entries change the page attribute in parallel to some other cpu +- * splitting a large page entry along with changing the attribute. ++ * Serialize cpa() using cpa_lock so that we don't allow any other cpu, with ++ * stale large tlb entries, to change the page attribute in parallel to some ++ * other cpu splitting a large page entry along with changing the attribute. + */ + static DEFINE_SPINLOCK(cpa_lock); + +-#define CPA_FLUSHTLB 1 +-#define CPA_ARRAY 2 +-#define CPA_PAGES_ARRAY 4 +-#define CPA_NO_CHECK_ALIAS 8 /* Do not search for aliases */ +-#define CPA_COLLAPSE 16 /* try to collapse large pages */ ++#define CPA_FLUSHTLB 0x01 ++#define CPA_ARRAY 0x02 ++#define CPA_PAGES_ARRAY 0x04 ++#define CPA_NO_CHECK_ALIAS 0x08 /* Do not search for aliases */ ++#define CPA_COLLAPSE 0x10 /* try to collapse large pages */ ++#define CPA_DEBUG_PAGEALLOC 0x20 + + static inline pgprot_t cachemode2pgprot(enum page_cache_mode pcm) + { +@@ -86,9 +86,8 @@ static unsigned long direct_pages_count[PG_LEVEL_NUM]; + void update_page_count(int level, unsigned long pages) + { + /* Protect against CPA */ +- spin_lock(&pgd_lock); ++ guard(spinlock)(&pgd_lock); + direct_pages_count[level] += pages; +- spin_unlock(&pgd_lock); + } + + static void split_page_count(int level) +@@ -418,6 +417,8 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa) + int collapsed = 0; + int i; + ++ guard(spinlock)(&cpa_lock); ++ + if (cpa->flags & (CPA_PAGES_ARRAY | CPA_ARRAY)) { + for (i = 0; i < cpa->numpages; i++) + collapsed += collapse_large_pages(__cpa_addr(cpa, i), +@@ -888,24 +889,23 @@ static void __set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte) + { + /* change init_mm */ + set_pte_atomic(kpte, pte); +-#ifdef CONFIG_X86_32 +- { +- struct page *page; + +- list_for_each_entry(page, &pgd_list, lru) { ++ if (IS_ENABLED(CONFIG_X86_32)) { ++ struct ptdesc *ptdesc; ++ ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + pgd_t *pgd; + p4d_t *p4d; + pud_t *pud; + pmd_t *pmd; + +- pgd = (pgd_t *)page_address(page) + pgd_index(address); ++ pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(address); + p4d = p4d_offset(pgd, address); + pud = pud_offset(p4d, address); + pmd = pmd_offset(pud, address); + set_pte_atomic((pte_t *)pmd, pte); + } + } +-#endif + } + + static pgprot_t pgprot_clear_protnone_bits(pgprot_t prot) +@@ -1075,16 +1075,11 @@ static int __should_split_large_page(pte_t *kpte, unsigned long address, + static int should_split_large_page(pte_t *kpte, unsigned long address, + struct cpa_data *cpa) + { +- int do_split; +- + if (cpa->force_split) + return 1; + +- spin_lock(&pgd_lock); +- do_split = __should_split_large_page(kpte, address, cpa); +- spin_unlock(&pgd_lock); +- +- return do_split; ++ guard(spinlock)(&pgd_lock); ++ return __should_split_large_page(kpte, address, cpa); + } + + static void split_set_pte(struct cpa_data *cpa, pte_t *pte, unsigned long pfn, +@@ -1135,16 +1130,14 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, + bool nx, rw; + pte_t *tmp; + +- spin_lock(&pgd_lock); ++ guard(spinlock)(&pgd_lock); + /* + * Check for races, another CPU might have split this page + * up for us already: + */ + tmp = _lookup_address_cpa(cpa, address, &level, &nx, &rw); +- if (tmp != kpte) { +- spin_unlock(&pgd_lock); ++ if (tmp != kpte) + return 1; +- } + + paravirt_alloc_pte(&init_mm, page_to_pfn(base)); + +@@ -1177,7 +1170,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, + break; + + default: +- spin_unlock(&pgd_lock); + return 1; + } + +@@ -1225,7 +1217,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, + * just split large page entry. + */ + flush_tlb_all(); +- spin_unlock(&pgd_lock); + + return 0; + } +@@ -1235,11 +1226,9 @@ static int split_large_page(struct cpa_data *cpa, pte_t *kpte, + { + struct ptdesc *ptdesc; + +- if (!debug_pagealloc_enabled()) +- spin_unlock(&cpa_lock); ++ spin_unlock(&cpa_lock); + ptdesc = pagetable_alloc(GFP_KERNEL, 0); +- if (!debug_pagealloc_enabled()) +- spin_lock(&cpa_lock); ++ spin_lock(&cpa_lock); + if (!ptdesc) + return -ENOMEM; + +@@ -1298,11 +1287,11 @@ static int collapse_pmd_page(pmd_t *pmd, unsigned long addr, + list_add(&page_ptdesc(pmd_page(old_pmd))->pt_list, pgtables); + + if (IS_ENABLED(CONFIG_X86_32)) { +- struct page *page; ++ struct ptdesc *ptdesc; + + /* Update all PGD tables to use the same large page */ +- list_for_each_entry(page, &pgd_list, lru) { +- pgd_t *pgd = (pgd_t *)page_address(page) + pgd_index(addr); ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { ++ pgd_t *pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(addr); + p4d_t *p4d = p4d_offset(pgd, addr); + pud_t *pud = pud_offset(p4d, addr); + pmd_t *pmd = pmd_offset(pud, addr); +@@ -1376,7 +1365,7 @@ static int collapse_pud_page(pud_t *pud, unsigned long addr, + */ + static int collapse_large_pages(unsigned long addr, struct list_head *pgtables) + { +- int collapsed = 0; ++ int collapsed; + pgd_t *pgd; + p4d_t *p4d; + pud_t *pud; +@@ -1384,26 +1373,24 @@ static int collapse_large_pages(unsigned long addr, struct list_head *pgtables) + + addr &= PMD_MASK; + +- spin_lock(&pgd_lock); ++ guard(spinlock)(&pgd_lock); + pgd = pgd_offset_k(addr); + if (pgd_none(*pgd)) +- goto out; ++ return 0; + p4d = p4d_offset(pgd, addr); + if (p4d_none(*p4d)) +- goto out; ++ return 0; + pud = pud_offset(p4d, addr); + if (!pud_present(*pud) || pud_leaf(*pud)) +- goto out; ++ return 0; + pmd = pmd_offset(pud, addr); + if (!pmd_present(*pmd) || pmd_leaf(*pmd)) +- goto out; ++ return 0; + + collapsed = collapse_pmd_page(pmd, addr, pgtables); + if (collapsed) + collapsed += collapse_pud_page(pud, addr, pgtables); + +-out: +- spin_unlock(&pgd_lock); + return collapsed; + } + +@@ -2004,6 +1991,7 @@ static int __change_page_attr_set_clr(struct cpa_data *cpa, int primary) + { + unsigned long numpages = cpa->numpages; + unsigned long rempages = numpages; ++ bool lock = true; + int ret = 0; + + /* +@@ -2013,6 +2001,29 @@ static int __change_page_attr_set_clr(struct cpa_data *cpa, int primary) + !cpa->force_split) + return ret; + ++ /* ++ * DEBUG_PAGEALLOC is special; it is called from any context the ++ * page-allocator is, which violates the normal cpa_lock locking ++ * rules. ++ * ++ * However, since it is part of the page-allocator, things are still ++ * properly serialized by the page-allocator locking and the fact that ++ * when a page is owned by the page-allocator, it isn't owned by ++ * anybody else. That is, you *SHOULD NOT* be calling cpa() on memory ++ * that isn't allocated. ++ * ++ * Additionally, DEBUG_PAGEALLOC ensures (per probe_page_size_mask()) ++ * that the kernel mapping is 4k pages, therefore there are no large ++ * pages to split/collapse. ++ * ++ * Furthermore, the page-allocator strictly manages pages that ++ * *exist*, avoiding pgd_lock. ++ * ++ * Therefore, it is safe to not take cpa_lock. ++ */ ++ if (debug_pagealloc_enabled() && (cpa->flags & CPA_DEBUG_PAGEALLOC)) ++ lock = false; ++ + while (rempages) { + /* + * Store the remaining nr of pages for the large page +@@ -2023,11 +2034,12 @@ static int __change_page_attr_set_clr(struct cpa_data *cpa, int primary) + if (cpa->flags & (CPA_ARRAY | CPA_PAGES_ARRAY)) + cpa->numpages = 1; + +- if (!debug_pagealloc_enabled()) +- spin_lock(&cpa_lock); +- ret = __change_page_attr(cpa, primary); +- if (!debug_pagealloc_enabled()) +- spin_unlock(&cpa_lock); ++ if (lock) { ++ guard(spinlock)(&cpa_lock); ++ ret = __change_page_attr(cpa, primary); ++ } else { ++ ret = __change_page_attr(cpa, primary); ++ } + if (ret) + goto out; + +@@ -2606,7 +2618,7 @@ int set_pages_rw(struct page *page, int numpages) + return set_memory_rw(addr, numpages); + } + +-static int __set_pages_p(struct page *page, int numpages) ++static int __set_pages_p(struct page *page, int numpages, unsigned int cpa_flags) + { + unsigned long tempaddr = (unsigned long) page_address(page); + struct cpa_data cpa = { .vaddr = &tempaddr, +@@ -2614,7 +2626,7 @@ static int __set_pages_p(struct page *page, int numpages) + .numpages = numpages, + .mask_set = __pgprot(_PAGE_PRESENT | _PAGE_RW), + .mask_clr = __pgprot(0), +- .flags = CPA_NO_CHECK_ALIAS }; ++ .flags = CPA_NO_CHECK_ALIAS | cpa_flags }; + + /* + * No alias checking needed for setting present flag. otherwise, +@@ -2625,7 +2637,7 @@ static int __set_pages_p(struct page *page, int numpages) + return __change_page_attr_set_clr(&cpa, 1); + } + +-static int __set_pages_np(struct page *page, int numpages) ++static int __set_pages_np(struct page *page, int numpages, unsigned int cpa_flags) + { + unsigned long tempaddr = (unsigned long) page_address(page); + struct cpa_data cpa = { .vaddr = &tempaddr, +@@ -2633,7 +2645,7 @@ static int __set_pages_np(struct page *page, int numpages) + .numpages = numpages, + .mask_set = __pgprot(0), + .mask_clr = __pgprot(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY), +- .flags = CPA_NO_CHECK_ALIAS }; ++ .flags = CPA_NO_CHECK_ALIAS | cpa_flags }; + + /* + * No alias checking needed for setting not present flag. otherwise, +@@ -2646,20 +2658,20 @@ static int __set_pages_np(struct page *page, int numpages) + + int set_direct_map_invalid_noflush(struct page *page) + { +- return __set_pages_np(page, 1); ++ return __set_pages_np(page, 1, 0); + } + + int set_direct_map_default_noflush(struct page *page) + { +- return __set_pages_p(page, 1); ++ return __set_pages_p(page, 1, 0); + } + + int set_direct_map_valid_noflush(struct page *page, unsigned nr, bool valid) + { + if (valid) +- return __set_pages_p(page, nr); ++ return __set_pages_p(page, nr, 0); + +- return __set_pages_np(page, nr); ++ return __set_pages_np(page, nr, 0); + } + + #ifdef CONFIG_DEBUG_PAGEALLOC +@@ -2678,15 +2690,23 @@ void __kernel_map_pages(struct page *page, int numpages, int enable) + * and hence no memory allocations during large page split. + */ + if (enable) +- __set_pages_p(page, numpages); ++ __set_pages_p(page, numpages, CPA_DEBUG_PAGEALLOC); + else +- __set_pages_np(page, numpages); ++ __set_pages_np(page, numpages, CPA_DEBUG_PAGEALLOC); + + /* +- * We should perform an IPI and flush all tlbs, +- * but that can deadlock->flush only current cpu. +- * Preemption needs to be disabled around __flush_tlb_all() due to +- * CR3 reload in __native_flush_tlb(). ++ * We should perform an IPI and flush all tlbs, but that can ++ * deadlock, settle for a local flush. ++ * ++ * Not doing a global TLB flush means that remote CPUs will retain ++ * stale TLB entries. In case of P->NP (on free) this means the remote ++ * CPUs will not take the faults, making the debug scheme less ++ * reliable. On the NP->P (on alloc) this means the remote CPUs can ++ * take a spurious fault. However spurious_kernel_fault() will observe ++ * *_present() and fix it up. ++ * ++ * Preemption needs to be disabled around __flush_tlb_all() due to CR3 ++ * reload in __native_flush_tlb(). + */ + preempt_disable(); + __flush_tlb_all(); +diff --git a/arch/x86/mm/pgtable.c b/arch/x86/mm/pgtable.c +--- a/arch/x86/mm/pgtable.c ++++ b/arch/x86/mm/pgtable.c +@@ -74,9 +74,9 @@ static void pgd_set_mm(pgd_t *pgd, struct mm_struct *mm) + virt_to_ptdesc(pgd)->pt_mm = mm; + } + +-struct mm_struct *pgd_page_get_mm(struct page *page) ++struct mm_struct *pgd_page_get_mm(struct ptdesc *pt) + { +- return page_ptdesc(page)->pt_mm; ++ return pt->pt_mm; + } + + static void pgd_ctor(struct mm_struct *mm, pgd_t *pgd) +diff --git a/arch/x86/mm/tlb.c b/arch/x86/mm/tlb.c +--- a/arch/x86/mm/tlb.c ++++ b/arch/x86/mm/tlb.c +@@ -1373,35 +1373,19 @@ void flush_tlb_multi(const struct cpumask *cpumask, + */ + unsigned long tlb_single_page_flush_ceiling __read_mostly = 33; + +-static DEFINE_PER_CPU_SHARED_ALIGNED(struct flush_tlb_info, flush_tlb_info); +- +-#ifdef CONFIG_DEBUG_VM +-static DEFINE_PER_CPU(unsigned int, flush_tlb_info_idx); +-#endif +- +-static struct flush_tlb_info *get_flush_tlb_info(struct mm_struct *mm, +- unsigned long start, unsigned long end, +- unsigned int stride_shift, bool freed_tables, +- u64 new_tlb_gen) ++static void init_flush_tlb_info(struct flush_tlb_info *info, ++ struct mm_struct *mm, ++ unsigned long start, unsigned long end, ++ unsigned int stride_shift, bool freed_tables, ++ u64 new_tlb_gen) + { +- struct flush_tlb_info *info = this_cpu_ptr(&flush_tlb_info); +- +-#ifdef CONFIG_DEBUG_VM +- /* +- * Ensure that the following code is non-reentrant and flush_tlb_info +- * is not overwritten. This means no TLB flushing is initiated by +- * interrupt handlers and machine-check exception handlers. +- */ +- BUG_ON(this_cpu_inc_return(flush_tlb_info_idx) != 1); +-#endif +- + /* + * If the number of flushes is so large that a full flush + * would be faster, do a full flush. + */ + if ((end - start) >> stride_shift > tlb_single_page_flush_ceiling) { +- start = 0; +- end = TLB_FLUSH_ALL; ++ start = 0; ++ end = TLB_FLUSH_ALL; + } + + info->start = start; +@@ -1412,32 +1396,21 @@ static struct flush_tlb_info *get_flush_tlb_info(struct mm_struct *mm, + info->new_tlb_gen = new_tlb_gen; + info->initiating_cpu = smp_processor_id(); + info->trim_cpumask = 0; +- +- return info; +-} +- +-static void put_flush_tlb_info(void) +-{ +-#ifdef CONFIG_DEBUG_VM +- /* Complete reentrancy prevention checks */ +- barrier(); +- this_cpu_dec(flush_tlb_info_idx); +-#endif + } + + void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, + unsigned long end, unsigned int stride_shift, + bool freed_tables) + { +- struct flush_tlb_info *info; ++ struct flush_tlb_info info; ++ bool remote_flush = false; + int cpu = get_cpu(); + u64 new_tlb_gen; + + /* This is also a barrier that synchronizes with switch_mm(). */ + new_tlb_gen = inc_mm_tlb_gen(mm); + +- info = get_flush_tlb_info(mm, start, end, stride_shift, freed_tables, +- new_tlb_gen); ++ init_flush_tlb_info(&info, mm, start, end, stride_shift, freed_tables, new_tlb_gen); + + /* + * flush_tlb_multi() is not optimized for the common case in which only +@@ -1445,20 +1418,24 @@ void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, + * flush_tlb_func_local() directly in this case. + */ + if (mm_global_asid(mm)) { +- broadcast_tlb_flush(info); ++ broadcast_tlb_flush(&info); + } else if (cpumask_any_but(mm_cpumask(mm), cpu) < nr_cpu_ids) { +- info->trim_cpumask = should_trim_cpumask(mm); +- flush_tlb_multi(mm_cpumask(mm), info); +- consider_global_asid(mm); ++ remote_flush = true; + } else if (mm == this_cpu_read(cpu_tlbstate.loaded_mm)) { + lockdep_assert_irqs_enabled(); + local_irq_disable(); +- flush_tlb_func(info); ++ flush_tlb_func(&info); + local_irq_enable(); + } + +- put_flush_tlb_info(); + put_cpu(); ++ ++ if (remote_flush) { ++ info.trim_cpumask = should_trim_cpumask(mm); ++ flush_tlb_multi(mm_cpumask(mm), &info); ++ consider_global_asid(mm); ++ } ++ + mmu_notifier_arch_invalidate_secondary_tlbs(mm, start, end); + } + +@@ -1527,19 +1504,16 @@ static void kernel_tlb_flush_range(struct flush_tlb_info *info) + + void flush_tlb_kernel_range(unsigned long start, unsigned long end) + { +- struct flush_tlb_info *info; ++ struct flush_tlb_info info; + + guard(preempt)(); ++ init_flush_tlb_info(&info, NULL, start, end, PAGE_SHIFT, false, ++ TLB_GENERATION_INVALID); + +- info = get_flush_tlb_info(NULL, start, end, PAGE_SHIFT, false, +- TLB_GENERATION_INVALID); +- +- if (info->end == TLB_FLUSH_ALL) +- kernel_tlb_flush_all(info); ++ if (info.end == TLB_FLUSH_ALL) ++ kernel_tlb_flush_all(&info); + else +- kernel_tlb_flush_range(info); +- +- put_flush_tlb_info(); ++ kernel_tlb_flush_range(&info); + } + + /* +@@ -1707,12 +1681,12 @@ EXPORT_SYMBOL_FOR_KVM(__flush_tlb_all); + + void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch) + { +- struct flush_tlb_info *info; +- ++ struct flush_tlb_info info; ++ bool remote_flush = false; + int cpu = get_cpu(); + +- info = get_flush_tlb_info(NULL, 0, TLB_FLUSH_ALL, 0, false, +- TLB_GENERATION_INVALID); ++ init_flush_tlb_info(&info, NULL, 0, TLB_FLUSH_ALL, 0, false, ++ TLB_GENERATION_INVALID); + /* + * flush_tlb_multi() is not optimized for the common case in which only + * a local TLB flush is needed. Optimize this use-case by calling +@@ -1722,18 +1696,20 @@ void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch) + invlpgb_flush_all_nonglobals(); + batch->unmapped_pages = false; + } else if (cpumask_any_but(&batch->cpumask, cpu) < nr_cpu_ids) { +- flush_tlb_multi(&batch->cpumask, info); ++ remote_flush = true; + } else if (cpumask_test_cpu(cpu, &batch->cpumask)) { + lockdep_assert_irqs_enabled(); + local_irq_disable(); +- flush_tlb_func(info); ++ flush_tlb_func(&info); + local_irq_enable(); + } + +- cpumask_clear(&batch->cpumask); +- +- put_flush_tlb_info(); + put_cpu(); ++ ++ if (remote_flush) ++ flush_tlb_multi(&batch->cpumask, &info); ++ ++ cpumask_clear(&batch->cpumask); + } + + /* +diff --git a/arch/x86/xen/mmu_pv.c b/arch/x86/xen/mmu_pv.c +--- a/arch/x86/xen/mmu_pv.c ++++ b/arch/x86/xen/mmu_pv.c +@@ -836,15 +836,15 @@ static void xen_pgd_pin(struct mm_struct *mm) + */ + void xen_mm_pin_all(void) + { +- struct page *page; ++ struct ptdesc *ptdesc; + + spin_lock(&init_mm.page_table_lock); + spin_lock(&pgd_lock); + +- list_for_each_entry(page, &pgd_list, lru) { +- if (!PagePinned(page)) { +- __xen_pgd_pin(&init_mm, (pgd_t *)page_address(page)); +- SetPageSavePinned(page); ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { ++ if (!PagePinned(ptdesc_page(ptdesc))) { ++ __xen_pgd_pin(&init_mm, (pgd_t *)ptdesc_address(ptdesc)); ++ SetPageSavePinned(ptdesc_page(ptdesc)); + } + } + +@@ -947,16 +947,16 @@ static void xen_pgd_unpin(struct mm_struct *mm) + */ + void xen_mm_unpin_all(void) + { +- struct page *page; ++ struct ptdesc *ptdesc; + + spin_lock(&init_mm.page_table_lock); + spin_lock(&pgd_lock); + +- list_for_each_entry(page, &pgd_list, lru) { +- if (PageSavePinned(page)) { +- BUG_ON(!PagePinned(page)); +- __xen_pgd_unpin(&init_mm, (pgd_t *)page_address(page)); +- ClearPageSavePinned(page); ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { ++ if (PageSavePinned(ptdesc_page(ptdesc))) { ++ BUG_ON(!PagePinned(ptdesc_page(ptdesc))); ++ __xen_pgd_unpin(&init_mm, (pgd_t *)ptdesc_address(ptdesc)); ++ ClearPageSavePinned(ptdesc_page(ptdesc)); + } + } + diff --git a/pkgbuilds/linux-omarchy-bore/0121-smp-preempt.patch b/pkgbuilds/linux-omarchy-bore/0121-smp-preempt.patch new file mode 100644 index 0000000..66bd67c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0121-smp-preempt.patch @@ -0,0 +1,514 @@ +diff --git a/include/linux/sched.h b/include/linux/sched.h +--- a/include/linux/sched.h ++++ b/include/linux/sched.h +@@ -848,6 +848,17 @@ struct bore_ctx { + }; + #endif /* CONFIG_SCHED_BORE */ + ++#if defined(CONFIG_SMP) && defined(CONFIG_PREEMPTION) ++struct task_ipi_mask { ++ union { ++ cpumask_t *ipi_mask_ptr; ++ unsigned long ipi_mask_val; ++ }; ++}; ++#else ++struct task_ipi_mask { }; ++#endif ++ + struct task_struct { + #ifdef CONFIG_THREAD_INFO_IN_TASK + /* +@@ -1387,6 +1398,7 @@ struct task_struct { + struct list_head perf_event_list; + struct perf_ctx_data __rcu *perf_ctx_data; + #endif ++ struct task_ipi_mask __private ipi_mask; + #ifdef CONFIG_DEBUG_PREEMPT + unsigned long preempt_disable_ip; + #endif +diff --git a/include/linux/smp.h b/include/linux/smp.h +--- a/include/linux/smp.h ++++ b/include/linux/smp.h +@@ -47,8 +47,7 @@ extern void __smp_call_single_queue(int cpu, struct llist_node *node); + /* total number of cpus in this system (may exceed NR_CPUS) */ + extern unsigned int total_cpus; + +-int smp_call_function_single(int cpuid, smp_call_func_t func, void *info, +- int wait); ++int smp_call_function_single(int cpuid, smp_call_func_t func, void *info, bool wait); + + void on_each_cpu_cond_mask(smp_cond_func_t cond_func, smp_call_func_t func, + void *info, bool wait, const struct cpumask *mask); +@@ -239,6 +238,18 @@ static inline int get_boot_cpu_id(void) + + #endif /* !SMP */ + ++#if defined(CONFIG_PREEMPTION) && defined(CONFIG_SMP) ++int smp_task_ipi_mask_alloc(struct task_struct *task); ++void smp_task_ipi_mask_free(struct task_struct *task); ++#else ++static inline int smp_task_ipi_mask_alloc(struct task_struct *task) ++{ ++ return 0; ++} ++ ++static inline void smp_task_ipi_mask_free(struct task_struct *task) { } ++#endif ++ + /* + * raw_smp_processor_id() - get the current (unstable) CPU id + * +diff --git a/kernel/fork.c b/kernel/fork.c +--- a/kernel/fork.c ++++ b/kernel/fork.c +@@ -547,6 +547,7 @@ void free_task(struct task_struct *tsk) + #endif + release_user_cpus_ptr(tsk); + scs_release(tsk); ++ smp_task_ipi_mask_free(tsk); + + #ifndef CONFIG_THREAD_INFO_IN_TASK + /* +@@ -945,10 +946,14 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node) + #endif + account_kernel_stack(tsk, 1); + +- err = scs_prepare(tsk, node); ++ err = smp_task_ipi_mask_alloc(tsk); + if (err) + goto free_stack; + ++ err = scs_prepare(tsk, node); ++ if (err) ++ goto free_ipi_mask; ++ + #ifdef CONFIG_SECCOMP + /* + * We must handle setting up seccomp filters once we're under +@@ -1026,6 +1031,8 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node) + #endif + return tsk; + ++free_ipi_mask: ++ smp_task_ipi_mask_free(tsk); + free_stack: + exit_task_stack_account(tsk); + free_thread_stack(tsk); +diff --git a/kernel/scftorture.c b/kernel/scftorture.c +--- a/kernel/scftorture.c ++++ b/kernel/scftorture.c +@@ -348,6 +348,8 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + int ret = 0; + struct scf_check *scfcp = NULL; + struct scf_selector *scfsp = scf_sel_rand(trsp); ++ bool is_single = (scfsp->scfs_prim == SCF_PRIM_SINGLE || ++ scfsp->scfs_prim == SCF_PRIM_SINGLE_RPC); + + if (scfsp->scfs_prim == SCF_PRIM_SINGLE || scfsp->scfs_wait) { + scfcp = kmalloc_obj(*scfcp, GFP_ATOMIC); +@@ -364,8 +366,6 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + } + if (use_cpus_read_lock) + cpus_read_lock(); +- else +- preempt_disable(); + switch (scfsp->scfs_prim) { + case SCF_PRIM_RESCHED: + if (IS_BUILTIN(CONFIG_SCF_TORTURE_TEST)) { +@@ -411,13 +411,10 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + if (!ret) { + if (use_cpus_read_lock) + cpus_read_unlock(); +- else +- preempt_enable(); ++ + wait_for_completion(&scfcp->scfc_completion); + if (use_cpus_read_lock) + cpus_read_lock(); +- else +- preempt_disable(); + } else { + scfp->n_single_rpc_ofl++; + scf_add_to_free_list(scfcp); +@@ -452,7 +449,7 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + scfcp->scfc_out = true; + } + if (scfcp && scfsp->scfs_wait) { +- if (WARN_ON_ONCE((num_online_cpus() > 1 || scfsp->scfs_prim == SCF_PRIM_SINGLE) && ++ if (WARN_ON_ONCE(((use_cpus_read_lock && num_online_cpus() > 1) || is_single) && + !scfcp->scfc_out)) { + pr_warn("%s: Memory-ordering failure, scfs_prim: %d.\n", __func__, scfsp->scfs_prim); + atomic_inc(&n_mb_out_errs); // Leak rather than trash! +@@ -463,8 +460,6 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + } + if (use_cpus_read_lock) + cpus_read_unlock(); +- else +- preempt_enable(); + if (allocfail) + schedule_timeout_idle((1 + longwait) * HZ); // Let no-wait handlers complete. + else if (!(torture_random(trsp) & 0xfff)) +diff --git a/kernel/smp.c b/kernel/smp.c +--- a/kernel/smp.c ++++ b/kernel/smp.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -63,7 +64,14 @@ int smpcfd_prepare_cpu(unsigned int cpu) + free_cpumask_var(cfd->cpumask); + return -ENOMEM; + } +- cfd->csd = alloc_percpu(call_single_data_t); ++ ++ /* ++ * Allocate the per-CPU CSD the first time a CPU comes up. It is ++ * not freed when the CPU is offlined, so csd_lock_wait() can access ++ * it even when the CPU was offlined after preemption was re-enabled. ++ */ ++ if (!cfd->csd) ++ cfd->csd = alloc_percpu(call_single_data_t); + if (!cfd->csd) { + free_cpumask_var(cfd->cpumask); + free_cpumask_var(cfd->cpumask_ipi); +@@ -79,7 +87,6 @@ int smpcfd_dead_cpu(unsigned int cpu) + + free_cpumask_var(cfd->cpumask); + free_cpumask_var(cfd->cpumask_ipi); +- free_percpu(cfd->csd); + return 0; + } + +@@ -323,6 +330,8 @@ static void __csd_lock_wait(call_single_data_t *csd) + int bug_id = 0; + u64 ts0, ts1; + ++ guard(preempt)(); ++ + ts1 = ts0 = ktime_get_mono_fast_ns(); + for (;;) { + if (csd_lock_wait_toolong(csd, ts0, &ts1, &bug_id, &nmessages)) +@@ -659,17 +668,9 @@ void flush_smp_call_function_queue(void) + local_irq_restore(flags); + } + +-/** +- * smp_call_function_single - Run a function on a specific CPU +- * @cpu: Specific target CPU for this function. +- * @func: The function to run. This must be fast and non-blocking. +- * @info: An arbitrary pointer to pass to the function. +- * @wait: If true, wait until function has completed on other CPUs. +- * +- * Returns: %0 on success, else a negative status code. +- */ +-int smp_call_function_single(int cpu, smp_call_func_t func, void *info, +- int wait) ++static int __smp_call_function_single(int cpu, smp_call_func_t func, ++ void *info, const struct cpumask *mask, ++ bool wait) + { + call_single_data_t *csd; + call_single_data_t csd_stack = { +@@ -686,6 +687,14 @@ int smp_call_function_single(int cpu, smp_call_func_t func, void *info, + */ + this_cpu = get_cpu(); + ++ if (mask) { ++ /* Try for same CPU (cheapest) */ ++ if (!cpumask_test_cpu(this_cpu, mask)) ++ cpu = sched_numa_find_nth_cpu(mask, 0, cpu_to_node(this_cpu)); ++ else ++ cpu = this_cpu; ++ } ++ + /* + * Can deadlock when called with interrupts disabled. + * We allow cpu's that are not yet online though, as no one else can +@@ -718,13 +727,32 @@ int smp_call_function_single(int cpu, smp_call_func_t func, void *info, + + err = generic_exec_single(cpu, csd); + ++ /* ++ * @csd is stack-allocated when @wait is true. No concurrent access ++ * except from the IPI completion path, so we can re-enable preemption ++ * early to reduce latency. ++ */ ++ put_cpu(); ++ + if (wait) + csd_lock_wait(csd); + +- put_cpu(); +- + return err; + } ++ ++/** ++ * smp_call_function_single - Run a function on a specific CPU ++ * @cpu: Specific target CPU for this function. ++ * @func: The function to run. This must be fast and non-blocking. ++ * @info: An arbitrary pointer to pass to the function. ++ * @wait: If true, wait until function has completed on other CPUs. ++ * ++ * Returns: %0 on success, else a negative status code. ++ */ ++int smp_call_function_single(int cpu, smp_call_func_t func, void *info, bool wait) ++{ ++ return __smp_call_function_single(cpu, func, info, NULL, wait); ++} + EXPORT_SYMBOL(smp_call_function_single); + + /** +@@ -775,10 +803,10 @@ EXPORT_SYMBOL_GPL(smp_call_function_single_async); + + /** + * smp_call_function_any - Run a function on any of the given cpus +- * @mask: The mask of cpus it can run on. +- * @func: The function to run. This must be fast and non-blocking. +- * @info: An arbitrary pointer to pass to the function. +- * @wait: If true, wait until function has completed. ++ * @mask: The mask of cpus it can run on. ++ * @func: The function to run. This must be fast and non-blocking. ++ * @info: An arbitrary pointer to pass to the function. ++ * @wait: If true, wait until function has completed. + * + * Selection preference: + * 1) current cpu if in @mask +@@ -789,20 +817,54 @@ EXPORT_SYMBOL_GPL(smp_call_function_single_async); + int smp_call_function_any(const struct cpumask *mask, + smp_call_func_t func, void *info, int wait) + { +- unsigned int cpu; +- int ret; +- +- /* Try for same CPU (cheapest) */ +- cpu = get_cpu(); +- if (!cpumask_test_cpu(cpu, mask)) +- cpu = sched_numa_find_nth_cpu(mask, 0, cpu_to_node(cpu)); +- +- ret = smp_call_function_single(cpu, func, info, wait); +- put_cpu(); +- return ret; ++ return __smp_call_function_single(-1, func, info, mask, wait); + } + EXPORT_SYMBOL_GPL(smp_call_function_any); + ++static DEFINE_STATIC_KEY_FALSE(ipi_mask_inlined); ++ ++#ifdef CONFIG_PREEMPTION ++ ++int smp_task_ipi_mask_alloc(struct task_struct *task) ++{ ++ if (static_branch_unlikely(&ipi_mask_inlined)) ++ return 0; ++ ++ ACCESS_PRIVATE(task, ipi_mask).ipi_mask_ptr = ++ kmalloc(cpumask_size(), GFP_KERNEL); ++ if (!ACCESS_PRIVATE(task, ipi_mask).ipi_mask_ptr) ++ return -ENOMEM; ++ ++ return 0; ++} ++ ++void smp_task_ipi_mask_free(struct task_struct *task) ++{ ++ if (static_branch_unlikely(&ipi_mask_inlined)) ++ return; ++ ++ kfree(ACCESS_PRIVATE(task, ipi_mask).ipi_mask_ptr); ++} ++ ++static cpumask_t *smp_task_ipi_mask(struct task_struct *cur) ++{ ++ /* ++ * If cpumask_size() is smaller than or equal to the pointer ++ * size, it stashes the cpumask in the pointer itself to ++ * avoid extra memory allocations. ++ */ ++ if (static_branch_unlikely(&ipi_mask_inlined)) ++ return (cpumask_t *)&ACCESS_PRIVATE(cur, ipi_mask).ipi_mask_val; ++ ++ return ACCESS_PRIVATE(cur, ipi_mask).ipi_mask_ptr; ++} ++#else ++static cpumask_t *smp_task_ipi_mask(struct task_struct *cur) ++{ ++ return NULL; ++} ++#endif ++ + /* + * Flags to be used as scf_flags argument of smp_call_function_many_cond(). + * +@@ -817,13 +879,20 @@ static void smp_call_function_many_cond(const struct cpumask *mask, + unsigned int scf_flags, + smp_cond_func_t cond_func) + { +- int cpu, last_cpu, this_cpu = smp_processor_id(); +- struct call_function_data *cfd; ++ struct cpumask *cpumask, *task_mask; + bool wait = scf_flags & SCF_WAIT; +- int nr_cpus = 0; ++ struct call_function_data *cfd; ++ int cpu, last_cpu, this_cpu; + bool run_remote = false; ++ int nr_cpus = 0; + +- lockdep_assert_preemption_disabled(); ++ this_cpu = get_cpu(); ++ cfd = this_cpu_ptr(&cfd_data); ++ task_mask = smp_task_ipi_mask(current); ++ if (task_mask) ++ cpumask = task_mask; ++ else ++ cpumask = cfd->cpumask; + + /* + * Can deadlock when called with interrupts disabled. +@@ -845,16 +914,15 @@ static void smp_call_function_many_cond(const struct cpumask *mask, + + /* Check if we need remote execution, i.e., any CPU excluding this one. */ + if (cpumask_any_and_but(mask, cpu_online_mask, this_cpu) < nr_cpu_ids) { +- cfd = this_cpu_ptr(&cfd_data); +- cpumask_and(cfd->cpumask, mask, cpu_online_mask); +- __cpumask_clear_cpu(this_cpu, cfd->cpumask); ++ cpumask_and(cpumask, mask, cpu_online_mask); ++ __cpumask_clear_cpu(this_cpu, cpumask); + + cpumask_clear(cfd->cpumask_ipi); +- for_each_cpu(cpu, cfd->cpumask) { ++ for_each_cpu(cpu, cpumask) { + call_single_data_t *csd = per_cpu_ptr(cfd->csd, cpu); + + if (cond_func && !cond_func(cpu, info)) { +- __cpumask_clear_cpu(cpu, cfd->cpumask); ++ __cpumask_clear_cpu(cpu, cpumask); + continue; + } + +@@ -904,8 +972,18 @@ static void smp_call_function_many_cond(const struct cpumask *mask, + local_irq_restore(flags); + } + ++ /* ++ * The IPI work has been queued and dispatched. On PREEMPT kernels, ++ * tasks created through dup_task_struct() have task-local wait masks. ++ * The boot init_task can fall back to cfd->cpumask when the mask is ++ * not inlined, but other tasks still use task-local masks and cannot ++ * overwrite it. On !PREEMPT kernels, preempt_enable() cannot schedule ++ * another task, so the per-CPU mask remains protected. ++ */ ++ put_cpu(); ++ + if (run_remote && wait) { +- for_each_cpu(cpu, cfd->cpumask) { ++ for_each_cpu(cpu, cpumask) { + call_single_data_t *csd; + + csd = per_cpu_ptr(cfd->csd, cpu); +@@ -916,15 +994,14 @@ static void smp_call_function_many_cond(const struct cpumask *mask, + + /** + * smp_call_function_many() - Run a function on a set of CPUs. +- * @mask: The set of cpus to run on (only runs on online subset). +- * @func: The function to run. This must be fast and non-blocking. +- * @info: An arbitrary pointer to pass to the function. +- * @wait: If true, wait (atomically) until function has completed +- * on other CPUs. ++ * @mask: The set of cpus to run on (only runs on online subset). ++ * @func: The function to run. This must be fast and non-blocking. ++ * @info: An arbitrary pointer to pass to the function. ++ * @wait: If true, wait (atomically) until function has completed ++ * on other CPUs. + * + * You must not call this function with disabled interrupts or from a +- * hardware interrupt handler or from a bottom half handler. Preemption +- * must be disabled when calling this function. ++ * hardware interrupt handler or from a bottom half handler. + * + * @func is not called on the local CPU even if @mask contains it. Consider + * using on_each_cpu_cond_mask() instead if this is not desirable. +@@ -938,10 +1015,10 @@ EXPORT_SYMBOL(smp_call_function_many); + + /** + * smp_call_function() - Run a function on all other CPUs. +- * @func: The function to run. This must be fast and non-blocking. +- * @info: An arbitrary pointer to pass to the function. +- * @wait: If true, wait (atomically) until function has completed +- * on other CPUs. ++ * @func: The function to run. This must be fast and non-blocking. ++ * @info: An arbitrary pointer to pass to the function. ++ * @wait: If true, wait (atomically) until function has completed ++ * on other CPUs. + * + * If @wait is true, then returns once @func has returned; otherwise + * it returns just before the target cpu calls @func. +@@ -951,9 +1028,8 @@ EXPORT_SYMBOL(smp_call_function_many); + */ + void smp_call_function(smp_call_func_t func, void *info, int wait) + { +- preempt_disable(); +- smp_call_function_many(cpu_online_mask, func, info, wait); +- preempt_enable(); ++ smp_call_function_many_cond(cpu_online_mask, func, info, ++ wait ? SCF_WAIT : 0, NULL); + } + EXPORT_SYMBOL(smp_call_function); + +@@ -1019,6 +1095,9 @@ EXPORT_SYMBOL(nr_cpu_ids); + void __init setup_nr_cpu_ids(void) + { + set_nr_cpu_ids(find_last_bit(cpumask_bits(cpu_possible_mask), NR_CPUS) + 1); ++ ++ if (IS_ENABLED(CONFIG_PREEMPTION) && cpumask_size() <= sizeof(unsigned long)) ++ static_branch_enable(&ipi_mask_inlined); + } + + /* Called by boot processor to activate the rest. */ +@@ -1055,12 +1134,14 @@ void __init smp_init(void) + * @func: The function to run on all applicable CPUs. + * This must be fast and non-blocking. + * @info: An arbitrary pointer to pass to both functions. +- * @wait: If true, wait (atomically) until function has +- * completed on other CPUs. ++ * @wait: If true, wait until function has completed on other CPUs. + * @mask: The set of cpus to run on (only runs on online subset). + * +- * Preemption is disabled to protect against CPUs going offline but not online. +- * CPUs going online during the call will not be seen or sent an IPI. ++ * Target CPU selection and work queueing are done with preemption ++ * disabled. This protects against CPUs going offline, but not against ++ * CPUs coming online concurrently; newly online CPUs are not guaranteed ++ * to be seen or sent an IPI. If @wait is true, the final wait for remote ++ * completion happens after that preemption-disabled section. + * + * You must not call this function with disabled interrupts or + * from a hardware interrupt handler or from a bottom half handler. +@@ -1073,9 +1154,7 @@ void on_each_cpu_cond_mask(smp_cond_func_t cond_func, smp_call_func_t func, + if (wait) + scf_flags |= SCF_WAIT; + +- preempt_disable(); + smp_call_function_many_cond(mask, func, info, scf_flags, cond_func); +- preempt_enable(); + } + EXPORT_SYMBOL(on_each_cpu_cond_mask); + +diff --git a/kernel/up.c b/kernel/up.c +--- a/kernel/up.c ++++ b/kernel/up.c +@@ -9,8 +9,7 @@ + #include + #include + +-int smp_call_function_single(int cpu, void (*func) (void *info), void *info, +- int wait) ++int smp_call_function_single(int cpu, void (*func)(void *info), void *info, bool wait) + { + unsigned long flags; + diff --git a/pkgbuilds/linux-omarchy-bore/0130-sched-detach-tasks.patch b/pkgbuilds/linux-omarchy-bore/0130-sched-detach-tasks.patch new file mode 100644 index 0000000..9c155d1 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0130-sched-detach-tasks.patch @@ -0,0 +1,21 @@ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -13464,12 +13464,15 @@ static int sched_balance_rq(int this_cpu, struct rq *this_rq, + * still unbalanced. ld_moved simply stays zero, so it is + * correctly treated as an imbalance. + */ +- env.loop_max = min(sysctl_sched_nr_migrate, busiest->nr_running); +- + more_balance: + rq_lock_irqsave(busiest, &rf); + update_rq_clock(busiest); + ++ if (!env.loop_max) ++ env.loop_max = min(sysctl_sched_nr_migrate, busiest->cfs.h_nr_queued); ++ else ++ env.loop_max = min(env.loop_max, busiest->cfs.h_nr_queued); ++ + /* + * cur_ld_moved - load moved in current iteration + * ld_moved - cumulative load moved across iterations diff --git a/pkgbuilds/linux-omarchy-bore/0131-sched-avg-idle.patch b/pkgbuilds/linux-omarchy-bore/0131-sched-avg-idle.patch new file mode 100644 index 0000000..cdb96f8 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0131-sched-avg-idle.patch @@ -0,0 +1,23 @@ +diff --git a/kernel/sched/core.c b/kernel/sched/core.c +--- a/kernel/sched/core.c ++++ b/kernel/sched/core.c +@@ -3751,11 +3751,17 @@ static inline void ttwu_do_wakeup(struct task_struct *p) + + void update_rq_avg_idle(struct rq *rq) + { +- u64 delta = rq_clock(rq) - rq->idle_stamp; +- u64 max = 2*rq->max_idle_balance_cost; ++ u64 idle_stamp = rq->idle_stamp; ++ u64 delta, max; ++ ++ if (!idle_stamp) ++ return; ++ ++ delta = rq_clock(rq) - idle_stamp; + + update_avg(&rq->avg_idle, delta); + ++ max = 2 * rq->max_idle_balance_cost; + if (rq->avg_idle > max) + rq->avg_idle = max; + rq->idle_stamp = 0; diff --git a/pkgbuilds/linux-omarchy-bore/0140-sched-always-inline.patch b/pkgbuilds/linux-omarchy-bore/0140-sched-always-inline.patch new file mode 100644 index 0000000..8754792 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0140-sched-always-inline.patch @@ -0,0 +1,367 @@ +diff --git a/arch/arm/include/asm/mmu_context.h b/arch/arm/include/asm/mmu_context.h +--- a/arch/arm/include/asm/mmu_context.h ++++ b/arch/arm/include/asm/mmu_context.h +@@ -80,7 +80,7 @@ static inline void check_and_switch_context(struct mm_struct *mm, + #ifndef MODULE + #define finish_arch_post_lock_switch \ + finish_arch_post_lock_switch +-static inline void finish_arch_post_lock_switch(void) ++static __always_inline void finish_arch_post_lock_switch(void) + { + struct mm_struct *mm = current->mm; + +diff --git a/arch/riscv/include/asm/sync_core.h b/arch/riscv/include/asm/sync_core.h +--- a/arch/riscv/include/asm/sync_core.h ++++ b/arch/riscv/include/asm/sync_core.h +@@ -6,7 +6,7 @@ + * RISC-V implements return to user-space through an xRET instruction, + * which is not core serializing. + */ +-static inline void sync_core_before_usermode(void) ++static __always_inline void sync_core_before_usermode(void) + { + asm volatile ("fence.i" ::: "memory"); + } +diff --git a/arch/s390/include/asm/mmu_context.h b/arch/s390/include/asm/mmu_context.h +--- a/arch/s390/include/asm/mmu_context.h ++++ b/arch/s390/include/asm/mmu_context.h +@@ -93,7 +93,7 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, + } + + #define finish_arch_post_lock_switch finish_arch_post_lock_switch +-static inline void finish_arch_post_lock_switch(void) ++static __always_inline void finish_arch_post_lock_switch(void) + { + struct task_struct *tsk = current; + struct mm_struct *mm = tsk->mm; +diff --git a/arch/sparc/include/asm/mmu_context_64.h b/arch/sparc/include/asm/mmu_context_64.h +--- a/arch/sparc/include/asm/mmu_context_64.h ++++ b/arch/sparc/include/asm/mmu_context_64.h +@@ -160,7 +160,7 @@ static inline void arch_start_context_switch(struct task_struct *prev) + } + + #define finish_arch_post_lock_switch finish_arch_post_lock_switch +-static inline void finish_arch_post_lock_switch(void) ++static __always_inline void finish_arch_post_lock_switch(void) + { + /* Restore the state of MCDPER register for the new process + * just switched to. +diff --git a/arch/x86/include/asm/sync_core.h b/arch/x86/include/asm/sync_core.h +--- a/arch/x86/include/asm/sync_core.h ++++ b/arch/x86/include/asm/sync_core.h +@@ -93,7 +93,7 @@ static __always_inline void sync_core(void) + * to user-mode. x86 implements return to user-space through sysexit, + * sysrel, and sysretq, which are not core serializing. + */ +-static inline void sync_core_before_usermode(void) ++static __always_inline void sync_core_before_usermode(void) + { + /* With PTI, we unconditionally serialize before running user code. */ + if (static_cpu_has(X86_FEATURE_PTI)) +diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h +--- a/include/linux/perf_event.h ++++ b/include/linux/perf_event.h +@@ -1632,7 +1632,7 @@ static inline void perf_event_task_migrate(struct task_struct *task) + task->sched_migrated = 1; + } + +-static inline void perf_event_task_sched_in(struct task_struct *prev, ++static __always_inline void perf_event_task_sched_in(struct task_struct *prev, + struct task_struct *task) + { + if (static_branch_unlikely(&perf_sched_events)) +diff --git a/include/linux/sched/mm.h b/include/linux/sched/mm.h +--- a/include/linux/sched/mm.h ++++ b/include/linux/sched/mm.h +@@ -32,7 +32,7 @@ extern struct mm_struct *mm_alloc(void); + * See also for an in-depth explanation + * of &mm_struct.mm_count vs &mm_struct.mm_users. + */ +-static inline void mmgrab(struct mm_struct *mm) ++static __always_inline void mmgrab(struct mm_struct *mm) + { + atomic_inc(&mm->mm_count); + } +@@ -44,7 +44,7 @@ static inline void smp_mb__after_mmgrab(void) + + extern void __mmdrop(struct mm_struct *mm); + +-static inline void mmdrop(struct mm_struct *mm) ++static __always_inline void mmdrop(struct mm_struct *mm) + { + /* + * The implicit full barrier implied by atomic_dec_and_test() is +@@ -71,27 +71,27 @@ static inline void __mmdrop_delayed(struct rcu_head *rhp) + * Invoked from finish_task_switch(). Delegates the heavy lifting on RT + * kernels via RCU. + */ +-static inline void mmdrop_sched(struct mm_struct *mm) ++static __always_inline void mmdrop_sched(struct mm_struct *mm) + { + /* Provides a full memory barrier. See mmdrop() */ + if (atomic_dec_and_test(&mm->mm_count)) + call_rcu(&mm->delayed_drop, __mmdrop_delayed); + } + #else +-static inline void mmdrop_sched(struct mm_struct *mm) ++static __always_inline void mmdrop_sched(struct mm_struct *mm) + { + mmdrop(mm); + } + #endif + + /* Helpers for lazy TLB mm refcounting */ +-static inline void mmgrab_lazy_tlb(struct mm_struct *mm) ++static __always_inline void mmgrab_lazy_tlb(struct mm_struct *mm) + { + if (IS_ENABLED(CONFIG_MMU_LAZY_TLB_REFCOUNT)) + mmgrab(mm); + } + +-static inline void mmdrop_lazy_tlb(struct mm_struct *mm) ++static __always_inline void mmdrop_lazy_tlb(struct mm_struct *mm) + { + if (IS_ENABLED(CONFIG_MMU_LAZY_TLB_REFCOUNT)) { + mmdrop(mm); +@@ -104,7 +104,7 @@ static inline void mmdrop_lazy_tlb(struct mm_struct *mm) + } + } + +-static inline void mmdrop_lazy_tlb_sched(struct mm_struct *mm) ++static __always_inline void mmdrop_lazy_tlb_sched(struct mm_struct *mm) + { + if (IS_ENABLED(CONFIG_MMU_LAZY_TLB_REFCOUNT)) + mmdrop_sched(mm); +@@ -128,12 +128,12 @@ static inline void mmdrop_lazy_tlb_sched(struct mm_struct *mm) + * See also for an in-depth explanation + * of &mm_struct.mm_count vs &mm_struct.mm_users. + */ +-static inline void mmget(struct mm_struct *mm) ++static __always_inline void mmget(struct mm_struct *mm) + { + atomic_inc(&mm->mm_users); + } + +-static inline bool mmget_not_zero(struct mm_struct *mm) ++static __always_inline bool mmget_not_zero(struct mm_struct *mm) + { + return atomic_inc_not_zero(&mm->mm_users); + } +@@ -532,7 +532,7 @@ enum { + #include + #endif + +-static inline void membarrier_mm_sync_core_before_usermode(struct mm_struct *mm) ++static __always_inline void membarrier_mm_sync_core_before_usermode(struct mm_struct *mm) + { + /* + * The atomic_read() below prevents CSE. The following should +diff --git a/include/linux/tick.h b/include/linux/tick.h +--- a/include/linux/tick.h ++++ b/include/linux/tick.h +@@ -175,7 +175,7 @@ extern cpumask_var_t tick_nohz_full_mask; + #ifdef CONFIG_NO_HZ_FULL + extern bool tick_nohz_full_running; + +-static inline bool tick_nohz_full_enabled(void) ++static __always_inline bool tick_nohz_full_enabled(void) + { + if (!context_tracking_enabled()) + return false; +@@ -299,7 +299,7 @@ static inline void __tick_nohz_task_switch(void) { } + static inline void tick_nohz_full_setup(cpumask_var_t cpumask) { } + #endif + +-static inline void tick_nohz_task_switch(void) ++static __always_inline void tick_nohz_task_switch(void) + { + if (tick_nohz_full_enabled()) + __tick_nohz_task_switch(); +diff --git a/include/linux/vtime.h b/include/linux/vtime.h +--- a/include/linux/vtime.h ++++ b/include/linux/vtime.h +@@ -89,24 +89,24 @@ static __always_inline void vtime_account_guest_exit(void) + * For now vtime state is tied to context tracking. We might want to decouple + * those later if necessary. + */ +-static inline bool vtime_accounting_enabled(void) ++static __always_inline bool vtime_accounting_enabled(void) + { + return context_tracking_enabled(); + } + +-static inline bool vtime_accounting_enabled_cpu(int cpu) ++static __always_inline bool vtime_accounting_enabled_cpu(int cpu) + { + return vtime_generic_enabled_cpu(cpu); + } + +-static inline bool vtime_accounting_enabled_this_cpu(void) ++static __always_inline bool vtime_accounting_enabled_this_cpu(void) + { + return vtime_generic_enabled_this_cpu(); + } + + extern void vtime_task_switch_generic(struct task_struct *prev); + +-static inline void vtime_task_switch(struct task_struct *prev) ++static __always_inline void vtime_task_switch(struct task_struct *prev) + { + if (vtime_accounting_enabled_this_cpu()) + vtime_task_switch_generic(prev); +diff --git a/kernel/sched/core.c b/kernel/sched/core.c +--- a/kernel/sched/core.c ++++ b/kernel/sched/core.c +@@ -5099,7 +5099,7 @@ static inline void prepare_task(struct task_struct *next) + WRITE_ONCE(next->on_cpu, 1); + } + +-static inline void finish_task(struct task_struct *prev) ++static __always_inline void finish_task(struct task_struct *prev) + { + /* + * This must be the very last reference to @prev from this CPU. After +@@ -5143,7 +5143,7 @@ static void zap_balance_callbacks(struct rq *rq) + rq->balance_callback = found ? &balance_push_callback : NULL; + } + +-static void do_balance_callbacks(struct rq *rq, struct balance_callback *head) ++static __always_inline void do_balance_callbacks(struct rq *rq, struct balance_callback *head) + { + void (*func)(struct rq *rq); + struct balance_callback *next; +@@ -5178,7 +5178,7 @@ struct balance_callback balance_push_callback = { + .func = balance_push, + }; + +-static inline struct balance_callback * ++static __always_inline struct balance_callback * + __splice_balance_callbacks(struct rq *rq, bool split) + { + struct balance_callback *head = rq->balance_callback; +@@ -5252,7 +5252,7 @@ prepare_lock_switch(struct rq *rq, struct task_struct *next, struct rq_flags *rf + __acquire(__rq_lockp(this_rq())); + } + +-static inline void finish_lock_switch(struct rq *rq) ++static __always_inline void finish_lock_switch(struct rq *rq) + __releases(__rq_lockp(rq)) + { + /* +@@ -5286,7 +5286,7 @@ static inline void kmap_local_sched_out(void) + #endif + } + +-static inline void kmap_local_sched_in(void) ++static __always_inline void kmap_local_sched_in(void) + { + #ifdef CONFIG_KMAP_LOCAL + if (unlikely(current->kmap_ctrl.idx)) +@@ -5340,7 +5340,7 @@ prepare_task_switch(struct rq *rq, struct task_struct *prev, + * past. 'prev == current' is still correct but we need to recalculate this_rq + * because prev may have moved to another CPU. + */ +-static struct rq *finish_task_switch(struct task_struct *prev) ++static __always_inline struct rq *finish_task_switch(struct task_struct *prev) + __releases(__rq_lockp(this_rq())) + { + struct rq *rq = this_rq(); +diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h +--- a/kernel/sched/sched.h ++++ b/kernel/sched/sched.h +@@ -1460,12 +1460,12 @@ static inline struct cpumask *sched_group_span(struct sched_group *sg); + + DECLARE_STATIC_KEY_FALSE(__sched_core_enabled); + +-static inline bool sched_core_enabled(struct rq *rq) ++static __always_inline bool sched_core_enabled(struct rq *rq) + { + return static_branch_unlikely(&__sched_core_enabled) && rq->core_enabled; + } + +-static inline bool sched_core_disabled(void) ++static __always_inline bool sched_core_disabled(void) + { + return !static_branch_unlikely(&__sched_core_enabled); + } +@@ -1474,7 +1474,7 @@ static inline bool sched_core_disabled(void) + * Be careful with this function; not for general use. The return value isn't + * stable unless you actually hold a relevant rq->__lock. + */ +-static inline raw_spinlock_t *rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *rq_lockp(struct rq *rq) + { + if (sched_core_enabled(rq)) + return &rq->core->__lock; +@@ -1482,7 +1482,7 @@ static inline raw_spinlock_t *rq_lockp(struct rq *rq) + return &rq->__lock; + } + +-static inline raw_spinlock_t *__rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *__rq_lockp(struct rq *rq) + __returns_ctx_lock(rq_lockp(rq)) /* alias them */ + { + if (rq->core_enabled) +@@ -1585,12 +1585,12 @@ static inline bool sched_core_disabled(void) + return true; + } + +-static inline raw_spinlock_t *rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *rq_lockp(struct rq *rq) + { + return &rq->__lock; + } + +-static inline raw_spinlock_t *__rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *__rq_lockp(struct rq *rq) + __returns_ctx_lock(rq_lockp(rq)) /* alias them */ + { + return &rq->__lock; +@@ -1650,33 +1650,33 @@ extern void raw_spin_rq_lock_nested(struct rq *rq, int subclass) + extern bool raw_spin_rq_trylock(struct rq *rq) + __cond_acquires(true, __rq_lockp(rq)); + +-static inline void raw_spin_rq_lock(struct rq *rq) ++static __always_inline void raw_spin_rq_lock(struct rq *rq) + __acquires(__rq_lockp(rq)) + { + raw_spin_rq_lock_nested(rq, 0); + } + +-static inline void raw_spin_rq_unlock(struct rq *rq) ++static __always_inline void raw_spin_rq_unlock(struct rq *rq) + __releases(__rq_lockp(rq)) + { + raw_spin_unlock(rq_lockp(rq)); + } + +-static inline void raw_spin_rq_lock_irq(struct rq *rq) ++static __always_inline void raw_spin_rq_lock_irq(struct rq *rq) + __acquires(__rq_lockp(rq)) + { + local_irq_disable(); + raw_spin_rq_lock(rq); + } + +-static inline void raw_spin_rq_unlock_irq(struct rq *rq) ++static __always_inline void raw_spin_rq_unlock_irq(struct rq *rq) + __releases(__rq_lockp(rq)) + { + raw_spin_rq_unlock(rq); + local_irq_enable(); + } + +-static inline unsigned long _raw_spin_rq_lock_irqsave(struct rq *rq) ++static __always_inline unsigned long _raw_spin_rq_lock_irqsave(struct rq *rq) + __acquires(__rq_lockp(rq)) + { + unsigned long flags; +@@ -1687,7 +1687,7 @@ static inline unsigned long _raw_spin_rq_lock_irqsave(struct rq *rq) + return flags; + } + +-static inline void raw_spin_rq_unlock_irqrestore(struct rq *rq, unsigned long flags) ++static __always_inline void raw_spin_rq_unlock_irqrestore(struct rq *rq, unsigned long flags) + __releases(__rq_lockp(rq)) + { + raw_spin_rq_unlock(rq); diff --git a/pkgbuilds/linux-omarchy-bore/0141-sched-urgent-fixes.patch b/pkgbuilds/linux-omarchy-bore/0141-sched-urgent-fixes.patch new file mode 100644 index 0000000..e59e1f1 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0141-sched-urgent-fixes.patch @@ -0,0 +1,125 @@ +diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c +--- a/kernel/sched/deadline.c ++++ b/kernel/sched/deadline.c +@@ -3026,8 +3026,8 @@ static struct task_struct *pick_next_pushable_dl_task(struct rq *rq) + next_node = rb_first_cached(&rq->dl.pushable_dl_tasks_root); + while (next_node) { + i = __node_2_pdl(next_node); +- /* make sure task isn't on_cpu (possible with proxy-exec) */ +- if (!task_on_cpu(rq, i)) { ++ /* skip tasks that cannot be migrated */ ++ if (!task_on_cpu(rq, i) && !is_migration_disabled(i)) { + p = i; + break; + } +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -10691,17 +10691,40 @@ static enum llc_mig can_migrate_llc(int src_cpu, int dst_cpu, + return mig_llc; + } + ++static inline bool task_misfits_asym_cpu(struct lb_env *env, struct task_struct *p) ++{ ++ /* ++ * On asymmetric CPU capacity domains, do not let cache-aware ++ * balancing pull the task onto a destination CPU that cannot ++ * accommodate it. Doing so would turn the task into a misfit on ++ * the destination, trading a cache-locality gain for a capacity ++ * loss. If the task already does not fit its source CPU, the move ++ * cannot make things worse, so let the LLC preference decide. ++ */ ++ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && p && ++ !task_fits_cpu(p, env->dst_cpu) && ++ task_fits_cpu(p, env->src_cpu)) ++ return true; ++ ++ return false; ++} ++ + /* + * Check if task p can migrate from source LLC to + * destination LLC in terms of cache aware load balance. + */ +-static enum llc_mig can_migrate_llc_task(int src_cpu, int dst_cpu, ++static enum llc_mig can_migrate_llc_task(struct lb_env *env, + struct task_struct *p) + { + struct mm_struct *mm; + bool to_pref; +- int cpu; ++ int cpu, src_cpu, dst_cpu; + ++ if (task_misfits_asym_cpu(env, p)) ++ return mig_forbid; ++ ++ src_cpu = env->src_cpu; ++ dst_cpu = env->dst_cpu; + mm = p->mm; + if (!mm) + return mig_unrestricted; +@@ -10758,6 +10781,14 @@ alb_break_llc(struct lb_env *env) + unsigned long util = 0; + struct task_struct *cur; + ++ /* ++ * Migrating misfit tasks from current CPU ++ * to CPU with a better fit. ++ * Prioritize that over LLC preference. ++ */ ++ if (env->migration_type == migrate_misfit) ++ return false; ++ + if (env->src_rq->nr_running <= 1) + return true; + +@@ -10765,7 +10796,8 @@ alb_break_llc(struct lb_env *env) + if (cur && cur->sched_class == &fair_sched_class) + util = task_util(cur); + +- if (can_migrate_llc(env->src_cpu, env->dst_cpu, ++ if (task_misfits_asym_cpu(env, cur) || ++ can_migrate_llc(env->src_cpu, env->dst_cpu, + util, false) == mig_forbid) + return true; + } +@@ -10805,8 +10837,7 @@ static bool migrate_degrades_llc(struct task_struct *p, struct lb_env *env) + READ_ONCE(p->preferred_llc) != llc_id(env->dst_cpu)) + return true; + +- if (can_migrate_llc_task(env->src_cpu, +- env->dst_cpu, p) != mig_forbid) ++ if (can_migrate_llc_task(env, p) != mig_forbid) + return false; + + return true; +@@ -11869,6 +11900,15 @@ static inline bool llc_balance(struct lb_env *env, struct sg_lb_stats *sgs, + if (env->sd->flags & SD_SHARE_LLC) + return false; + ++ /* ++ * On asymmetric domains, group_misfit_task_load ++ * should be prioritized to move tasks to CPU that fit them ++ * over aggregating tasks to their preferred LLC. ++ */ ++ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && ++ sgs->group_misfit_task_load) ++ return false; ++ + /* + * Skip cache aware tagging if nr_balanced_failed is sufficiently high. + * Threshold of cache_nice_tries is set to 1 higher than nr_balance_failed +diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c +--- a/kernel/sched/rt.c ++++ b/kernel/sched/rt.c +@@ -1871,8 +1871,8 @@ static struct task_struct *pick_next_pushable_task(struct rq *rq) + return NULL; + + plist_for_each_entry(i, head, pushable_tasks) { +- /* make sure task isn't on_cpu (possible with proxy-exec) */ +- if (!task_on_cpu(rq, i)) { ++ /* skip tasks that cannot be migrated */ ++ if (!task_on_cpu(rq, i) && !is_migration_disabled(i)) { + p = i; + break; + } diff --git a/pkgbuilds/linux-omarchy-bore/0142-sched-itmt-no-debugfs-dependency.patch b/pkgbuilds/linux-omarchy-bore/0142-sched-itmt-no-debugfs-dependency.patch new file mode 100644 index 0000000..a0f39ea --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0142-sched-itmt-no-debugfs-dependency.patch @@ -0,0 +1,24 @@ +diff --git a/arch/x86/kernel/itmt.c b/arch/x86/kernel/itmt.c +--- a/arch/x86/kernel/itmt.c ++++ b/arch/x86/kernel/itmt.c +@@ -110,18 +110,14 @@ int sched_set_itmt_support(void) + arch_debugfs_dir, + &sysctl_sched_itmt_enabled, + &dfs_sched_itmt_fops); +- if (IS_ERR_OR_NULL(dfs_sched_itmt)) { ++ if (IS_ERR(dfs_sched_itmt)) + dfs_sched_itmt = NULL; +- return -ENOMEM; +- } + + dfs_sched_core_prio = debugfs_create_file("sched_core_priority", 0644, + arch_debugfs_dir, NULL, + &sched_core_priority_fops); +- if (IS_ERR_OR_NULL(dfs_sched_core_prio)) { ++ if (IS_ERR(dfs_sched_core_prio)) + dfs_sched_core_prio = NULL; +- return -ENOMEM; +- } + + sched_itmt_capable = true; + diff --git a/pkgbuilds/linux-omarchy-bore/0143-sched-hybrid-cluster-balancing.patch b/pkgbuilds/linux-omarchy-bore/0143-sched-hybrid-cluster-balancing.patch new file mode 100644 index 0000000..be80f96 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0143-sched-hybrid-cluster-balancing.patch @@ -0,0 +1,124 @@ +diff --git a/include/linux/sched/sd_flags.h b/include/linux/sched/sd_flags.h +--- a/include/linux/sched/sd_flags.h ++++ b/include/linux/sched/sd_flags.h +@@ -146,8 +146,7 @@ SD_FLAG(SD_ASYM_PACKING, SDF_NEEDS_GROUPS) + /* + * Prefer to place tasks in a sibling domain + * +- * Set up until domains start spanning NUMA nodes. Close to being a SHARED_CHILD +- * flag, but cleared below domains with SD_ASYM_CPUCAPACITY. ++ * Set up until domains start spanning NUMA nodes. + * + * NEEDS_GROUPS: Load balancing flag. + */ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -12028,10 +12028,25 @@ static inline void update_sg_lb_stats(struct lb_env *env, + continue; + + if (sd_flags & SD_ASYM_CPUCAPACITY) { +- /* Check for a misfit task on the cpu */ +- if (sgs->group_misfit_task_load < rq->misfit_task_load) { +- sgs->group_misfit_task_load = rq->misfit_task_load; +- *sg_overloaded = 1; ++ if (rq->misfit_task_load) { ++ /* ++ * Always mark the root domain overloaded so big ++ * CPUs can pick up misfit tasks via newly idle ++ * balance. ++ */ ++ if (balancing_at_rd) ++ *sg_overloaded = 1; ++ ++ /* ++ * Only account misfit load if @dst_cpu can ++ * help; otherwise, the group may be classified ++ * as misfit_task and update_sd_pick_busiest() ++ * will skip it. ++ */ ++ if (capacity_greater(capacity_of(env->dst_cpu), ++ group->sgc->max_capacity) && ++ (sgs->group_misfit_task_load < rq->misfit_task_load)) ++ sgs->group_misfit_task_load = rq->misfit_task_load; + } + } else if (env->idle && sched_reduced_capacity(rq, env->sd)) { + /* Check for a task running on a CPU with reduced capacity */ +@@ -12110,6 +12125,17 @@ static bool update_sd_pick_busiest(struct lb_env *env, + sds->local_stat.group_type != group_has_spare)) + return false; + ++ /* ++ * Candidate sg has no more than one task per CPU and has higher ++ * per-CPU capacity. Migrating tasks to less capable CPUs may harm ++ * throughput. Maximize throughput, power/energy consequences are not ++ * considered. ++ */ ++ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && ++ (sgs->group_type <= group_fully_busy) && ++ (capacity_greater(sg->sgc->min_capacity, capacity_of(env->dst_cpu)))) ++ return false; ++ + if (sgs->group_type > busiest->group_type) + return true; + +@@ -12216,17 +12242,6 @@ static bool update_sd_pick_busiest(struct lb_env *env, + break; + } + +- /* +- * Candidate sg has no more than one task per CPU and has higher +- * per-CPU capacity. Migrating tasks to less capable CPUs may harm +- * throughput. Maximize throughput, power/energy consequences are not +- * considered. +- */ +- if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && +- (sgs->group_type <= group_fully_busy) && +- (capacity_greater(sg->sgc->min_capacity, capacity_of(env->dst_cpu)))) +- return false; +- + return true; + } + +@@ -13144,9 +13159,24 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env, + * average load. + */ + if (env->sd->flags & SD_ASYM_CPUCAPACITY && +- !capacity_greater(capacity_of(env->dst_cpu), capacity) && +- nr_running == 1) +- continue; ++ nr_running == 1) { ++ bool cluster_equal_cap = static_branch_unlikely(&sched_cluster_active) && ++ (get_actual_cpu_capacity(env->dst_cpu) == ++ get_actual_cpu_capacity(i)); ++ bool smt_degraded_cap = sched_smt_active() && !is_core_idle(i); ++ ++ /* ++ * Busy SMT siblings reduce the capacity of CPU @i. Do ++ * not skip it in this case. ++ * ++ * CONFIG_SCHED_CLUSTER requires balancing load across ++ * clusters of identical capacity, accounting for ++ * hardware and cpufreq pressure. ++ */ ++ if (!smt_degraded_cap && !cluster_equal_cap && ++ !capacity_greater(capacity_of(env->dst_cpu), capacity)) ++ continue; ++ } + + /* + * Make sure we only pull tasks from a CPU of lower priority +diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c +--- a/kernel/sched/topology.c ++++ b/kernel/sched/topology.c +@@ -1995,10 +1995,6 @@ sd_init(struct sched_domain_topology_level *tl, + /* + * Convert topological properties into behaviour. + */ +- /* Don't attempt to spread across CPUs of different capacities. */ +- if ((sd->flags & SD_ASYM_CPUCAPACITY) && sd->child) +- sd->child->flags &= ~SD_PREFER_SIBLING; +- + if (sd->flags & SD_SHARE_CPUCAPACITY) { + sd->imbalance_pct = 110; + diff --git a/pkgbuilds/linux-omarchy-bore/0144-sched-nohz-idle-core.patch b/pkgbuilds/linux-omarchy-bore/0144-sched-nohz-idle-core.patch new file mode 100644 index 0000000..f3af36b --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0144-sched-nohz-idle-core.patch @@ -0,0 +1,77 @@ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -14050,29 +14050,62 @@ static inline int on_null_domain(struct rq *rq) + */ + static inline int find_new_ilb(void) + { +- int this_cpu = smp_processor_id(); +- const struct cpumask *hk_mask; +- int ilb_cpu; ++ struct cpumask *ilb_cpus; ++ int ilb_cpu, fallback = -1; + +- hk_mask = housekeeping_cpumask(HK_TYPE_KERNEL_NOISE); ++ lockdep_assert_irqs_disabled(); + +- for_each_cpu_and(ilb_cpu, nohz.idle_cpus_mask, hk_mask) { +- if (ilb_cpu == this_cpu) ++ /* ++ * Reuse the per-CPU select_rq_mask, which is protected from concurrent ++ * use on this CPU by having interrupts disabled. ++ */ ++ ilb_cpus = this_cpu_cpumask_var_ptr(select_rq_mask); ++ cpumask_and(ilb_cpus, nohz.idle_cpus_mask, ++ housekeeping_cpumask(HK_TYPE_KERNEL_NOISE)); ++ ++ for_each_cpu(ilb_cpu, ilb_cpus) { ++ if (!idle_cpu(ilb_cpu)) { ++ /* ++ * Once an idle fallback exists, a busy CPU proves that ++ * this core cannot be fully idle. Skip its siblings. ++ */ ++ if (sched_smt_active() && fallback >= 0) ++ cpumask_andnot(ilb_cpus, ilb_cpus, cpu_smt_mask(ilb_cpu)); + continue; ++ } + +- if (idle_cpu(ilb_cpu)) +- return ilb_cpu; ++ /* ++ * Running the idle load balancer on an idle sibling of a busy ++ * SMT core can reduce the capacity available to its sibling. Prefer ++ * a CPU whose entire core is idle, but retain the first idle CPU as ++ * a fallback so idle balancing can still make progress when no fully ++ * idle core exists. ++ */ ++ if (sched_smt_active() && !is_core_idle(ilb_cpu)) { ++ if (fallback < 0) ++ fallback = ilb_cpu; ++ ++ /* ++ * The core is not idle, so there is no need to check ++ * any of its other SMT siblings. ++ */ ++ cpumask_andnot(ilb_cpus, ilb_cpus, ++ cpu_smt_mask(ilb_cpu)); ++ continue; ++ } ++ ++ return ilb_cpu; + } + +- return -1; ++ return fallback; + } + + /* + * Kick a CPU to do the NOHZ balancing, if it is time for it, via a cross-CPU + * SMP function call (IPI). + * +- * We pick the first idle CPU in the HK_TYPE_KERNEL_NOISE housekeeping set +- * (if there is one). ++ * Prefer a CPU on a fully idle core in the HK_TYPE_KERNEL_NOISE housekeeping ++ * set. Fall back to the first idle CPU when no fully idle core exists. + */ + static void kick_ilb(unsigned int flags) + { diff --git a/pkgbuilds/linux-omarchy-bore/0150-adios-3.2.0.patch b/pkgbuilds/linux-omarchy-bore/0150-adios-3.2.0.patch new file mode 100644 index 0000000..7d0ef21 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0150-adios-3.2.0.patch @@ -0,0 +1,2154 @@ +diff --git a/block/Kconfig.iosched b/block/Kconfig.iosched +index 27f11320b8d1..e98585dd83e0 100644 +--- a/block/Kconfig.iosched ++++ b/block/Kconfig.iosched +@@ -16,6 +16,20 @@ config MQ_IOSCHED_KYBER + synchronous writes, it will self-tune queue depths to achieve that + goal. + ++config MQ_IOSCHED_ADIOS ++ tristate "Adaptive Deadline I/O scheduler" ++ default m ++ help ++ The Adaptive Deadline I/O Scheduler (ADIOS) is a multi-queue I/O ++ scheduler with learning-based adaptive latency control. ++ ++config MQ_IOSCHED_DEFAULT_ADIOS ++ bool "Enable ADIOS I/O scheduler as default MQ I/O scheduler" ++ depends on MQ_IOSCHED_ADIOS=y ++ default n ++ help ++ Enable the ADIOS I/O scheduler as the default scheduler for MQ I/O. ++ + config IOSCHED_BFQ + tristate "BFQ I/O scheduler" + select BLK_ICQ +diff --git a/block/Makefile b/block/Makefile +index e7bd320e3d69..5523e5f62f92 100644 +--- a/block/Makefile ++++ b/block/Makefile +@@ -25,6 +25,7 @@ obj-$(CONFIG_BLK_CGROUP_IOLATENCY) += blk-iolatency.o + obj-$(CONFIG_BLK_CGROUP_IOCOST) += blk-iocost.o + obj-$(CONFIG_MQ_IOSCHED_DEADLINE) += mq-deadline.o + obj-$(CONFIG_MQ_IOSCHED_KYBER) += kyber-iosched.o ++obj-$(CONFIG_MQ_IOSCHED_ADIOS) += adios.o + bfq-y := bfq-iosched.o bfq-wf2q.o bfq-cgroup.o + obj-$(CONFIG_IOSCHED_BFQ) += bfq.o + +@@ -39,3 +40,10 @@ obj-$(CONFIG_BLK_INLINE_ENCRYPTION) += blk-crypto.o blk-crypto-profile.o \ + blk-crypto-sysfs.o + obj-$(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK) += blk-crypto-fallback.o + obj-$(CONFIG_BLOCK_HOLDER_DEPRECATED) += holder.o ++ ++all: ++ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules ++ ++clean: ++ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean ++ +diff --git a/block/adios.c b/block/adios.c +new file mode 100644 +index 000000000000..c7f380bb65dd +--- /dev/null ++++ b/block/adios.c +@@ -0,0 +1,2062 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Adaptive Deadline I/O Scheduler (ADIOS) ++ * Copyright (C) 2025 Masahito Suzuki ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "elevator.h" ++#include "blk.h" ++#include "blk-mq.h" ++#include "blk-mq-sched.h" ++ ++#define ADIOS_VERSION "3.2.0" ++ ++/* Request Types: ++ * ++ * Tier 0 (Highest Priority): Emergency & System Integrity Requests ++ * ----------------------------------------------------------------- ++ * - Target: Requests with the BLK_MQ_INSERT_AT_HEAD flag. ++ * - Purpose: For critical, non-negotiable operations such as device error ++ * recovery or flush sequences that must bypass all other scheduling logic. ++ * - Implementation: Placed in a dedicated, high-priority FIFO queue ++ * (`prio_queue[0]`) for immediate dispatch. ++ * ++ * Tier 1 (High Priority): I/O Barrier Guarantees ++ * --------------------------------------------------------------- ++ * - Target: Requests with the REQ_OP_FLUSH flag. ++ * - Purpose: To enforce a strict I/O barrier. When a flush request is ++ * received, the scheduler stops processing new requests from its main ++ * queues until all preceding requests have been completed. This guarantees ++ * the order of operations required by filesystems for data integrity. ++ * - Implementation: A state flag (ADIOS_STATE_BARRIER) halts ++ * insertion into the main deadline tree. The barrier request and all ++ * subsequent requests are held in a temporary `barrier_queue`. Once the ++ * main queues are drained, the barrier request and the subsequent requests ++ * are released from the pending queue back into the scheduler. ++ * ++ * Tier 2 (Medium Priority): Application Responsiveness ++ * ---------------------------------------------------- ++ * - Target: Normal synchronous requests (e.g., from standard file reads). ++ * - Purpose: To ensure correct application behavior for operations that ++ * depend on sequential I/O completion (e.g., file system mounts) and to ++ * provide low latency for interactive applications. ++ * - Implementation: The deadline for these requests is set to their start ++ * time (`rq->start_time_ns`). This effectively enforces FIFO-like behavior ++ * within the deadline-sorted red-black tree, preventing out-of-order ++ * execution of dependent synchronous operations. ++ * ++ * Tier 3 (Normal Priority): Background Throughput ++ * ----------------------------------------------- ++ * - Target: Asynchronous requests. ++ * - Purpose: To maximize disk throughput for background tasks where latency ++ * is not critical. ++ * - Implementation: These are the only requests where ADIOS's adaptive ++ * latency prediction model is used. A dynamic deadline is calculated based ++ * on the predicted I/O latency, allowing for aggressive reordering to ++ * optimize I/O efficiency. ++ * ++ * Dispatch Logic: ++ * The scheduler always dispatches requests in strict priority order: ++ * 1. prio_queue[0] (Tier 0) ++ * 2. The deadline-sorted batch queue (which naturally prioritizes Tier 2 ++ * over Tier 3 due to their calculated deadlines). ++ * 3. Barrier-pending requests are handled only after the main queues are empty. ++ */ ++ ++// Global variable to control the latency ++static u64 default_global_latency_window = 16000000ULL; ++static u64 default_global_latency_window_rotational = 22000000ULL; ++// Ratio below which batch queues should be refilled ++static u8 default_bq_refill_below_ratio = 20; ++// Maximum latency sample to input ++static u64 default_lat_model_latency_limit = 500 * NSEC_PER_MSEC; ++// Batch ordering strategy ++static u64 default_batch_order = 0; ++ ++/* Compliance Flags: ++ * 0x1: Async requests will not be reordered based on the predicted latency ++ */ ++enum adios_compliance_flags { ++ ADIOS_CF_FIXORDER = 1U << 0, ++}; ++ ++// Flags to control compliance with block layer constraints ++static u64 default_compliance_flags = 0x0; ++ ++// Dynamic thresholds for shrinkage ++static u32 default_lm_shrink_at_kreqs = 5000; ++static u32 default_lm_shrink_at_gbytes = 50; ++static u32 default_lm_shrink_resist = 2; ++ ++enum adios_optype { ++ ADIOS_READ = 0, ++ ADIOS_WRITE = 1, ++ ADIOS_DISCARD = 2, ++ ADIOS_OTHER = 3, ++ ADIOS_OPTYPES = 4, ++}; ++ ++// Latency targets for each operation type ++static u64 default_latency_target[ADIOS_OPTYPES] = { ++ [ADIOS_READ] = 2ULL * NSEC_PER_MSEC, ++ [ADIOS_WRITE] = 2000ULL * NSEC_PER_MSEC, ++ [ADIOS_DISCARD] = 8000ULL * NSEC_PER_MSEC, ++ [ADIOS_OTHER] = 0ULL * NSEC_PER_MSEC, ++}; ++ ++// Maximum batch size limits for each operation type ++static u32 default_batch_limit[ADIOS_OPTYPES] = { ++ [ADIOS_READ] = 36, ++ [ADIOS_WRITE] = 72, ++ [ADIOS_DISCARD] = 1, ++ [ADIOS_OTHER] = 1, ++}; ++ ++enum adios_batch_order { ++ ADIOS_BO_OPTYPE = 0, ++ ADIOS_BO_ELEVATOR = 1, ++}; ++ ++// Thresholds for latency model control ++#define LM_BLOCK_SIZE_THRESHOLD 4096 ++#define LM_SAMPLES_THRESHOLD 1024 ++#define LM_INTERVAL_THRESHOLD 1500 ++#define LM_OUTLIER_PERCENTILE 99 ++#define LM_LAT_BUCKET_COUNT 64 ++ ++#define ADIOS_PQ_LEVELS 2 ++#define ADIOS_DL_TYPES 2 ++#define ADIOS_BQ_PAGES 2 ++ ++static u32 default_dl_prio[ADIOS_DL_TYPES] = {8, 0}; ++ ++// Bit flags for the atomic state variable, indicating which queues have requests. ++enum adios_state_flags { ++ ADIOS_STATE_PQ_0 = 1U << 0, ++ ADIOS_STATE_PQ_1 = 1U << 1, ++ ADIOS_STATE_DL_0 = 1U << 2, ++ ADIOS_STATE_DL_1 = 1U << 3, ++ ADIOS_STATE_BQ_PAGE_0 = 1U << 4, ++ ADIOS_STATE_BQ_PAGE_1 = 1U << 5, ++ ADIOS_STATE_BARRIER = 1U << 6, ++}; ++#define ADIOS_STATE_PQ 0 ++#define ADIOS_STATE_DL 2 ++#define ADIOS_STATE_BQ 4 ++#define ADIOS_STATE_BP 6 ++ ++// Temporal granularity of the deadline tree node (dl_group) ++#define ADIOS_QUANTUM_SHIFT 20 ++ ++#define ADIOS_MAX_INSERTS_PER_LOCK 72 ++#define ADIOS_MAX_DELETES_PER_LOCK 24 ++ ++// Structure to hold latency bucket data for small requests ++struct latency_bucket_small { ++ u64 weighted_sum_latency; ++ u64 sum_of_weights; ++}; ++ ++// Structure to hold latency bucket data for large requests ++struct latency_bucket_large { ++ u64 weighted_sum_latency; ++ u64 weighted_sum_block_size; ++ u64 sum_of_weights; ++}; ++ ++// Structure to hold per-cpu buckets, improving data locality and code clarity. ++struct lm_buckets { ++ struct latency_bucket_small small_bucket[LM_LAT_BUCKET_COUNT]; ++ struct latency_bucket_large large_bucket[LM_LAT_BUCKET_COUNT]; ++}; ++ ++// Structure to hold RCU-protected latency model parameters ++struct latency_model_params { ++ u64 base; ++ u64 slope; ++ u64 small_sum_delay; ++ u64 small_count; ++ u64 large_sum_delay; ++ u64 large_sum_bsize; ++ u64 last_update_jiffies; ++ struct rcu_head rcu; ++}; ++ ++// Structure to hold the latency model context data ++struct latency_model { ++ spinlock_t update_lock; ++ struct latency_model_params __rcu *params; ++ ++ // Per-CPU buckets to avoid lock contention on the completion path ++ struct lm_buckets __percpu *pcpu_buckets; ++ // Per-CPU snapshots for delta-based aggregation (accessed under update_lock) ++ struct lm_buckets __percpu *pcpu_snapshot; ++ ++ u32 lm_shrink_at_kreqs; ++ u32 lm_shrink_at_gbytes; ++ u8 lm_shrink_resist; ++}; ++ ++struct adios_pcpu_completion { ++ u64 last_completed_time; ++ sector_t last_completed_pos; ++}; ++ ++union adios_in_flight_rqs { ++ atomic64_t atomic; ++ u64 scalar; ++ struct { ++ u64 count: 16; ++ u64 total_pred_lat: 48; ++ }; ++}; ++ ++// Adios scheduler data ++struct adios_data { ++ spinlock_t pq_lock; ++ struct list_head prio_queue[2]; ++ ++ struct rb_root_cached dl_tree[2]; ++ spinlock_t lock; ++ s64 dl_bias; ++ s32 dl_prio[2]; ++ ++ atomic_t state; ++ u8 bq_state[ADIOS_BQ_PAGES]; ++ ++ bool models_stable; ++ ++ u64 global_latency_window; ++ u64 compliance_flags; ++ u64 latency_target[ADIOS_OPTYPES]; ++ u32 batch_limit[ADIOS_OPTYPES]; ++ u32 batch_actual_max_size[ADIOS_OPTYPES]; ++ u32 batch_actual_max_total; ++ u32 async_depth; ++ u32 lat_model_latency_limit; ++ u8 bq_refill_below_ratio; ++ u8 is_rotational; ++ u8 batch_order; ++ u8 elv_direction; ++ sector_t head_pos; ++ ++ bool bq_page; ++ struct list_head batch_queue[ADIOS_BQ_PAGES][ADIOS_OPTYPES]; ++ u32 batch_count[ADIOS_BQ_PAGES][ADIOS_OPTYPES]; ++ u8 bq_batch_order[ADIOS_BQ_PAGES]; ++ spinlock_t bq_lock; ++ spinlock_t barrier_lock; ++ struct list_head barrier_queue; ++ ++ struct lm_buckets *aggr_buckets; ++ ++ struct latency_model latency_model[ADIOS_OPTYPES]; ++ struct timer_list update_timer; ++ ++ union adios_in_flight_rqs in_flight_rqs; ++ atomic64_t total_pred_lat; ++ ++ struct adios_pcpu_completion __percpu *pcpu_completion; ++ ++ struct kmem_cache *rq_data_cache; ++ mempool_t *rq_data_pool; ++ struct kmem_cache *dl_group_pool; ++ ++ struct request_queue *queue; ++}; ++ ++// List of requests with the same deadline in the deadline-sorted tree ++struct dl_group { ++ struct rb_node node; ++ struct list_head rqs; ++ u64 deadline; ++} __attribute__((aligned(64))); ++ ++// Structure to hold scheduler-specific data for each request ++struct adios_rq_data { ++ struct list_head *dl_group; ++ struct list_head dl_node; ++ ++ struct request *rq; ++ u64 deadline; ++ u64 pred_lat; ++ u32 block_size; ++ bool managed; ++} __attribute__((aligned(64))); ++ ++static const int adios_prio_to_wmult[40] = { ++ /* -20 */ 88761, 71755, 56483, 46273, 36291, ++ /* -15 */ 29154, 23254, 18705, 14949, 11916, ++ /* -10 */ 9548, 7620, 6100, 4904, 3906, ++ /* -5 */ 3121, 2501, 1991, 1586, 1277, ++ /* 0 */ 1024, 820, 655, 526, 423, ++ /* 5 */ 335, 272, 215, 172, 137, ++ /* 10 */ 110, 87, 70, 56, 45, ++ /* 15 */ 36, 29, 23, 18, 15, ++}; ++ ++static inline bool compliant(struct adios_data *ad, u32 flag) { ++ return ad->compliance_flags & flag; ++} ++ ++// Count the number of entries in aggregated small buckets ++static u64 lm_count_small_entries(struct latency_bucket_small *buckets) { ++ u64 total_weight = 0; ++ for (u8 i = 0; i < LM_LAT_BUCKET_COUNT; i++) ++ total_weight += buckets[i].sum_of_weights; ++ return total_weight; ++} ++ ++// Update the small buckets in the latency model from aggregated data ++static bool lm_update_small_buckets(struct latency_model *model, ++ struct latency_model_params *params, ++ struct latency_bucket_small *buckets, ++ u64 total_weight, bool count_all) { ++ u64 sum_latency = 0; ++ u64 sum_weight = 0; ++ u64 cumulative_weight = 0, threshold_weight = 0; ++ u8 outlier_threshold_bucket = 0; ++ u8 outlier_percentile = LM_OUTLIER_PERCENTILE; ++ u8 reduction; ++ ++ if (count_all) ++ outlier_percentile = 100; ++ ++ // Calculate the threshold weight for outlier detection ++ threshold_weight = (total_weight * outlier_percentile) / 100; ++ ++ // Identify the bucket that corresponds to the outlier threshold ++ for (u8 i = 0; i < LM_LAT_BUCKET_COUNT; i++) { ++ cumulative_weight += buckets[i].sum_of_weights; ++ if (cumulative_weight >= threshold_weight) { ++ outlier_threshold_bucket = i; ++ break; ++ } ++ } ++ ++ // Calculate the average latency, excluding outliers ++ for (u8 i = 0; i <= outlier_threshold_bucket; i++) { ++ struct latency_bucket_small *bucket = &buckets[i]; ++ if (i < outlier_threshold_bucket) { ++ sum_latency += bucket->weighted_sum_latency; ++ sum_weight += bucket->sum_of_weights; ++ } else { ++ // The threshold bucket's contribution is proportional ++ u64 remaining_weight = ++ threshold_weight - (cumulative_weight - bucket->sum_of_weights); ++ if (bucket->sum_of_weights > 0) { ++ sum_latency += div_u64(bucket->weighted_sum_latency * ++ remaining_weight, bucket->sum_of_weights); ++ sum_weight += remaining_weight; ++ } ++ } ++ } ++ ++ // Shrink the model if it reaches at the readjustment threshold ++ if (params->small_count >= 1000ULL * model->lm_shrink_at_kreqs) { ++ reduction = model->lm_shrink_resist; ++ if (params->small_count >> reduction) { ++ params->small_sum_delay -= params->small_sum_delay >> reduction; ++ params->small_count -= params->small_count >> reduction; ++ } ++ } ++ ++ if (!sum_weight) ++ return false; ++ ++ // Accumulate the average latency into the statistics ++ params->small_sum_delay += sum_latency; ++ params->small_count += sum_weight; ++ ++ return true; ++} ++ ++// Count the number of entries in aggregated large buckets ++static u64 lm_count_large_entries(struct latency_bucket_large *buckets) { ++ u64 total_weight = 0; ++ for (u8 i = 0; i < LM_LAT_BUCKET_COUNT; i++) ++ total_weight += buckets[i].sum_of_weights; ++ return total_weight; ++} ++ ++// Update the large buckets in the latency model from aggregated data ++static bool lm_update_large_buckets(struct latency_model *model, ++ struct latency_model_params *params, ++ struct latency_bucket_large *buckets, ++ u64 total_weight, bool count_all) { ++ s64 sum_latency = 0; ++ u64 sum_block_size = 0, intercept; ++ u64 cumulative_weight = 0, threshold_weight = 0; ++ u64 sum_weight = 0; ++ u8 outlier_threshold_bucket = 0; ++ u8 outlier_percentile = LM_OUTLIER_PERCENTILE; ++ u8 reduction; ++ ++ if (count_all) ++ outlier_percentile = 100; ++ ++ // Calculate the threshold weight for outlier detection ++ threshold_weight = (total_weight * outlier_percentile) / 100; ++ ++ // Identify the bucket that corresponds to the outlier threshold ++ for (u8 i = 0; i < LM_LAT_BUCKET_COUNT; i++) { ++ cumulative_weight += buckets[i].sum_of_weights; ++ if (cumulative_weight >= threshold_weight) { ++ outlier_threshold_bucket = i; ++ break; ++ } ++ } ++ ++ // Calculate the average latency and block size, excluding outliers ++ for (u8 i = 0; i <= outlier_threshold_bucket; i++) { ++ struct latency_bucket_large *bucket = &buckets[i]; ++ if (i < outlier_threshold_bucket) { ++ sum_latency += bucket->weighted_sum_latency; ++ sum_block_size += bucket->weighted_sum_block_size; ++ sum_weight += bucket->sum_of_weights; ++ } else { ++ // The threshold bucket's contribution is proportional ++ u64 remaining_weight = ++ threshold_weight - (cumulative_weight - bucket->sum_of_weights); ++ if (bucket->sum_of_weights > 0) { ++ sum_latency += div_u64(bucket->weighted_sum_latency * ++ remaining_weight, bucket->sum_of_weights); ++ sum_block_size += div_u64(bucket->weighted_sum_block_size * ++ remaining_weight, bucket->sum_of_weights); ++ sum_weight += remaining_weight; ++ } ++ } ++ } ++ ++ if (!sum_weight) ++ return false; ++ ++ // Shrink the model if it reaches at the readjustment threshold ++ if (params->large_sum_bsize >= 0x40000000ULL * model->lm_shrink_at_gbytes) { ++ reduction = model->lm_shrink_resist; ++ if (params->large_sum_bsize >> reduction) { ++ params->large_sum_delay -= params->large_sum_delay >> reduction; ++ params->large_sum_bsize -= params->large_sum_bsize >> reduction; ++ } ++ } ++ ++ // Accumulate the average delay into the statistics ++ intercept = params->base; ++ if (sum_latency > intercept) ++ sum_latency -= intercept; ++ ++ params->large_sum_delay += sum_latency; ++ params->large_sum_bsize += sum_block_size; ++ ++ return true; ++} ++ ++static void reset_buckets(struct lm_buckets *buckets) ++{ memset(buckets, 0, sizeof(*buckets)); } ++ ++static void lm_reset_pcpu_buckets(struct latency_model *model) { ++ int cpu; ++ for_each_possible_cpu(cpu) { ++ reset_buckets(per_cpu_ptr(model->pcpu_buckets, cpu)); ++ reset_buckets(per_cpu_ptr(model->pcpu_snapshot, cpu)); ++ } ++} ++ ++// Update the latency model parameters and statistics ++static void latency_model_update( ++ struct adios_data *ad, struct latency_model *model) { ++ u64 now; ++ u64 small_weight, large_weight; ++ bool time_elapsed; ++ bool small_processed = false, large_processed = false; ++ struct lm_buckets *aggr = ad->aggr_buckets; ++ struct latency_bucket_small *asb; ++ struct latency_bucket_large *alb; ++ struct lm_buckets *pcpu_b, *snap; ++ unsigned long flags; ++ int cpu; ++ struct latency_model_params *old_params, *new_params; ++ ++ spin_lock_irqsave(&model->update_lock, flags); ++ ++ old_params = rcu_dereference_protected(model->params, ++ lockdep_is_held(&model->update_lock)); ++ new_params = kmemdup(old_params, sizeof(*new_params), GFP_ATOMIC); ++ if (!new_params) { ++ spin_unlock_irqrestore(&model->update_lock, flags); ++ return; ++ } ++ ++ // Aggregate deltas from all CPUs using snapshot-delta method. ++ // Per-CPU counters increase monotonically; we compute delta = current - snapshot. ++ for_each_possible_cpu(cpu) { ++ pcpu_b = per_cpu_ptr(model->pcpu_buckets, cpu); ++ snap = per_cpu_ptr(model->pcpu_snapshot, cpu); ++ ++ for (u8 i = 0; i < LM_LAT_BUCKET_COUNT; i++) { ++ u64 sw = pcpu_b->small_bucket[i].sum_of_weights; ++ u64 sl = pcpu_b->small_bucket[i].weighted_sum_latency; ++ u64 dsw = sw - snap->small_bucket[i].sum_of_weights; ++ u64 dsl = sl - snap->small_bucket[i].weighted_sum_latency; ++ snap->small_bucket[i].sum_of_weights = sw; ++ snap->small_bucket[i].weighted_sum_latency = sl; ++ if (dsw) { ++ asb = &aggr->small_bucket[i]; ++ asb->sum_of_weights += dsw; ++ asb->weighted_sum_latency += dsl; ++ } ++ ++ u64 lw = pcpu_b->large_bucket[i].sum_of_weights; ++ u64 ll = pcpu_b->large_bucket[i].weighted_sum_latency; ++ u64 lb = pcpu_b->large_bucket[i].weighted_sum_block_size; ++ u64 dlw = lw - snap->large_bucket[i].sum_of_weights; ++ u64 dll = ll - snap->large_bucket[i].weighted_sum_latency; ++ u64 dlb = lb - snap->large_bucket[i].weighted_sum_block_size; ++ snap->large_bucket[i].sum_of_weights = lw; ++ snap->large_bucket[i].weighted_sum_latency = ll; ++ snap->large_bucket[i].weighted_sum_block_size = lb; ++ if (dlw) { ++ alb = &aggr->large_bucket[i]; ++ alb->sum_of_weights += dlw; ++ alb->weighted_sum_latency += dll; ++ alb->weighted_sum_block_size += dlb; ++ } ++ } ++ } ++ ++ // Count the number of entries in aggregated buckets ++ small_weight = lm_count_small_entries(aggr->small_bucket); ++ large_weight = lm_count_large_entries(aggr->large_bucket); ++ ++ // Whether enough time has elapsed since the last update ++ now = jiffies; ++ time_elapsed = unlikely(!new_params->base) || ++ new_params->last_update_jiffies + ++ msecs_to_jiffies(LM_INTERVAL_THRESHOLD) <= now; ++ ++ // Update small buckets ++ if (small_weight && (time_elapsed || ++ LM_SAMPLES_THRESHOLD <= small_weight || !new_params->base)) { ++ small_processed = lm_update_small_buckets(model, new_params, ++ aggr->small_bucket, small_weight, !new_params->base); ++ memset(&aggr->small_bucket[0], 0, sizeof(aggr->small_bucket)); ++ } ++ // Update large buckets ++ if (large_weight && (time_elapsed || ++ LM_SAMPLES_THRESHOLD <= large_weight || !new_params->slope)) { ++ large_processed = lm_update_large_buckets(model, new_params, ++ aggr->large_bucket, large_weight, !new_params->slope); ++ memset(&aggr->large_bucket[0], 0, sizeof(aggr->large_bucket)); ++ } ++ ++ // Update the base parameter if small bucket was processed ++ if (small_processed && likely(new_params->small_count)) ++ new_params->base = div_u64(new_params->small_sum_delay, ++ new_params->small_count); ++ ++ // Update the slope parameter if large bucket was processed ++ if (large_processed && likely(new_params->large_sum_bsize)) ++ new_params->slope = div_u64(new_params->large_sum_delay, ++ DIV_ROUND_UP_ULL(new_params->large_sum_bsize, 1024)); ++ ++ // Update last updated jiffies if update happened or time has elapsed ++ if (small_processed || large_processed || time_elapsed) ++ new_params->last_update_jiffies = now; ++ ++ rcu_assign_pointer(model->params, new_params); ++ spin_unlock_irqrestore(&model->update_lock, flags); ++ ++ kfree_rcu(old_params, rcu); ++} ++ ++// Determine the bucket index for a given measured and predicted latency ++static u8 lm_input_bucket_index(u64 measured, u64 predicted) { ++ u32 bucket_index; ++ ++ if (measured < predicted * 2) ++ bucket_index = div_u64((measured * 20), predicted); ++ else if (measured < predicted * 5) ++ bucket_index = div_u64((measured * 10), predicted) + 20; ++ else ++ bucket_index = div_u64((measured * 3), predicted) + 40; ++ ++ if (bucket_index >= LM_LAT_BUCKET_COUNT) ++ bucket_index = LM_LAT_BUCKET_COUNT - 1; ++ ++ return (u8)bucket_index; ++} ++ ++// Input latency data into the latency model ++static void latency_model_input(struct adios_data *ad, ++ struct latency_model *model, ++ u32 block_size, u64 latency, u64 pred_lat, u32 weight) { ++ unsigned long flags; ++ u8 bucket_index; ++ struct lm_buckets *buckets; ++ u64 current_base; ++ struct latency_model_params *params; ++ ++ local_irq_save(flags); ++ buckets = per_cpu_ptr(model->pcpu_buckets, __smp_processor_id()); ++ ++ rcu_read_lock(); ++ params = rcu_dereference(model->params); ++ current_base = params->base; ++ rcu_read_unlock(); ++ ++ if (block_size <= LM_BLOCK_SIZE_THRESHOLD) { ++ // Handle small requests ++ bucket_index = lm_input_bucket_index(latency, current_base ?: 1); ++ ++ buckets->small_bucket[bucket_index].sum_of_weights += weight; ++ buckets->small_bucket[bucket_index].weighted_sum_latency += ++ latency * weight; ++ ++ local_irq_restore(flags); ++ ++ if (unlikely(!current_base)) { ++ latency_model_update(ad, model); ++ return; ++ } ++ } else { ++ // Handle large requests ++ if (!current_base || !pred_lat) { ++ local_irq_restore(flags); ++ return; ++ } ++ ++ bucket_index = lm_input_bucket_index(latency, pred_lat); ++ ++ buckets->large_bucket[bucket_index].sum_of_weights += weight; ++ buckets->large_bucket[bucket_index].weighted_sum_latency += ++ latency * weight; ++ buckets->large_bucket[bucket_index].weighted_sum_block_size += ++ block_size * weight; ++ ++ local_irq_restore(flags); ++ } ++} ++ ++// Predict the latency for a given block size using the latency model ++static u64 latency_model_predict(struct latency_model *model, u32 block_size) { ++ u64 result; ++ struct latency_model_params *params; ++ ++ rcu_read_lock(); ++ params = rcu_dereference(model->params); ++ ++ result = params->base; ++ if (block_size > LM_BLOCK_SIZE_THRESHOLD) ++ result += params->slope * ++ DIV_ROUND_UP_ULL(block_size - LM_BLOCK_SIZE_THRESHOLD, 1024); ++ ++ rcu_read_unlock(); ++ ++ return result; ++} ++ ++// Determine the type of operation based on request flags ++static u8 adios_optype(struct request *rq) { ++ switch (rq->cmd_flags & REQ_OP_MASK) { ++ case REQ_OP_READ: ++ return ADIOS_READ; ++ case REQ_OP_WRITE: ++ return ADIOS_WRITE; ++ case REQ_OP_DISCARD: ++ return ADIOS_DISCARD; ++ default: ++ return ADIOS_OTHER; ++ } ++} ++ ++static inline u8 adios_optype_not_read(struct request *rq) { ++ return (rq->cmd_flags & REQ_OP_MASK) != REQ_OP_READ; ++} ++ ++// Helper function to retrieve adios_rq_data from a request ++static inline struct adios_rq_data *get_rq_data(struct request *rq) { ++ return rq->elv.priv[0]; ++} ++ ++static inline ++void set_adios_state(struct adios_data *ad, u32 shift, u32 idx, bool flag) { ++ if (flag) ++ atomic_or(1U << (idx + shift), &ad->state); ++ else ++ atomic_andnot(1U << (idx + shift), &ad->state); ++} ++ ++static inline u32 get_adios_state(struct adios_data *ad) ++{ return atomic_read(&ad->state); } ++ ++static inline u32 eval_this_adios_state(u32 state, u32 shift) ++{ return (state >> shift) & 0x3; } ++ ++static inline u32 eval_adios_state(struct adios_data *ad, u32 shift) ++{ return eval_this_adios_state(get_adios_state(ad), shift); } ++ ++// Add a request to the deadline-sorted red-black tree ++static void add_to_dl_tree( ++ struct adios_data *ad, bool dl_idx, struct request *rq) { ++ struct rb_root_cached *root = &ad->dl_tree[dl_idx]; ++ struct rb_node **link = &(root->rb_root.rb_node), *parent = NULL; ++ bool leftmost = true; ++ struct adios_rq_data *rd = get_rq_data(rq); ++ struct dl_group *dlg; ++ u64 deadline; ++ bool was_empty = RB_EMPTY_ROOT(&root->rb_root); ++ ++ /* Tier-2: Synchronous Requests ++ * - Needs to be FIFO within a same optype ++ * - Relaxed order between different optypes ++ * - basically needs to be processed in early time */ ++ rd->deadline = rq->start_time_ns; ++ ++ /* Tier-3: Aynchronous Requests ++ * - Can be reordered and delayed freely */ ++ if (!(rq->cmd_flags & REQ_SYNC)) { ++ rd->deadline += ad->latency_target[adios_optype(rq)]; ++ if (!compliant(ad, ADIOS_CF_FIXORDER)) ++ rd->deadline += rd->pred_lat; ++ } ++ ++ // Now quantize the deadline (-> dlg->deadline == RB-Tree key) ++ deadline = rd->deadline & ~((1ULL << ADIOS_QUANTUM_SHIFT) - 1); ++ ++ while (*link) { ++ dlg = rb_entry(*link, struct dl_group, node); ++ s64 diff = deadline - dlg->deadline; ++ ++ parent = *link; ++ if (diff < 0) { ++ link = &((*link)->rb_left); ++ } else if (diff > 0) { ++ link = &((*link)->rb_right); ++ leftmost = false; ++ } else { // diff == 0 ++ goto found; ++ } ++ } ++ ++ dlg = rb_entry_safe(parent, struct dl_group, node); ++ if (!dlg || dlg->deadline != deadline) { ++ dlg = kmem_cache_zalloc(ad->dl_group_pool, GFP_ATOMIC); ++ if (!dlg) ++ return; ++ dlg->deadline = deadline; ++ INIT_LIST_HEAD(&dlg->rqs); ++ rb_link_node(&dlg->node, parent, link); ++ rb_insert_color_cached(&dlg->node, root, leftmost); ++ } ++found: ++ list_add_tail(&rd->dl_node, &dlg->rqs); ++ rd->dl_group = &dlg->rqs; ++ ++ if (was_empty) ++ set_adios_state(ad, ADIOS_STATE_DL, dl_idx, true); ++} ++ ++// Remove a request from the deadline-sorted red-black tree ++static void del_from_dl_tree( ++ struct adios_data *ad, bool dl_idx, struct request *rq) { ++ struct rb_root_cached *root = &ad->dl_tree[dl_idx]; ++ struct adios_rq_data *rd = get_rq_data(rq); ++ struct dl_group *dlg = container_of(rd->dl_group, struct dl_group, rqs); ++ ++ list_del_init(&rd->dl_node); ++ if (list_empty(&dlg->rqs)) { ++ rb_erase_cached(&dlg->node, root); ++ kmem_cache_free(ad->dl_group_pool, dlg); ++ } ++ rd->dl_group = NULL; ++ ++ if (RB_EMPTY_ROOT(&ad->dl_tree[dl_idx].rb_root)) ++ set_adios_state(ad, ADIOS_STATE_DL, dl_idx, false); ++} ++ ++// Remove a request from the scheduler ++static void remove_request(struct adios_data *ad, struct request *rq) { ++ bool dl_idx = adios_optype_not_read(rq); ++ struct request_queue *q = rq->q; ++ struct adios_rq_data *rd = get_rq_data(rq); ++ ++ list_del_init(&rq->queuelist); ++ ++ // We might not be on the rbtree, if we are doing an insert merge ++ if (rd->dl_group) ++ del_from_dl_tree(ad, dl_idx, rq); ++ ++ elv_rqhash_del(q, rq); ++ if (q->last_merge == rq) ++ q->last_merge = NULL; ++} ++ ++// Convert a queue depth to the corresponding word depth for shallow allocation ++static int to_word_depth(struct blk_mq_hw_ctx *hctx, unsigned int qdepth) { ++ struct sbitmap_queue *bt = &hctx->sched_tags->bitmap_tags; ++ const unsigned int nrr = hctx->queue->nr_requests; ++ ++ return ((qdepth << bt->sb.shift) + nrr - 1) / nrr; ++} ++ ++// We limit the depth of request allocation for asynchronous and write requests ++static void adios_limit_depth(blk_opf_t opf, struct blk_mq_alloc_data *data) { ++ struct adios_data *ad = data->q->elevator->elevator_data; ++ ++ // Do not throttle synchronous reads ++ if (op_is_sync(opf) && !op_is_write(opf)) ++ return; ++ ++ data->shallow_depth = to_word_depth(data->hctx, ad->async_depth); ++} ++ ++// The number of requests in the queue was notified from the block layer ++static void adios_depth_updated(struct request_queue *q) { ++ struct adios_data *ad = q->elevator->elevator_data; ++ ++ ad->async_depth = q->nr_requests; ++ blk_mq_set_min_shallow_depth(q, 1); ++} ++ ++// Handle request merging after a merge operation ++static void adios_request_merged(struct request_queue *q, struct request *req, ++ enum elv_merge type) { ++ bool dl_idx = adios_optype_not_read(req); ++ struct adios_data *ad = q->elevator->elevator_data; ++ ++ // Reposition request in the deadline-sorted tree ++ del_from_dl_tree(ad, dl_idx, req); ++ add_to_dl_tree(ad, dl_idx, req); ++} ++ ++// Handle merging of requests after one has been merged into another ++static void adios_merged_requests(struct request_queue *q, struct request *req, ++ struct request *next) { ++ struct adios_data *ad = q->elevator->elevator_data; ++ ++ lockdep_assert_held(&ad->lock); ++ ++ // kill knowledge of next, this one is a goner ++ remove_request(ad, next); ++} ++ ++// Try to merge a bio into an existing rq before associating it with an rq ++static bool adios_bio_merge(struct request_queue *q, struct bio *bio, ++ unsigned int nr_segs) { ++ unsigned long flags; ++ struct adios_data *ad = q->elevator->elevator_data; ++ struct request *free = NULL; ++ bool ret; ++ ++ if (eval_adios_state(ad, ADIOS_STATE_BP)) ++ return false; ++ ++ if (!spin_trylock_irqsave(&ad->lock, flags)) ++ return false; ++ ++ ret = blk_mq_sched_try_merge(q, bio, nr_segs, &free); ++ spin_unlock_irqrestore(&ad->lock, flags); ++ ++ if (free) ++ blk_mq_free_request(free); ++ ++ return ret; ++} ++ ++static bool merge_or_insert_to_dl_tree(struct adios_data *ad, ++ struct request *rq, struct request_queue *q, struct list_head *free) { ++ if (blk_mq_sched_try_insert_merge(q, rq, free)) ++ return true; ++ ++ bool dl_idx = adios_optype_not_read(rq); ++ add_to_dl_tree(ad, dl_idx, rq); ++ ++ if (rq_mergeable(rq)) { ++ elv_rqhash_add(q, rq); ++ if (!q->last_merge) ++ q->last_merge = rq; ++ } ++ ++ return false; ++} ++ ++static void insert_to_prio_queue(struct adios_data *ad, ++ struct request *rq, bool pq_idx) { ++ struct adios_rq_data *rd = get_rq_data(rq); ++ ++ /* We're sure that rd->managed == true */ ++ union adios_in_flight_rqs ifr = { ++ .count = 1, ++ .total_pred_lat = rd->pred_lat, ++ }; ++ atomic64_add(ifr.scalar, &ad->in_flight_rqs.atomic); ++ ++ scoped_guard(spinlock_irqsave, &ad->pq_lock) { ++ bool was_empty = list_empty(&ad->prio_queue[pq_idx]); ++ list_add_tail(&rq->queuelist, &ad->prio_queue[pq_idx]); ++ if (was_empty) ++ set_adios_state(ad, ADIOS_STATE_PQ, pq_idx, true); ++ } ++} ++ ++// Insert a request into the scheduler (after Read & Write models stabilized) ++static void insert_request_post_stability(struct blk_mq_hw_ctx *hctx, ++ struct request *rq, blk_insert_t insert_flags, struct list_head *free) { ++ struct request_queue *q = hctx->queue; ++ struct adios_data *ad = q->elevator->elevator_data; ++ struct adios_rq_data *rd = get_rq_data(rq); ++ u8 optype = adios_optype(rq); ++ bool rq_is_flush; ++ ++ rd->managed = true; ++ rd->block_size = blk_rq_bytes(rq); ++ rd->pred_lat = ++ latency_model_predict(&ad->latency_model[optype], rd->block_size); ++ if (unlikely(rd->pred_lat > ad->lat_model_latency_limit)) ++ rd->pred_lat = ad->lat_model_latency_limit; ++ ++ /* Tier-0: BLK_MQ_INSERT_AT_HEAD Requests */ ++ if (insert_flags & BLK_MQ_INSERT_AT_HEAD) { ++ insert_to_prio_queue(ad, rq, 0); ++ return; ++ } ++ ++ /* ++ * Strict Barrier Handling for REQ_OP_FLUSH: ++ * If a flush request arrives, or if the scheduler is already in a ++ * barrier-pending state, all subsequent requests are diverted to a ++ * separate barrier_queue. This ensures that no new requests are processed ++ * until all work preceding the barrier is complete. ++ */ ++ rq_is_flush = (rq->cmd_flags & REQ_OP_MASK) == REQ_OP_FLUSH; ++ if (eval_adios_state(ad, ADIOS_STATE_BP) || rq_is_flush) { ++ scoped_guard(spinlock_irqsave, &ad->barrier_lock) { ++ if (rq_is_flush) ++ set_adios_state(ad, ADIOS_STATE_BP, 0, true); ++ list_add_tail(&rq->queuelist, &ad->barrier_queue); ++ } ++ return; ++ } ++ ++ if (merge_or_insert_to_dl_tree(ad, rq, q, free)) ++ return; ++} ++ ++// Insert a request into the scheduler (before Read & Write models stabilizes) ++static void insert_request_pre_stability(struct blk_mq_hw_ctx *hctx, ++ struct request *rq, blk_insert_t insert_flags, struct list_head *free) { ++ struct adios_data *ad = hctx->queue->elevator->elevator_data; ++ struct adios_rq_data *rd = get_rq_data(rq); ++ u8 optype = adios_optype(rq); ++ u8 pq_idx = !(insert_flags & BLK_MQ_INSERT_AT_HEAD); ++ bool stable = false; ++ ++ rd->managed = true; ++ rd->block_size = blk_rq_bytes(rq); ++ rd->pred_lat = ++ latency_model_predict(&ad->latency_model[optype], rd->block_size); ++ if (unlikely(rd->pred_lat > ad->lat_model_latency_limit)) ++ rd->pred_lat = ad->lat_model_latency_limit; ++ ++ insert_to_prio_queue(ad, rq, pq_idx); ++ ++ rcu_read_lock(); ++ if (rcu_dereference(ad->latency_model[ADIOS_READ].params)->base > 0 && ++ rcu_dereference(ad->latency_model[ADIOS_WRITE].params)->base > 0) ++ stable = true; ++ rcu_read_unlock(); ++ ++ if (stable) ++ ad->models_stable = true; ++} ++ ++// Insert multiple requests into the scheduler ++static void adios_insert_requests(struct blk_mq_hw_ctx *hctx, ++ struct list_head *list, ++ blk_insert_t insert_flags) { ++ struct request_queue *q = hctx->queue; ++ struct adios_data *ad = q->elevator->elevator_data; ++ struct request *rq; ++ bool stop = false; ++ LIST_HEAD(free); ++ ++ do { ++ scoped_guard(spinlock_irqsave, &ad->lock) ++ for (int i = 0; i < ADIOS_MAX_INSERTS_PER_LOCK; i++) { ++ if (list_empty(list)) { ++ stop = true; ++ break; ++ } ++ rq = list_first_entry(list, struct request, queuelist); ++ list_del_init(&rq->queuelist); ++ if (likely(ad->models_stable)) ++ insert_request_post_stability(hctx, rq, insert_flags, &free); ++ else ++ insert_request_pre_stability(hctx, rq, insert_flags, &free); ++ }} while (!stop); ++ ++ blk_mq_free_requests(&free); ++} ++ ++// Prepare a request before it is inserted into the scheduler ++static void adios_prepare_request(struct request *rq) { ++ struct adios_data *ad = rq->q->elevator->elevator_data; ++ struct adios_rq_data *rd; ++ ++ rd = mempool_alloc(ad->rq_data_pool, GFP_ATOMIC); ++ memset(rd, 0, sizeof(*rd)); ++ rd->rq = rq; ++ rq->elv.priv[0] = rd; ++} ++ ++static struct adios_rq_data *get_dl_first_rd(struct adios_data *ad, bool idx) { ++ struct rb_root_cached *root = &ad->dl_tree[idx]; ++ struct rb_node *first = rb_first_cached(root); ++ struct dl_group *dl_group = rb_entry(first, struct dl_group, node); ++ ++ return list_first_entry(&dl_group->rqs, struct adios_rq_data, dl_node); ++} ++ ++// Comparison function for sorting requests by block address ++static int cmp_rq_pos(void *priv, ++ const struct list_head *a, const struct list_head *b) { ++ struct request *rq_a = list_entry(a, struct request, queuelist); ++ struct request *rq_b = list_entry(b, struct request, queuelist); ++ u64 pos_a = blk_rq_pos(rq_a); ++ u64 pos_b = blk_rq_pos(rq_b); ++ ++ return (int)(pos_a > pos_b) - (int)(pos_a < pos_b); ++} ++ ++#ifndef list_last_entry_or_null ++#define list_last_entry_or_null(ptr, type, member) \ ++ (!list_empty(ptr) ? list_last_entry(ptr, type, member) : NULL) ++#endif ++ ++// Update the elevator direction ++static void update_elv_direction(struct adios_data *ad) { ++ if (!ad->is_rotational) ++ return; ++ ++ bool page = ad->bq_page; ++ struct list_head *q = &ad->batch_queue[page][1]; ++ if (ad->bq_batch_order[page] < ADIOS_BO_ELEVATOR || list_empty(q)) { ++ ad->elv_direction = 0; ++ return; ++ } ++ ++ // Get first and last request positions in the queue ++ struct request *rq_a = list_first_entry(q, struct request, queuelist); ++ struct request *rq_b = list_last_entry (q, struct request, queuelist); ++ u64 pos_a = blk_rq_pos(rq_a); ++ u64 pos_b = blk_rq_pos(rq_b); ++ u64 avg_rq_pos = (pos_a + pos_b) >> 1; ++ ++ ad->elv_direction = !!(ad->head_pos > avg_rq_pos); ++} ++ ++// Fill the batch queues with requests from the deadline-sorted red-black tree ++static bool fill_batch_queues(struct adios_data *ad, u64 tpl) { ++ struct adios_rq_data *rd; ++ struct request *rq; ++ struct list_head *dest_q; ++ u8 dest_idx; ++ u64 added_lat = 0; ++ u32 optype_count[ADIOS_OPTYPES] = {0}; ++ u32 count = 0; ++ u8 optype; ++ bool page = !ad->bq_page, dl_idx, bias_idx, update_bias; ++ u32 dl_queued; ++ u8 bq_batch_order; ++ bool stop = false; ++ ++ // Reset batch queue counts for the back page ++ memset(&ad->batch_count[page], 0, sizeof(ad->batch_count[page])); ++ ++ ad->bq_batch_order[page] = ++ bq_batch_order = ad->batch_order; ++ ++ do { ++ scoped_guard(spinlock_irqsave, &ad->lock) ++ for (int i = 0; i < ADIOS_MAX_DELETES_PER_LOCK; i++) { ++ bool has_base = false; ++ ++ dl_queued = eval_adios_state(ad, ADIOS_STATE_DL); ++ // Check if there are any requests queued in the deadline tree ++ if (!dl_queued) { ++ stop = true; ++ break; ++ } ++ ++ // Reads if both queues have requests, otherwise pick the non-empty. ++ dl_idx = dl_queued >> 1; ++ ++ // Get the first request from the deadline-sorted tree ++ rd = get_dl_first_rd(ad, dl_idx); ++ ++ bias_idx = ad->dl_bias < 0; ++ // If read and write requests are queued, choose one based on bias ++ if (dl_queued == 0x3) { ++ struct adios_rq_data *trd[2] = {get_dl_first_rd(ad, 0), rd}; ++ rd = trd[bias_idx]; ++ ++ update_bias = (trd[bias_idx]->deadline > trd[!bias_idx]->deadline); ++ } else ++ update_bias = (bias_idx == dl_idx); ++ ++ rq = rd->rq; ++ optype = adios_optype(rq); ++ ++ rcu_read_lock(); ++ has_base = ++ !!rcu_dereference(ad->latency_model[optype].params)->base; ++ rcu_read_unlock(); ++ ++ // Check batch size and total predicted latency ++ if (count && (!has_base || ++ ad->batch_count[page][optype] >= ad->batch_limit[optype] || ++ (tpl + added_lat + rd->pred_lat) > ad->global_latency_window)) { ++ stop = true; ++ break; ++ } ++ ++ if (update_bias) { ++ s64 sign = ((s64)bias_idx << 1) - 1; ++ if (unlikely(!rd->pred_lat)) ++ ad->dl_bias = sign; ++ else ++ // Adjust the bias based on the predicted latency ++ ad->dl_bias += sign * (s64)((rd->pred_lat * ++ adios_prio_to_wmult[ad->dl_prio[bias_idx] + 20]) >> 10); ++ } ++ ++ remove_request(ad, rq); ++ ++ // Add request to the corresponding batch queue ++ dest_idx = (bq_batch_order == ADIOS_BO_OPTYPE || optype == ADIOS_OTHER)? ++ optype : !!(rd->deadline != rq->start_time_ns); ++ dest_q = &ad->batch_queue[page][dest_idx]; ++ list_add_tail(&rq->queuelist, dest_q); ++ ad->bq_state[page] |= 1U << dest_idx; ++ ad->batch_count[page][optype]++; ++ optype_count[optype]++; ++ added_lat += rd->pred_lat; ++ count++; ++ }} while (!stop); ++ ++ if (bq_batch_order == ADIOS_BO_ELEVATOR && ad->batch_count[page][1] > 1) ++ list_sort(NULL, &ad->batch_queue[page][1], cmp_rq_pos); ++ ++ if (count) { ++ /* We're sure that every request's rd->managed == true */ ++ union adios_in_flight_rqs ifr = { ++ .count = count, ++ .total_pred_lat = added_lat, ++ }; ++ atomic64_add(ifr.scalar, &ad->in_flight_rqs.atomic); ++ ++ set_adios_state(ad, ADIOS_STATE_BQ, page, true); ++ ++ for (optype = 0; optype < ADIOS_OPTYPES; optype++) ++ if (ad->batch_actual_max_size[optype] < optype_count[optype]) ++ ad->batch_actual_max_size[optype] = optype_count[optype]; ++ if (ad->batch_actual_max_total < count) ++ ad->batch_actual_max_total = count; ++ } ++ return count; ++} ++ ++// Flip to the next batch queue page ++static void flip_bq_page(struct adios_data *ad) { ++ ad->bq_page = !ad->bq_page; ++ update_elv_direction(ad); ++} ++ ++// Pop a request from the specified index (optype or elevator tier) ++static inline struct request *pop_bq_request( ++ struct adios_data *ad, u8 idx, bool direction) { ++ bool page = ad->bq_page; ++ struct list_head *q = &ad->batch_queue[page][idx]; ++ struct request *rq = direction ? ++ list_last_entry_or_null (q, struct request, queuelist): ++ list_first_entry_or_null(q, struct request, queuelist); ++ if (rq) { ++ list_del_init(&rq->queuelist); ++ if (list_empty(q)) ++ ad->bq_state[page] &= ~(1U << idx); ++ } ++ return rq; ++} ++ ++static struct request *pop_next_bq_request_optype(struct adios_data *ad) { ++ u32 bq_state = ad->bq_state[ad->bq_page]; ++ if (!bq_state) return NULL; ++ ++ struct request *rq; ++ u32 bq_idx = __builtin_ctz(bq_state); ++ ++ // Dispatch based on optype (FIFO within each) or single-queue elevator ++ rq = pop_bq_request(ad, bq_idx, false); ++ return rq; ++} ++ ++static struct request *pop_next_bq_request_elevator(struct adios_data *ad) { ++ u32 bq_state = ad->bq_state[ad->bq_page]; ++ if (!bq_state) return NULL; ++ ++ struct request *rq; ++ u32 bq_idx = __builtin_ctz(bq_state); ++ bool direction = (bq_idx == 1) & ad->elv_direction; ++ ++ // Tier-2 (sync) is always high priority ++ // Tier-3 (async) uses the pre-calculated elevator direction ++ rq = pop_bq_request(ad, bq_idx, direction); ++ ++ /* If batch queue for the sync requests just became empty */ ++ if (bq_idx == 0 && rq && !(bq_state & 0x1)) ++ update_elv_direction(ad); ++ ++ return rq; ++} ++ ++// Returns the state of the batch queue page ++static inline bool bq_page_has_rq(u32 bq_state, bool page) { ++ return bq_state & (1U << page); ++} ++ ++// Dispatch a request from the batch queues ++static struct request *dispatch_from_bq(struct adios_data *ad) { ++ struct request *rq; ++ ++ guard(spinlock_irqsave)(&ad->bq_lock); ++ ++ u32 state = get_adios_state(ad); ++ u32 bq_state = eval_this_adios_state(state, ADIOS_STATE_BQ); ++ u32 bq_curr_page_has_rq = bq_page_has_rq(bq_state, ad->bq_page); ++ union adios_in_flight_rqs ifr; ++ ifr.scalar = atomic64_read(&ad->in_flight_rqs.atomic); ++ u64 tpl = ifr.total_pred_lat; ++ ++ // Refill the batch queues if the back page is empty, dl_tree has work, and ++ // current page is empty or the total ongoing latency is below the threshold ++ if (!bq_page_has_rq(bq_state, !ad->bq_page) && ++ (!bq_curr_page_has_rq || (!tpl || tpl < div_u64( ++ ad->global_latency_window * ad->bq_refill_below_ratio, 100))) && ++ eval_this_adios_state(state, ADIOS_STATE_DL)) ++ fill_batch_queues(ad, tpl); ++ ++ // If current batch queue page is empty, and the other page has work, flip ++ if (!bq_curr_page_has_rq && ++ bq_page_has_rq(eval_adios_state(ad, ADIOS_STATE_BQ), !ad->bq_page)) ++ flip_bq_page(ad); ++ ++ // Use the per-page state to decide the dispatch logic, ensuring correctness ++ rq = (ad->bq_batch_order[ad->bq_page] == ADIOS_BO_ELEVATOR) ? ++ pop_next_bq_request_elevator(ad): ++ pop_next_bq_request_optype(ad); ++ ++ if (rq) { ++ bool page = ad->bq_page; ++ bool is_empty = !ad->bq_state[page]; ++ if (is_empty) ++ set_adios_state(ad, ADIOS_STATE_BQ, page, false); ++ return rq; ++ } ++ ++ return NULL; ++} ++ ++// Dispatch a request from the priority queue ++static struct request *dispatch_from_pq(struct adios_data *ad) { ++ struct request *rq = NULL; ++ ++ guard(spinlock_irqsave)(&ad->pq_lock); ++ u32 pq_state = eval_adios_state(ad, ADIOS_STATE_PQ); ++ u8 pq_idx = pq_state >> 1; ++ struct list_head *q = &ad->prio_queue[pq_idx]; ++ ++ if (unlikely(list_empty(q))) return NULL; ++ ++ rq = list_first_entry(q, struct request, queuelist); ++ list_del_init(&rq->queuelist); ++ if (list_empty(q)) { ++ set_adios_state(ad, ADIOS_STATE_PQ, pq_idx, false); ++ update_elv_direction(ad); ++ } ++ return rq; ++} ++ ++static bool release_barrier_requests(struct adios_data *ad) { ++ u32 moved_count = 0; ++ LIST_HEAD(local_list); ++ ++ scoped_guard(spinlock_irqsave, &ad->barrier_lock) { ++ if (!list_empty(&ad->barrier_queue)) { ++ struct request *trq, *next; ++ bool first_barrier_moved = false; ++ ++ list_for_each_entry_safe(trq, next, &ad->barrier_queue, queuelist) { ++ if (!first_barrier_moved) { ++ list_del_init(&trq->queuelist); ++ insert_to_prio_queue(ad, trq, 1); ++ moved_count++; ++ first_barrier_moved = true; ++ continue; ++ } ++ ++ if ((trq->cmd_flags & REQ_OP_MASK) == REQ_OP_FLUSH) ++ break; ++ ++ list_move_tail(&trq->queuelist, &local_list); ++ moved_count++; ++ } ++ ++ if (list_empty(&ad->barrier_queue)) ++ set_adios_state(ad, ADIOS_STATE_BP, 0, false); ++ } ++ } ++ ++ if (!moved_count) ++ return false; ++ ++ if (!list_empty(&local_list)) { ++ struct request *trq, *next; ++ LIST_HEAD(free_list); ++ ++ /* ad->lock is already held */ ++ list_for_each_entry_safe(trq, next, &local_list, queuelist) { ++ list_del_init(&trq->queuelist); ++ if (merge_or_insert_to_dl_tree(ad, trq, ad->queue, &free_list)) ++ continue; ++ } ++ ++ if (!list_empty(&free_list)) ++ blk_mq_free_requests(&free_list); ++ } ++ ++ return true; ++} ++ ++// Dispatch a request to the hardware queue ++static struct request *adios_dispatch_request(struct blk_mq_hw_ctx *hctx) { ++ struct adios_data *ad = hctx->queue->elevator->elevator_data; ++ struct request *rq; ++ ++retry: ++ rq = dispatch_from_pq(ad); ++ if (rq) ++ goto found; ++ ++ rq = dispatch_from_bq(ad); ++ if (rq) ++ goto found; ++ ++ /* ++ * If all active queues are empty, check if we need to process a barrier. ++ * This is the trigger to release requests that were held in barrier_queue ++ * due to a REQ_OP_FLUSH barrier. ++ */ ++ if (eval_adios_state(ad, ADIOS_STATE_BP)) { ++ bool barrier_released = false; ++ scoped_guard(spinlock_irqsave, &ad->lock) ++ barrier_released = release_barrier_requests(ad); ++ if (barrier_released) ++ goto retry; ++ } ++ ++ return NULL; ++found: ++ if (ad->is_rotational) ++ ad->head_pos = blk_rq_pos(rq) + blk_rq_sectors(rq); ++ ++ rq->rq_flags |= RQF_STARTED; ++ return rq; ++} ++ ++// Timer callback function to periodically update latency models ++static void update_timer_callback(struct timer_list *t) { ++ struct adios_data *ad = timer_container_of(ad, t, update_timer); ++ ++ for (u8 optype = 0; optype < ADIOS_OPTYPES; optype++) ++ latency_model_update(ad, &ad->latency_model[optype]); ++} ++ ++// Handle the completion of a request ++static void adios_completed_request(struct request *rq, u64 now) { ++ struct adios_data *ad = rq->q->elevator->elevator_data; ++ struct adios_rq_data *rd = get_rq_data(rq); ++ union adios_in_flight_rqs ifr = { .scalar = 0 }; ++ ++ if (rd->managed) { ++ union adios_in_flight_rqs ifr_to_sub = { ++ .count = 1, ++ .total_pred_lat = rd->pred_lat, ++ }; ++ ifr.scalar = atomic64_sub_return( ++ ifr_to_sub.scalar, &ad->in_flight_rqs.atomic); ++ } ++ u8 optype = adios_optype(rq); ++ ++ unsigned long flags; ++ struct adios_pcpu_completion *pc; ++ ++ local_irq_save(flags); ++ pc = this_cpu_ptr(ad->pcpu_completion); ++ ++ if (optype == ADIOS_OTHER) { ++ // Non-positional commands make the head position unpredictable. ++ // Invalidate our knowledge of the last completed position. ++ if (ad->is_rotational) ++ pc->last_completed_pos = 0; ++ local_irq_restore(flags); ++ return; ++ } ++ ++ u64 lct = pc->last_completed_time ?: rq->io_start_time_ns; ++ pc->last_completed_time = (ifr.count) ? now : 0; ++ ++ if (!rq->io_start_time_ns || !rd->block_size || unlikely(now < lct)) { ++ local_irq_restore(flags); ++ return; ++ } ++ ++ u64 latency = now - lct; ++ ++ u32 weight = 1; ++ if (ad->is_rotational) { ++ sector_t current_pos = blk_rq_pos(rq); ++ // Only calculate seek distance if we have a valid last position. ++ if (pc->last_completed_pos > 0) { ++ u64 seek_distance = abs( ++ (s64)current_pos - (s64)pc->last_completed_pos); ++ if (seek_distance) ++ weight = 65 - __builtin_clzll(seek_distance); ++ } ++ // Update (or re-synchronize) our knowledge of the head position. ++ pc->last_completed_pos = current_pos + blk_rq_sectors(rq); ++ } ++ ++ local_irq_restore(flags); ++ ++ if (latency > ad->lat_model_latency_limit) ++ return; ++ ++ latency_model_input(ad, &ad->latency_model[optype], ++ rd->block_size, latency, rd->pred_lat, weight); ++ timer_reduce(&ad->update_timer, jiffies + msecs_to_jiffies(100)); ++} ++ ++// Clean up after a request is finished ++static void adios_finish_request(struct request *rq) { ++ struct adios_data *ad = rq->q->elevator->elevator_data; ++ ++ if (rq->elv.priv[0]) { ++ // Free adios_rq_data back to the memory pool ++ mempool_free(get_rq_data(rq), ad->rq_data_pool); ++ rq->elv.priv[0] = NULL; ++ } ++} ++ ++// Check if there are any requests available for dispatch ++static bool adios_has_work(struct blk_mq_hw_ctx *hctx) { ++ struct adios_data *ad = hctx->queue->elevator->elevator_data; ++ ++ return atomic_read(&ad->state) != 0; ++} ++ ++// Initialize the scheduler-specific data when initializing the request queue ++static int adios_init_sched(struct request_queue *q, struct elevator_queue *eq) { ++ struct adios_data *ad; ++ int ret = -ENOMEM; ++ u8 optype = 0; ++ ++ ad = kzalloc_node(sizeof(*ad), GFP_KERNEL, q->node); ++ if (!ad) { ++ pr_err("adios: Failed to create adios_data\n"); ++ goto put_eq; ++ } ++ ++ eq->elevator_data = ad; ++ ++ // Create a memory pool for adios_rq_data ++ ad->rq_data_cache = kmem_cache_create("adios_rq_data", ++ sizeof(struct adios_rq_data), ++ 0, SLAB_HWCACHE_ALIGN, NULL); ++ if (!ad->rq_data_cache) { ++ pr_err("adios: Failed to create rq_data_cache\n"); ++ goto free_ad; ++ } ++ ++ ad->rq_data_pool = mempool_create_slab_pool( ++ q->nr_requests, ad->rq_data_cache); ++ if (!ad->rq_data_pool) { ++ pr_err("adios: Failed to create rq_data_pool\n"); ++ goto destroy_rq_data_cache; ++ } ++ ++ /* Create a memory pool for dl_group */ ++ ad->dl_group_pool = kmem_cache_create("dl_group_pool", ++ sizeof(struct dl_group), ++ 0, SLAB_HWCACHE_ALIGN, NULL); ++ if (!ad->dl_group_pool) { ++ pr_err("adios: Failed to create dl_group_pool\n"); ++ goto destroy_rq_data_pool; ++ } ++ ++ for (int i = 0; i < ADIOS_PQ_LEVELS; i++) ++ INIT_LIST_HEAD(&ad->prio_queue[i]); ++ ++ for (u8 i = 0; i < ADIOS_DL_TYPES; i++) { ++ ad->dl_tree[i] = RB_ROOT_CACHED; ++ ad->dl_prio[i] = default_dl_prio[i]; ++ } ++ ad->dl_bias = 0; ++ ++ for (u8 page = 0; page < ADIOS_BQ_PAGES; page++) ++ for (optype = 0; optype < ADIOS_OPTYPES; optype++) ++ INIT_LIST_HEAD(&ad->batch_queue[page][optype]); ++ ++ ad->aggr_buckets = kzalloc(sizeof(*ad->aggr_buckets), GFP_KERNEL); ++ if (!ad->aggr_buckets) { ++ pr_err("adios: Failed to allocate aggregation buckets\n"); ++ goto destroy_dl_group_pool; ++ } ++ ++ ad->pcpu_completion = alloc_percpu(struct adios_pcpu_completion); ++ if (!ad->pcpu_completion) { ++ pr_err("adios: Failed to allocate per-CPU completion data\n"); ++ goto free_aggr_buckets; ++ } ++ ++ for (optype = 0; optype < ADIOS_OPTYPES; optype++) { ++ struct latency_model *model = &ad->latency_model[optype]; ++ struct latency_model_params *params; ++ ++ spin_lock_init(&model->update_lock); ++ params = kzalloc(sizeof(*params), GFP_KERNEL); ++ if (!params) { ++ pr_err("adios: Failed to allocate latency_model_params\n"); ++ goto free_buckets; ++ } ++ params->last_update_jiffies = jiffies; ++ RCU_INIT_POINTER(model->params, params); ++ ++ model->pcpu_buckets = alloc_percpu(struct lm_buckets); ++ if (!model->pcpu_buckets) { ++ pr_err("adios: Failed to allocate per-CPU buckets\n"); ++ kfree(params); ++ goto free_buckets; ++ } ++ ++ model->pcpu_snapshot = alloc_percpu(struct lm_buckets); ++ if (!model->pcpu_snapshot) { ++ pr_err("adios: Failed to allocate per-CPU snapshot\n"); ++ free_percpu(model->pcpu_buckets); ++ kfree(params); ++ goto free_buckets; ++ } ++ ++ model->lm_shrink_at_kreqs = default_lm_shrink_at_kreqs; ++ model->lm_shrink_at_gbytes = default_lm_shrink_at_gbytes; ++ model->lm_shrink_resist = default_lm_shrink_resist; ++ } ++ ++ for (optype = 0; optype < ADIOS_OPTYPES; optype++) { ++ ad->latency_target[optype] = default_latency_target[optype]; ++ ad->batch_limit[optype] = default_batch_limit[optype]; ++ } ++ ++ eq->elevator_data = ad; ++ ++ ad->is_rotational = !!(q->limits.features & BLK_FEAT_ROTATIONAL); ++ ad->global_latency_window = (ad->is_rotational)? ++ default_global_latency_window_rotational: ++ default_global_latency_window; ++ ad->bq_refill_below_ratio = default_bq_refill_below_ratio; ++ ad->lat_model_latency_limit = default_lat_model_latency_limit; ++ ad->batch_order = default_batch_order; ++ ad->compliance_flags = default_compliance_flags; ++ ++ ad->models_stable = false; ++ ++ atomic_set(&ad->state, 0); ++ ++ spin_lock_init(&ad->lock); ++ spin_lock_init(&ad->pq_lock); ++ spin_lock_init(&ad->bq_lock); ++ spin_lock_init(&ad->barrier_lock); ++ INIT_LIST_HEAD(&ad->barrier_queue); ++ ++ timer_setup(&ad->update_timer, update_timer_callback, 0); ++ ++ /* We dispatch from request queue wide instead of hw queue */ ++ blk_queue_flag_set(QUEUE_FLAG_SQ_SCHED, q); ++ ++ ad->queue = q; ++ blk_stat_enable_accounting(q); ++ ++ q->elevator = eq; ++ adios_depth_updated(q); ++ return 0; ++ ++free_buckets: ++ pr_err("adios: Failed to allocate per-cpu buckets\n"); ++ while (optype-- > 0) { ++ struct latency_model *prev_model = &ad->latency_model[optype]; ++ kfree(rcu_access_pointer(prev_model->params)); ++ free_percpu(prev_model->pcpu_snapshot); ++ free_percpu(prev_model->pcpu_buckets); ++ } ++ free_percpu(ad->pcpu_completion); ++free_aggr_buckets: ++ kfree(ad->aggr_buckets); ++destroy_dl_group_pool: ++ kmem_cache_destroy(ad->dl_group_pool); ++destroy_rq_data_pool: ++ mempool_destroy(ad->rq_data_pool); ++destroy_rq_data_cache: ++ kmem_cache_destroy(ad->rq_data_cache); ++free_ad: ++ kfree(ad); ++put_eq: ++ kobject_put(&eq->kobj); ++ return ret; ++} ++ ++// Clean up and free resources when exiting the scheduler ++static void adios_exit_sched(struct elevator_queue *e) { ++ struct adios_data *ad = e->elevator_data; ++ ++ timer_shutdown_sync(&ad->update_timer); ++ ++ WARN_ON_ONCE(!list_empty(&ad->barrier_queue)); ++ for (int i = 0; i < 2; i++) ++ WARN_ON_ONCE(!list_empty(&ad->prio_queue[i])); ++ ++ for (u8 i = 0; i < ADIOS_OPTYPES; i++) { ++ struct latency_model *model = &ad->latency_model[i]; ++ struct latency_model_params *params = rcu_access_pointer(model->params); ++ ++ RCU_INIT_POINTER(model->params, NULL); ++ kfree_rcu(params, rcu); ++ ++ free_percpu(model->pcpu_snapshot); ++ free_percpu(model->pcpu_buckets); ++ } ++ ++ synchronize_rcu(); ++ ++ free_percpu(ad->pcpu_completion); ++ kfree(ad->aggr_buckets); ++ ++ mempool_destroy(ad->rq_data_pool); ++ kmem_cache_destroy(ad->rq_data_cache); ++ ++ if (ad->dl_group_pool) ++ kmem_cache_destroy(ad->dl_group_pool); ++ ++ blk_stat_disable_accounting(ad->queue); ++ ++ kfree(ad); ++} ++ ++static void sideload_latency_model( ++ struct latency_model *model, u64 base, u64 slope) { ++ struct latency_model_params *old_params, *new_params; ++ unsigned long flags; ++ ++ new_params = kzalloc(sizeof(*new_params), GFP_KERNEL); ++ if (!new_params) ++ return; ++ ++ spin_lock_irqsave(&model->update_lock, flags); ++ ++ old_params = rcu_dereference_protected(model->params, ++ lockdep_is_held(&model->update_lock)); ++ ++ new_params->last_update_jiffies = jiffies; ++ ++ // Initialize base and its statistics as a single sample. ++ new_params->base = base; ++ new_params->small_sum_delay = base; ++ new_params->small_count = 1; ++ ++ // Initialize slope and its statistics as a single sample. ++ new_params->slope = slope; ++ new_params->large_sum_delay = slope; ++ new_params->large_sum_bsize = 1024; /* Corresponds to 1 KiB */ ++ ++ lm_reset_pcpu_buckets(model); ++ ++ rcu_assign_pointer(model->params, new_params); ++ spin_unlock_irqrestore(&model->update_lock, flags); ++ ++ kfree_rcu(old_params, rcu); ++} ++ ++// Define sysfs attributes for operation types ++#define SYSFS_OPTYPE_DECL(name, optype) \ ++static ssize_t adios_lat_model_##name##_show( \ ++ struct elevator_queue *e, char *page) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ struct latency_model *model = &ad->latency_model[optype]; \ ++ struct latency_model_params *params; \ ++ ssize_t len = 0; \ ++ u64 base, slope; \ ++ rcu_read_lock(); \ ++ params = rcu_dereference(model->params); \ ++ base = params->base; \ ++ slope = params->slope; \ ++ rcu_read_unlock(); \ ++ len += sprintf(page, "base : %llu ns\n", base); \ ++ len += sprintf(page + len, "slope: %llu ns/KiB\n", slope); \ ++ return len; \ ++} \ ++static ssize_t adios_lat_model_##name##_store( \ ++ struct elevator_queue *e, const char *page, size_t count) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ struct latency_model *model = &ad->latency_model[optype]; \ ++ u64 base, slope; \ ++ int ret; \ ++ ret = sscanf(page, "%llu %llu", &base, &slope); \ ++ if (ret != 2) \ ++ return -EINVAL; \ ++ sideload_latency_model(model, base, slope); \ ++ reset_buckets(ad->aggr_buckets); \ ++ return count; \ ++} \ ++static ssize_t adios_lat_target_##name##_show( \ ++ struct elevator_queue *e, char *page) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ return sprintf(page, "%llu\n", ad->latency_target[optype]); \ ++} \ ++static ssize_t adios_lat_target_##name##_store( \ ++ struct elevator_queue *e, const char *page, size_t count) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ unsigned long nsec; \ ++ int ret; \ ++ ret = kstrtoul(page, 10, &nsec); \ ++ if (ret) \ ++ return ret; \ ++ sideload_latency_model(&ad->latency_model[optype], 0, 0); \ ++ ad->latency_target[optype] = nsec; \ ++ return count; \ ++} \ ++static ssize_t adios_batch_limit_##name##_show( \ ++ struct elevator_queue *e, char *page) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ return sprintf(page, "%u\n", ad->batch_limit[optype]); \ ++} \ ++static ssize_t adios_batch_limit_##name##_store( \ ++ struct elevator_queue *e, const char *page, size_t count) { \ ++ unsigned long max_batch; \ ++ int ret; \ ++ ret = kstrtoul(page, 10, &max_batch); \ ++ if (ret || max_batch == 0) \ ++ return -EINVAL; \ ++ struct adios_data *ad = e->elevator_data; \ ++ ad->batch_limit[optype] = max_batch; \ ++ return count; \ ++} ++ ++SYSFS_OPTYPE_DECL(read, ADIOS_READ); ++SYSFS_OPTYPE_DECL(write, ADIOS_WRITE); ++SYSFS_OPTYPE_DECL(discard, ADIOS_DISCARD); ++ ++// Show the maximum batch size actually achieved for each operation type ++static ssize_t adios_batch_actual_max_show( ++ struct elevator_queue *e, char *page) { ++ struct adios_data *ad = e->elevator_data; ++ u32 total_count, read_count, write_count, discard_count; ++ ++ total_count = ad->batch_actual_max_total; ++ read_count = ad->batch_actual_max_size[ADIOS_READ]; ++ write_count = ad->batch_actual_max_size[ADIOS_WRITE]; ++ discard_count = ad->batch_actual_max_size[ADIOS_DISCARD]; ++ ++ return sprintf(page, ++ "Total : %u\nDiscard: %u\nRead : %u\nWrite : %u\n", ++ total_count, discard_count, read_count, write_count); ++} ++ ++#define SYSFS_ULL_DECL(field, min_val, max_val) \ ++static ssize_t adios_##field##_show( \ ++ struct elevator_queue *e, char *page) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ return sprintf(page, "%llu\n", ad->field); \ ++} \ ++static ssize_t adios_##field##_store( \ ++ struct elevator_queue *e, const char *page, size_t count) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ unsigned long val; \ ++ int ret; \ ++ ret = kstrtoul(page, 10, &val); \ ++ if (ret || val < (min_val) || val > (max_val)) \ ++ return -EINVAL; \ ++ ad->field = val; \ ++ return count; \ ++} ++ ++SYSFS_ULL_DECL(global_latency_window, 0, ULLONG_MAX) ++SYSFS_ULL_DECL(compliance_flags, 0, ULLONG_MAX) ++ ++#define SYSFS_INT_DECL(field, min_val, max_val) \ ++static ssize_t adios_##field##_show( \ ++ struct elevator_queue *e, char *page) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ return sprintf(page, "%d\n", ad->field); \ ++} \ ++static ssize_t adios_##field##_store( \ ++ struct elevator_queue *e, const char *page, size_t count) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ int val; \ ++ int ret; \ ++ ret = kstrtoint(page, 10, &val); \ ++ if (ret || val < (min_val) || val > (max_val)) \ ++ return -EINVAL; \ ++ ad->field = val; \ ++ return count; \ ++} ++ ++SYSFS_INT_DECL(bq_refill_below_ratio, 0, 100) ++SYSFS_INT_DECL(lat_model_latency_limit, 0, 2*NSEC_PER_SEC) ++SYSFS_INT_DECL(batch_order, ADIOS_BO_OPTYPE, !!ad->is_rotational) ++ ++// Show the read priority ++static ssize_t adios_read_priority_show( ++ struct elevator_queue *e, char *page) { ++ struct adios_data *ad = e->elevator_data; ++ return sprintf(page, "%d\n", ad->dl_prio[0]); ++} ++ ++// Set the read priority ++static ssize_t adios_read_priority_store( ++ struct elevator_queue *e, const char *page, size_t count) { ++ struct adios_data *ad = e->elevator_data; ++ int prio; ++ int ret; ++ ++ ret = kstrtoint(page, 10, &prio); ++ if (ret || prio < -20 || prio > 19) ++ return -EINVAL; ++ ++ guard(spinlock_irqsave)(&ad->lock); ++ ad->dl_prio[0] = prio; ++ ad->dl_bias = 0; ++ ++ return count; ++} ++ ++// Reset batch queue statistics ++static ssize_t adios_reset_bq_stats_store( ++ struct elevator_queue *e, const char *page, size_t count) { ++ struct adios_data *ad = e->elevator_data; ++ unsigned long val; ++ int ret; ++ ++ ret = kstrtoul(page, 10, &val); ++ if (ret || val != 1) ++ return -EINVAL; ++ ++ for (u8 i = 0; i < ADIOS_OPTYPES; i++) ++ ad->batch_actual_max_size[i] = 0; ++ ++ ad->batch_actual_max_total = 0; ++ ++ return count; ++} ++ ++// Reset the latency model parameters or load them from user input ++static ssize_t adios_reset_lat_model_store( ++ struct elevator_queue *e, const char *page, size_t count) ++{ ++ struct adios_data *ad = e->elevator_data; ++ struct latency_model *model; ++ int ret; ++ ++ /* ++ * Differentiate between two modes based on input format: ++ * 1. "1": Fully reset the model (backward compatibility). ++ * 2. "R_base R_slope W_base W_slope D_base D_slope": Load values. ++ */ ++ if (!strchr(page, ' ')) { ++ // Mode 1: Full reset. ++ unsigned long val; ++ ++ ret = kstrtoul(page, 10, &val); ++ if (ret || val != 1) ++ return -EINVAL; ++ ++ for (u8 i = 0; i < ADIOS_OPTYPES; i++) { ++ model = &ad->latency_model[i]; ++ sideload_latency_model(model, 0, 0); ++ } ++ } else { ++ // Mode 2: Load initial values for all latency models. ++ u64 params[3][2]; /* 0:base, 1:slope for R, W, D */ ++ ++ ret = sscanf(page, "%llu %llu %llu %llu %llu %llu", ++ ¶ms[ADIOS_READ ][0], ¶ms[ADIOS_READ ][1], ++ ¶ms[ADIOS_WRITE ][0], ¶ms[ADIOS_WRITE ][1], ++ ¶ms[ADIOS_DISCARD][0], ¶ms[ADIOS_DISCARD][1]); ++ ++ if (ret != 6) ++ return -EINVAL; ++ ++ for (u8 i = ADIOS_READ; i <= ADIOS_DISCARD; i++) { ++ model = &ad->latency_model[i]; ++ sideload_latency_model(model, params[i][0], params[i][1]); ++ } ++ } ++ reset_buckets(ad->aggr_buckets); ++ ++ return count; ++} ++ ++// Show the ADIOS version ++static ssize_t adios_version_show(struct elevator_queue *e, char *page) { ++ return sprintf(page, "%s\n", ADIOS_VERSION); ++} ++ ++// Define sysfs attributes for dynamic thresholds ++#define SHRINK_THRESHOLD_ATTR_RW(name, model_field, min_value, max_value) \ ++static ssize_t adios_shrink_##name##_store( \ ++ struct elevator_queue *e, const char *page, size_t count) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ unsigned long val; \ ++ int ret; \ ++ ret = kstrtoul(page, 10, &val); \ ++ if (ret || val < min_value || val > max_value) \ ++ return -EINVAL; \ ++ for (u8 i = 0; i < ADIOS_OPTYPES; i++) { \ ++ struct latency_model *model = &ad->latency_model[i]; \ ++ unsigned long flags; \ ++ spin_lock_irqsave(&model->update_lock, flags); \ ++ model->model_field = val; \ ++ spin_unlock_irqrestore(&model->update_lock, flags); \ ++ } \ ++ return count; \ ++} \ ++static ssize_t adios_shrink_##name##_show( \ ++ struct elevator_queue *e, char *page) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ u32 val = 0; \ ++ unsigned long flags; \ ++ struct latency_model *model = &ad->latency_model[0]; \ ++ spin_lock_irqsave(&model->update_lock, flags); \ ++ val = model->model_field; \ ++ spin_unlock_irqrestore(&model->update_lock, flags); \ ++ return sprintf(page, "%u\n", val); \ ++} ++ ++SHRINK_THRESHOLD_ATTR_RW(at_kreqs, lm_shrink_at_kreqs, 1, 100000) ++SHRINK_THRESHOLD_ATTR_RW(at_gbytes, lm_shrink_at_gbytes, 1, 1000) ++SHRINK_THRESHOLD_ATTR_RW(resist, lm_shrink_resist, 1, 3) ++ ++// Define sysfs attributes ++#define AD_ATTR(name, show_func, store_func) \ ++ __ATTR(name, 0644, show_func, store_func) ++#define AD_ATTR_RW(name) \ ++ __ATTR(name, 0644, adios_##name##_show, adios_##name##_store) ++#define AD_ATTR_RO(name) \ ++ __ATTR(name, 0444, adios_##name##_show, NULL) ++#define AD_ATTR_WO(name) \ ++ __ATTR(name, 0200, NULL, adios_##name##_store) ++ ++// Define sysfs attributes for ADIOS scheduler ++static struct elv_fs_entry adios_sched_attrs[] = { ++ AD_ATTR_RO(batch_actual_max), ++ AD_ATTR_RW(bq_refill_below_ratio), ++ AD_ATTR_RW(global_latency_window), ++ AD_ATTR_RW(lat_model_latency_limit), ++ AD_ATTR_RW(batch_order), ++ AD_ATTR_RW(compliance_flags), ++ ++ AD_ATTR_RW(batch_limit_read), ++ AD_ATTR_RW(batch_limit_write), ++ AD_ATTR_RW(batch_limit_discard), ++ ++ AD_ATTR_RW(lat_model_read), ++ AD_ATTR_RW(lat_model_write), ++ AD_ATTR_RW(lat_model_discard), ++ ++ AD_ATTR_RW(lat_target_read), ++ AD_ATTR_RW(lat_target_write), ++ AD_ATTR_RW(lat_target_discard), ++ ++ AD_ATTR_RW(shrink_at_kreqs), ++ AD_ATTR_RW(shrink_at_gbytes), ++ AD_ATTR_RW(shrink_resist), ++ ++ AD_ATTR_RW(read_priority), ++ ++ AD_ATTR_WO(reset_bq_stats), ++ AD_ATTR_WO(reset_lat_model), ++ AD_ATTR(adios_version, adios_version_show, NULL), ++ ++ __ATTR_NULL ++}; ++ ++// Define the ADIOS scheduler type ++static struct elevator_type mq_adios = { ++ .ops = { ++ .next_request = elv_rb_latter_request, ++ .former_request = elv_rb_former_request, ++ .limit_depth = adios_limit_depth, ++ .depth_updated = adios_depth_updated, ++ .request_merged = adios_request_merged, ++ .requests_merged = adios_merged_requests, ++ .bio_merge = adios_bio_merge, ++ .insert_requests = adios_insert_requests, ++ .prepare_request = adios_prepare_request, ++ .dispatch_request = adios_dispatch_request, ++ .completed_request = adios_completed_request, ++ .finish_request = adios_finish_request, ++ .has_work = adios_has_work, ++ .init_sched = adios_init_sched, ++ .exit_sched = adios_exit_sched, ++ }, ++ .elevator_attrs = adios_sched_attrs, ++ .elevator_name = "adios", ++ .elevator_owner = THIS_MODULE, ++}; ++MODULE_ALIAS("mq-adios-iosched"); ++ ++#define ADIOS_PROGNAME "Adaptive Deadline I/O Scheduler" ++#define ADIOS_AUTHOR "Masahito Suzuki" ++ ++// Initialize the ADIOS scheduler module ++static int __init adios_init(void) { ++ printk(KERN_INFO "%s %s by %s\n", ++ ADIOS_PROGNAME, ADIOS_VERSION, ADIOS_AUTHOR); ++ return elv_register(&mq_adios); ++} ++ ++// Exit the ADIOS scheduler module ++static void __exit adios_exit(void) { ++ elv_unregister(&mq_adios); ++} ++ ++module_init(adios_init); ++module_exit(adios_exit); ++ ++MODULE_AUTHOR(ADIOS_AUTHOR); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION(ADIOS_PROGNAME); +\ No newline at end of file +diff --git a/block/elevator.c b/block/elevator.c +index 2161b6eea680..72a0dc6b345f 100644 +--- a/block/elevator.c ++++ b/block/elevator.c +@@ -740,6 +740,14 @@ void elevator_set_default(struct request_queue *q) + if (q->tag_set->flags & BLK_MQ_F_NO_SCHED_BY_DEFAULT) + return; + ++#ifdef CONFIG_MQ_IOSCHED_DEFAULT_ADIOS ++ ctx.name = "adios"; ++#else // !CONFIG_MQ_IOSCHED_DEFAULT_ADIOS ++ bool is_sq = q->nr_hw_queues == 1 || blk_mq_is_shared_tags(q->tag_set->flags); ++ if (!is_sq) ++ return; ++#endif // CONFIG_MQ_IOSCHED_DEFAULT_ADIOS ++ + /* + * For single queue devices, default to using mq-deadline. If we + * have multiple queues or mq-deadline is not available, default +@@ -749,13 +757,10 @@ void elevator_set_default(struct request_queue *q) + if (!ctx.type) + return; + +- if ((q->nr_hw_queues == 1 || +- blk_mq_is_shared_tags(q->tag_set->flags))) { +- err = elevator_change(q, &ctx); +- if (err < 0) +- pr_warn("\"%s\" elevator initialization, failed %d, falling back to \"none\"\n", +- ctx.name, err); +- } ++ err = elevator_change(q, &ctx); ++ if (err < 0) ++ pr_warn("\"%s\" elevator initialization, failed %d, falling back to \"none\"\n", ++ ctx.name, err); + elevator_put(ctx.type); + } + diff --git a/pkgbuilds/linux-omarchy-bore/0200-idle.patch b/pkgbuilds/linux-omarchy-bore/0200-idle.patch new file mode 100644 index 0000000..dc499e5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0200-idle.patch @@ -0,0 +1,61 @@ +diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c +--- a/drivers/idle/intel_idle.c ++++ b/drivers/idle/intel_idle.c +@@ -53,6 +53,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2697,6 +2698,9 @@ static void __init cmdline_table_adjust(struct cpuidle_driver *drv) + pr_info("Failed to adjust C-states with data from 'intel_idle.table'\n"); + } + ++#define INTEL_IDLE_INIT_QOS 20 ++static struct pm_qos_request qos_req __initdata; ++ + static int __init intel_idle_init(void) + { + const struct x86_cpu_id *id; +@@ -2766,6 +2770,13 @@ static int __init intel_idle_init(void) + if (retval) + pr_warn("failed to initialized sysfs"); + ++ /* ++ * Some platforms, in particular the Intel S1200BTL motherboard, have a ++ * problem with using package idle states too early, so prevent that ++ * from taking place until the device_initcall() phase is over. ++ */ ++ cpu_latency_qos_add_request(&qos_req, INTEL_IDLE_INIT_QOS); ++ + retval = cpuidle_register_driver(&intel_idle_driver); + if (retval) { + struct cpuidle_driver *drv = cpuidle_get_driver(); +@@ -2790,6 +2801,9 @@ static int __init intel_idle_init(void) + intel_idle_cpuidle_devices_uninit(); + cpuidle_unregister_driver(&intel_idle_driver); + init_driver_fail: ++ if (cpu_latency_qos_request_active((&qos_req))) ++ cpu_latency_qos_remove_request(&qos_req); ++ + intel_idle_sysfs_uninit(); + free_percpu(intel_idle_cpuidle_devices); + return retval; +@@ -2797,6 +2811,15 @@ static int __init intel_idle_init(void) + } + subsys_initcall_sync(intel_idle_init); + ++static int __init intel_idle_init_complete(void) ++{ ++ if (cpu_latency_qos_request_active((&qos_req))) ++ cpu_latency_qos_remove_request(&qos_req); ++ ++ return 0; ++} ++device_initcall_sync(intel_idle_init_complete); ++ + /* + * We are not really modular, but we used to support that. Meaning we also + * support "intel_idle.max_cstate=..." at boot and also a read-only export of diff --git a/pkgbuilds/linux-omarchy-bore/0210-pstate.patch b/pkgbuilds/linux-omarchy-bore/0210-pstate.patch new file mode 100644 index 0000000..3d4eefc --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0210-pstate.patch @@ -0,0 +1,480 @@ +diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c +--- a/drivers/cpufreq/amd-pstate.c ++++ b/drivers/cpufreq/amd-pstate.c +@@ -782,6 +782,7 @@ static unsigned int amd_pstate_fast_switch(struct cpufreq_policy *policy, + static void amd_pstate_adjust_perf(struct cpufreq_policy *policy, + unsigned long _min_perf, + unsigned long target_perf, ++ unsigned long _max_perf, + unsigned long capacity) + { + u8 max_perf, min_perf, des_perf, cap_perf; +diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c +--- a/drivers/cpufreq/cpufreq.c ++++ b/drivers/cpufreq/cpufreq.c +@@ -2225,14 +2225,17 @@ EXPORT_SYMBOL_GPL(cpufreq_driver_fast_switch); + * @policy: cpufreq policy object of the target CPU. + * @min_perf: Minimum (required) performance level (units of @capacity). + * @target_perf: Target (desired) performance level (units of @capacity). ++ * @max_perf: Maximum (allowed) performance level (units of @capacity). + * @capacity: Capacity of the target CPU. + * +- * Carry out a fast performance level switch of @cpu without sleeping. ++ * Carry out a fast performance level adjustment for the CPU represented by ++ * @policy without sleeping. + * + * The driver's ->adjust_perf() callback invoked by this function must be +- * suitable for being called from within RCU-sched read-side critical sections +- * and it is expected to select a suitable performance level equal to or above +- * @min_perf and preferably equal to or below @target_perf. ++ * suitable for calling from within RCU-sched read-side critical sections and ++ * it is expected to program the processor to select suitable performance ++ * levels between @min_perf and @max_perf inclusive and preferably close to ++ * @target_perf going forward for the CPU represented by @policy. + * + * This function must not be called if policy->fast_switch_enabled is unset. + * +@@ -2244,9 +2247,10 @@ EXPORT_SYMBOL_GPL(cpufreq_driver_fast_switch); + void cpufreq_driver_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity) + { +- cpufreq_driver->adjust_perf(policy, min_perf, target_perf, capacity); ++ cpufreq_driver->adjust_perf(policy, min_perf, target_perf, max_perf, capacity); + } + + /** +diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c +--- a/drivers/cpufreq/intel_pstate.c ++++ b/drivers/cpufreq/intel_pstate.c +@@ -299,7 +299,6 @@ struct pstate_funcs { + static struct pstate_funcs pstate_funcs __read_mostly; + + static bool hwp_active __ro_after_init; +-static int hwp_mode_bdw __ro_after_init; + static bool per_cpu_limits __ro_after_init; + static bool hwp_forced __ro_after_init; + static bool hwp_boost __read_mostly; +@@ -587,21 +586,14 @@ static void intel_pstate_hybrid_hwp_adjust(struct cpudata *cpu) + + hwp_is_hybrid = true; + +- cpu->pstate.turbo_freq = rounddown(cpu->pstate.turbo_pstate * scaling, +- perf_ctl_scaling); +- cpu->pstate.max_freq = rounddown(cpu->pstate.max_pstate * scaling, +- perf_ctl_scaling); +- + freq = perf_ctl_max_phys * perf_ctl_scaling; + cpu->pstate.max_pstate_physical = intel_pstate_freq_to_hwp(cpu, freq); + +- freq = cpu->pstate.min_pstate * perf_ctl_scaling; +- cpu->pstate.min_freq = freq; + /* + * Cast the min P-state value retrieved via pstate_funcs.get_min() to + * the effective range of HWP performance levels. + */ +- cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, freq); ++ cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, cpu->pstate.min_freq); + } + + static bool turbo_is_disabled(void) +@@ -979,12 +971,10 @@ static int hybrid_get_cost(struct device *dev, unsigned long freq, + * capacity. Similarly, P-cores start to be populated when E-cores are + * utilized above 60% of the capacity. + */ +- if (hybrid_get_cpu_type(dev->id) == INTEL_CPU_TYPE_ATOM) { +- if (hybrid_has_l3(dev->id)) /* E-core */ +- *cost += 1; +- } else { /* P-core */ ++ if (hybrid_get_cpu_type(dev->id) == INTEL_CPU_TYPE_CORE) /* P-core */ + *cost += 2; +- } ++ else if (hybrid_has_l3(dev->id)) /* E-core */ ++ *cost += 1; + + return 0; + } +@@ -1185,6 +1175,22 @@ static bool hybrid_clear_max_perf_cpu(void) + return ret; + } + ++static void intel_pstate_update_freq_limits(struct cpudata *cpu) ++{ ++ int scaling = cpu->pstate.scaling; ++ unsigned int turbo_freq = cpu->pstate.turbo_pstate * scaling; ++ unsigned int max_freq = cpu->pstate.max_pstate * scaling; ++ int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; ++ ++ if (scaling != perf_ctl_scaling) { ++ turbo_freq = rounddown(turbo_freq, perf_ctl_scaling); ++ max_freq = rounddown(max_freq, perf_ctl_scaling); ++ } ++ ++ cpu->pstate.turbo_freq = turbo_freq; ++ cpu->pstate.max_freq = max_freq; ++} ++ + static void __intel_pstate_get_hwp_cap(struct cpudata *cpu) + { + u64 cap; +@@ -1197,20 +1203,8 @@ static void __intel_pstate_get_hwp_cap(struct cpudata *cpu) + + static void intel_pstate_get_hwp_cap(struct cpudata *cpu) + { +- int scaling = cpu->pstate.scaling; +- + __intel_pstate_get_hwp_cap(cpu); +- +- cpu->pstate.max_freq = cpu->pstate.max_pstate * scaling; +- cpu->pstate.turbo_freq = cpu->pstate.turbo_pstate * scaling; +- if (scaling != cpu->pstate.perf_ctl_scaling) { +- int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; +- +- cpu->pstate.max_freq = rounddown(cpu->pstate.max_freq, +- perf_ctl_scaling); +- cpu->pstate.turbo_freq = rounddown(cpu->pstate.turbo_freq, +- perf_ctl_scaling); +- } ++ intel_pstate_update_freq_limits(cpu); + } + + static void hybrid_update_capacity(struct cpudata *cpu) +@@ -2299,33 +2293,16 @@ static int hwp_get_cpu_scaling(int cpu) + return intel_pstate_cppc_get_scaling(cpu); + } + +-static void intel_pstate_set_pstate(struct cpudata *cpu, int pstate) +-{ +- trace_cpu_frequency(pstate * cpu->pstate.scaling, cpu->cpu); +- cpu->pstate.current_pstate = pstate; +- /* +- * Generally, there is no guarantee that this code will always run on +- * the CPU being updated, so force the register update to run on the +- * right CPU. +- */ +- wrmsrq_on_cpu(cpu->cpu, MSR_IA32_PERF_CTL, +- pstate_funcs.get_val(cpu, pstate)); +-} +- +-static void intel_pstate_set_min_pstate(struct cpudata *cpu) +-{ +- intel_pstate_set_pstate(cpu, cpu->pstate.min_pstate); +-} +- + static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) + { + int perf_ctl_scaling = pstate_funcs.get_scaling(); + + cpu->pstate.max_pstate_physical = pstate_funcs.get_max_physical(cpu->cpu); + cpu->pstate.min_pstate = pstate_funcs.get_min(cpu->cpu); ++ cpu->pstate.min_freq = cpu->pstate.min_pstate * perf_ctl_scaling; + cpu->pstate.perf_ctl_scaling = perf_ctl_scaling; + +- if (hwp_active && !hwp_mode_bdw) { ++ if (hwp_active) { + __intel_pstate_get_hwp_cap(cpu); + + if (pstate_funcs.get_cpu_scaling) { +@@ -2345,19 +2322,13 @@ static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) + cpu->pstate.turbo_pstate = pstate_funcs.get_turbo(cpu->cpu); + } + +- if (cpu->pstate.scaling == perf_ctl_scaling) { +- cpu->pstate.min_freq = cpu->pstate.min_pstate * perf_ctl_scaling; +- cpu->pstate.max_freq = cpu->pstate.max_pstate * perf_ctl_scaling; +- cpu->pstate.turbo_freq = cpu->pstate.turbo_pstate * perf_ctl_scaling; +- } ++ intel_pstate_update_freq_limits(cpu); + + if (pstate_funcs.get_aperf_mperf_shift) + cpu->aperf_mperf_shift = pstate_funcs.get_aperf_mperf_shift(); + + if (pstate_funcs.get_vid) + pstate_funcs.get_vid(cpu); +- +- intel_pstate_set_min_pstate(cpu); + } + + /* +@@ -2884,8 +2855,22 @@ static void intel_pstate_update_perf_limits(struct cpudata *cpu, + cpu->min_perf_ratio); + } + ++static void intel_pstate_set_pstate(struct cpudata *cpu, int pstate) ++{ ++ trace_cpu_frequency(pstate * cpu->pstate.scaling, cpu->cpu); ++ cpu->pstate.current_pstate = pstate; ++ /* ++ * Generally, there is no guarantee that this code will always run on ++ * the CPU being updated, so force the register update to run on the ++ * right CPU. ++ */ ++ wrmsrq_on_cpu(cpu->cpu, MSR_IA32_PERF_CTL, ++ pstate_funcs.get_val(cpu, pstate)); ++} ++ + static int intel_pstate_set_policy(struct cpufreq_policy *policy) + { ++ unsigned int freq = policy->min; + struct cpudata *cpu; + + if (!policy->cpuinfo.max_freq) +@@ -2901,7 +2886,23 @@ static int intel_pstate_set_policy(struct cpufreq_policy *policy) + + intel_pstate_update_perf_limits(cpu, policy->min, policy->max); + +- if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { ++ if (hwp_active) { ++ /* ++ * The active mode only requires an update util hook if HWP ++ * boost is used and the policy is not "performance". ++ */ ++ if (hwp_boost && cpu->policy != CPUFREQ_POLICY_PERFORMANCE) { ++ intel_pstate_set_update_util_hook(policy->cpu); ++ } else { ++ intel_pstate_clear_update_util_hook(policy->cpu); ++ if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { ++ freq = cpu->max_perf_ratio * cpu->pstate.scaling; ++ if (cpu->pstate.scaling != cpu->pstate.perf_ctl_scaling) ++ freq = rounddown(freq, cpu->pstate.perf_ctl_scaling); ++ } ++ } ++ intel_pstate_hwp_set(policy->cpu); ++ } else if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { + int pstate = max(cpu->pstate.min_pstate, cpu->max_perf_ratio); + + /* +@@ -2910,25 +2911,17 @@ static int intel_pstate_set_policy(struct cpufreq_policy *policy) + */ + intel_pstate_clear_update_util_hook(policy->cpu); + intel_pstate_set_pstate(cpu, pstate); ++ freq = pstate * cpu->pstate.scaling; + } else { + intel_pstate_set_update_util_hook(policy->cpu); + } +- +- if (hwp_active) { +- /* +- * When hwp_boost was active before and dynamically it +- * was turned off, in that case we need to clear the +- * update util hook. +- */ +- if (!hwp_boost) +- intel_pstate_clear_update_util_hook(policy->cpu); +- intel_pstate_hwp_set(policy->cpu); +- } + /* +- * policy->cur is never updated with the intel_pstate driver, but it +- * is used as a stale frequency value. So, keep it within limits. ++ * policy->cur is never updated in the intel_pstate driver, but it is ++ * used as a stale frequency value, so set it to reflect the actual ++ * requested P-state in the "performance" policy case and to the min ++ * otherwise. + */ +- policy->cur = policy->min; ++ policy->cur = freq; + + mutex_unlock(&intel_pstate_limits_lock); + +@@ -2971,6 +2964,11 @@ static int intel_pstate_verify_policy(struct cpufreq_policy_data *policy) + return 0; + } + ++static void intel_pstate_set_min_pstate(struct cpudata *cpu) ++{ ++ intel_pstate_set_pstate(cpu, cpu->pstate.min_pstate); ++} ++ + static int intel_cpufreq_cpu_offline(struct cpufreq_policy *policy) + { + struct cpudata *cpu = all_cpu_data[policy->cpu]; +@@ -3063,6 +3061,7 @@ static int __intel_pstate_cpu_init(struct cpufreq_policy *policy) + static int intel_pstate_cpu_init(struct cpufreq_policy *policy) + { + int ret = __intel_pstate_cpu_init(policy); ++ struct cpudata *cpu; + + if (ret) + return ret; +@@ -3073,11 +3072,11 @@ static int intel_pstate_cpu_init(struct cpufreq_policy *policy) + */ + policy->policy = CPUFREQ_POLICY_POWERSAVE; + +- if (hwp_active) { +- struct cpudata *cpu = all_cpu_data[policy->cpu]; +- ++ cpu = all_cpu_data[policy->cpu]; ++ if (hwp_active) + cpu->epp_cached = intel_pstate_get_epp(cpu, 0); +- } ++ else ++ intel_pstate_set_min_pstate(cpu); + + return 0; + } +@@ -3243,6 +3242,7 @@ static unsigned int intel_cpufreq_fast_switch(struct cpufreq_policy *policy, + static void intel_cpufreq_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity) + { + struct cpudata *cpu = all_cpu_data[policy->cpu]; +@@ -3273,7 +3273,13 @@ static void intel_cpufreq_adjust_perf(struct cpufreq_policy *policy, + if (min_pstate > cpu->max_perf_ratio) + min_pstate = cpu->max_perf_ratio; + +- max_pstate = min(cap_pstate, cpu->max_perf_ratio); ++ max_pstate = cap_pstate; ++ if (max_perf < capacity) ++ max_pstate = DIV_ROUND_UP(cap_pstate * max_perf, capacity); ++ ++ if (max_pstate > cpu->max_perf_ratio) ++ max_pstate = cpu->max_perf_ratio; ++ + if (max_pstate < min_pstate) + max_pstate = min_pstate; + +@@ -3301,8 +3307,6 @@ static int intel_cpufreq_cpu_init(struct cpufreq_policy *policy) + return ret; + + policy->cpuinfo.transition_latency = INTEL_CPUFREQ_TRANSITION_LATENCY; +- /* This reflects the intel_pstate_get_cpu_pstates() setting. */ +- policy->cur = policy->cpuinfo.min_freq; + + req = kzalloc_objs(*req, 2); + if (!req) { +@@ -3323,9 +3327,15 @@ static int intel_cpufreq_cpu_init(struct cpufreq_policy *policy) + WRITE_ONCE(cpu->hwp_req_cached, value); + + cpu->epp_cached = intel_pstate_get_epp(cpu, value); ++ ++ intel_cpufreq_hwp_update(cpu, cpu->pstate.min_pstate, ++ cpu->pstate.max_pstate, ++ cpu->pstate.min_pstate, false); + } else { + policy->transition_delay_us = INTEL_CPUFREQ_TRANSITION_DELAY; ++ intel_pstate_set_min_pstate(cpu); + } ++ policy->cur = policy->cpuinfo.min_freq; + + freq = DIV_ROUND_UP(cpu->pstate.turbo_freq * global.min_perf_pct, 100); + +@@ -3676,14 +3686,14 @@ static inline bool intel_pstate_has_acpi_ppc(void) { return false; } + static inline void intel_pstate_request_control_from_smm(void) {} + #endif /* CONFIG_ACPI */ + +-#define INTEL_PSTATE_HWP_BROADWELL 0x01 ++#define INTEL_PSTATE_HWP_NOT_HYBRID 0x01 + + #define X86_MATCH_HWP(vfm, hwp_mode) \ + X86_MATCH_VFM_FEATURE(vfm, X86_FEATURE_HWP, hwp_mode) + + static const struct x86_cpu_id hwp_support_ids[] __initconst = { +- X86_MATCH_HWP(INTEL_BROADWELL_X, INTEL_PSTATE_HWP_BROADWELL), +- X86_MATCH_HWP(INTEL_BROADWELL_D, INTEL_PSTATE_HWP_BROADWELL), ++ X86_MATCH_HWP(INTEL_BROADWELL_X, INTEL_PSTATE_HWP_NOT_HYBRID), ++ X86_MATCH_HWP(INTEL_BROADWELL_D, INTEL_PSTATE_HWP_NOT_HYBRID), + X86_MATCH_HWP(INTEL_ANY, 0), + {} + }; +@@ -3808,7 +3818,6 @@ static int __init intel_pstate_init(void) + + if (!no_hwp) { + hwp_active = true; +- hwp_mode_bdw = id->driver_data; + intel_pstate.attr = hwp_cpufreq_attrs; + intel_cpufreq.attr = hwp_cpufreq_attrs; + intel_cpufreq.flags |= CPUFREQ_NEED_UPDATE_LIMITS; +@@ -3816,7 +3825,8 @@ static int __init intel_pstate_init(void) + if (!default_driver) + default_driver = &intel_pstate; + +- pstate_funcs.get_cpu_scaling = hwp_get_cpu_scaling; ++ if (!id->driver_data) ++ pstate_funcs.get_cpu_scaling = hwp_get_cpu_scaling; + + goto hwp_cpu_matched; + } +diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h +--- a/include/linux/cpufreq.h ++++ b/include/linux/cpufreq.h +@@ -379,6 +379,7 @@ struct cpufreq_driver { + void (*adjust_perf)(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity); + + /* +@@ -624,6 +625,7 @@ unsigned int cpufreq_driver_fast_switch(struct cpufreq_policy *policy, + void cpufreq_driver_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity); + bool cpufreq_driver_has_adjust_perf(void); + int cpufreq_driver_target(struct cpufreq_policy *policy, +diff --git a/kernel/sched/cpufreq_schedutil.c b/kernel/sched/cpufreq_schedutil.c +--- a/kernel/sched/cpufreq_schedutil.c ++++ b/kernel/sched/cpufreq_schedutil.c +@@ -50,6 +50,7 @@ struct sugov_cpu { + + unsigned long util; + unsigned long bw_min; ++ unsigned long bw_max; + + /* The field below is for single-CPU policies only: */ + #ifdef CONFIG_NO_HZ_COMMON +@@ -243,6 +244,7 @@ static void sugov_get_util(struct sugov_cpu *sg_cpu, unsigned long boost) + util = effective_cpu_util(sg_cpu->cpu, util, &min, &max); + util = max(util, boost); + sg_cpu->bw_min = min; ++ sg_cpu->bw_max = max; + sg_cpu->util = sugov_effective_cpu_perf(sg_cpu->cpu, util, min, max); + } + +@@ -495,7 +497,7 @@ static void sugov_update_single_perf(struct update_util_data *hook, u64 time, + sg_cpu->util = prev_util; + + cpufreq_driver_adjust_perf(sg_policy->policy, sg_cpu->bw_min, +- sg_cpu->util, max_cap); ++ sg_cpu->util, sg_cpu->bw_max, max_cap); + + sg_policy->need_freq_update = false; + sg_policy->last_freq_update_time = time; +diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs +--- a/rust/kernel/cpufreq.rs ++++ b/rust/kernel/cpufreq.rs +@@ -792,7 +792,13 @@ fn fast_switch(_policy: &mut Policy, _target_freq: u32) -> u32 { + } + + /// Driver's `adjust_perf` callback. +- fn adjust_perf(_policy: &mut Policy, _min_perf: usize, _target_perf: usize, _capacity: usize) { ++ fn adjust_perf( ++ _policy: &mut Policy, ++ _min_perf: usize, ++ _target_perf: usize, ++ _max_perf: usize, ++ _capacity: usize, ++ ) { + build_error!(VTABLE_DEFAULT_ERROR) + } + +@@ -1263,12 +1269,13 @@ impl Registration { + ptr: *mut bindings::cpufreq_policy, + min_perf: c_ulong, + target_perf: c_ulong, ++ max_perf: c_ulong, + capacity: c_ulong, + ) { + // SAFETY: The `ptr` is guaranteed to be valid by the contract with the C code for the + // lifetime of `policy`. + let policy = unsafe { Policy::from_raw_mut(ptr) }; +- T::adjust_perf(policy, min_perf, target_perf, capacity); ++ T::adjust_perf(policy, min_perf, target_perf, max_perf, capacity); + } + + /// Driver's `get_intermediate` callback. diff --git a/pkgbuilds/linux-omarchy-bore/0211-amd-pstate-fixes.patch b/pkgbuilds/linux-omarchy-bore/0211-amd-pstate-fixes.patch new file mode 100644 index 0000000..67a6cf4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0211-amd-pstate-fixes.patch @@ -0,0 +1,127 @@ +diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c +--- a/drivers/cpufreq/amd-pstate-ut.c ++++ b/drivers/cpufreq/amd-pstate-ut.c +@@ -560,6 +560,11 @@ static int amd_pstate_ut_check_freq_attrs(u32 index) + static int __init amd_pstate_ut_init(void) + { + u32 i = 0, arr_size = ARRAY_SIZE(amd_pstate_ut_cases); ++ enum amd_pstate_mode mode = amd_pstate_get_status(); ++ ++ /* don't test if no running amd-pstate driver */ ++ if (mode == AMD_PSTATE_UNDEFINED || mode == AMD_PSTATE_DISABLE) ++ return -EOPNOTSUPP; + + for (i = 0; i < arr_size; i++) { + int ret; +diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c +--- a/drivers/cpufreq/amd-pstate.c ++++ b/drivers/cpufreq/amd-pstate.c +@@ -199,7 +199,7 @@ static inline int get_mode_idx_from_str(const char *str, size_t size) + + static DEFINE_MUTEX(amd_pstate_driver_lock); + +-static u8 msr_get_epp(struct amd_cpudata *cpudata) ++static int msr_get_epp(struct amd_cpudata *cpudata) + { + u64 value; + int ret; +@@ -215,12 +215,12 @@ static u8 msr_get_epp(struct amd_cpudata *cpudata) + + DEFINE_STATIC_CALL(amd_pstate_get_epp, msr_get_epp); + +-static inline s16 amd_pstate_get_epp(struct amd_cpudata *cpudata) ++static inline int amd_pstate_get_epp(struct amd_cpudata *cpudata) + { + return static_call(amd_pstate_get_epp)(cpudata); + } + +-static u8 shmem_get_epp(struct amd_cpudata *cpudata) ++static int shmem_get_epp(struct amd_cpudata *cpudata) + { + u64 epp; + int ret; +@@ -526,9 +526,6 @@ static int shmem_init_perf(struct amd_cpudata *cpudata) + WRITE_ONCE(cpudata->perf, perf); + WRITE_ONCE(cpudata->prefcore_ranking, cppc_perf.highest_perf); + +- if (cppc_state == AMD_PSTATE_ACTIVE) +- return 0; +- + ret = cppc_get_auto_sel(cpudata->cpu, &auto_sel); + if (ret) { + pr_warn("failed to get auto_sel, ret: %d\n", ret); +@@ -1174,6 +1171,9 @@ static int amd_pstate_power_supply_notifier(struct notifier_block *nb, + if (cpudata->current_profile != PLATFORM_PROFILE_BALANCED) + return 0; + ++ if (!policy) ++ return NOTIFY_OK; ++ + epp = amd_pstate_get_balanced_epp(policy); + + ret = amd_pstate_set_epp(policy, epp); +@@ -1209,6 +1209,9 @@ static int amd_pstate_profile_set(struct device *dev, + struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpudata->cpu); + int ret; + ++ if (!policy) ++ return -ENODEV; ++ + switch (profile) { + case PLATFORM_PROFILE_LOW_POWER: + ret = amd_pstate_set_epp(policy, AMD_CPPC_EPP_POWERSAVE); +@@ -1877,6 +1880,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + struct amd_cpudata *cpudata; + union perf_cached perf; + struct device *dev; ++ int default_epp; + int ret; + + /* +@@ -1925,6 +1929,13 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + + policy->boost_supported = READ_ONCE(cpudata->boost_supported); + ++ /* Fetch the firmware programmed default EPP value */ ++ default_epp = amd_pstate_get_epp(cpudata); ++ if (default_epp < 0) { ++ ret = default_epp; ++ goto free_cpudata1; ++ } ++ + /* + * Set the policy to provide a valid fallback value in case + * the default cpufreq governor is neither powersave nor performance. +@@ -1932,7 +1943,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + if (amd_pstate_acpi_pm_profile_server() || + amd_pstate_acpi_pm_profile_undefined()) { + policy->policy = CPUFREQ_POLICY_PERFORMANCE; +- cpudata->epp_default_ac = cpudata->epp_default_dc = amd_pstate_get_epp(cpudata); ++ cpudata->epp_default_ac = cpudata->epp_default_dc = default_epp; + cpudata->current_profile = PLATFORM_PROFILE_PERFORMANCE; + } else { + policy->policy = CPUFREQ_POLICY_POWERSAVE; +diff --git a/drivers/cpufreq/amd-pstate.h b/drivers/cpufreq/amd-pstate.h +--- a/drivers/cpufreq/amd-pstate.h ++++ b/drivers/cpufreq/amd-pstate.h +@@ -32,6 +32,7 @@ + * @min_limit_perf: Cached value of the performance corresponding to policy->min + * @max_limit_perf: Cached value of the performance corresponding to policy->max + * @bios_min_perf: Cached perf value corresponding to the "Requested CPU Min Frequency" BIOS option ++ * @val: Raw 64-bit value for atomic access via READ_ONCE()/WRITE_ONCE() + */ + union perf_cached { + struct { +@@ -89,7 +90,12 @@ struct amd_aperf_mperf { + * @epp_default_ac: Default EPP value for AC power source + * @epp_default_dc: Default EPP value for DC power source + * @dynamic_epp: Whether dynamic EPP is enabled ++ * @raw_epp: Whether the last EPP write was a raw numeric value rather than a ++ * named preference + * @power_nb: Notifier block for power events ++ * @current_profile: Currently selected platform profile option ++ * @ppdev: Device registered with the platform profile handler ++ * @profile_name: Name under which @ppdev is registered + * + * The amd_cpudata is key private data for each CPU thread in AMD P-State, and + * represents all the attributes and goals that AMD P-State requests at runtime. diff --git a/pkgbuilds/linux-omarchy-bore/0212-amd-pstate-epp-cache.patch b/pkgbuilds/linux-omarchy-bore/0212-amd-pstate-epp-cache.patch new file mode 100644 index 0000000..61ea14c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0212-amd-pstate-epp-cache.patch @@ -0,0 +1,18 @@ +diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c +--- a/drivers/cpufreq/amd-pstate.c ++++ b/drivers/cpufreq/amd-pstate.c +@@ -1929,12 +1929,13 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + + policy->boost_supported = READ_ONCE(cpudata->boost_supported); + +- /* Fetch the firmware programmed default EPP value */ ++ /* Cache the firmware programmed EPP */ + default_epp = amd_pstate_get_epp(cpudata); + if (default_epp < 0) { + ret = default_epp; + goto free_cpudata1; + } ++ FIELD_MODIFY(AMD_CPPC_EPP_PERF_MASK, &cpudata->cppc_req_cached, default_epp); + + /* + * Set the policy to provide a valid fallback value in case diff --git a/pkgbuilds/linux-omarchy-bore/0250-zsmalloc.patch b/pkgbuilds/linux-omarchy-bore/0250-zsmalloc.patch new file mode 100644 index 0000000..43793e5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0250-zsmalloc.patch @@ -0,0 +1,396 @@ +diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c +--- a/mm/zsmalloc.c ++++ b/mm/zsmalloc.c +@@ -21,6 +21,10 @@ + * pool->lock + * class->lock + * zspage->lock ++ * ++ * When ZS_OBJ_CLASS_BITS > 0, zs_free() skips pool->lock; it picks ++ * the size_class from obj's encoded class_idx and serializes against ++ * page migration via class->lock. + */ + + #include +@@ -67,8 +71,8 @@ + #define MAX_POSSIBLE_PHYSMEM_BITS MAX_PHYSMEM_BITS + #else + /* +- * If this definition of MAX_PHYSMEM_BITS is used, OBJ_INDEX_BITS will just +- * be PAGE_SHIFT ++ * If this definition of MAX_PHYSMEM_BITS is used, ZS_OBJ_PFN_SHIFT will ++ * just be PAGE_SHIFT + */ + #define MAX_POSSIBLE_PHYSMEM_BITS BITS_PER_LONG + #endif +@@ -88,8 +92,23 @@ + #define OBJ_TAG_BITS 1 + #define OBJ_TAG_MASK OBJ_ALLOCATED_TAG + +-#define OBJ_INDEX_BITS (BITS_PER_LONG - _PFN_BITS) +-#define OBJ_INDEX_MASK ((_AC(1, UL) << OBJ_INDEX_BITS) - 1) ++/* ++ * obj is encoded as [PFN | class_idx | obj_idx] within an unsigned long: ++ * ++ * |<-- _PFN_BITS -->|<-- ZS_OBJ_CLASS_BITS -->|<-- ZS_OBJ_IDX_BITS -->| ++ * +-----------------+-------------------------+-----------------------+ ++ * | PFN | class_idx | obj_idx | ++ * +-----------------+-------------------------+-----------------------+ ++ * MSB ^ LSB ++ * | ++ * +-- ZS_OBJ_PFN_SHIFT ++ * ++ * Encoding class_idx into obj lets zs_free() locate the size_class ++ * without holding pool->lock; class_idx is invariant across page ++ * migration (only PFN changes), so a lockless read of the obj value ++ * always yields a valid class_idx. ++ */ ++#define ZS_OBJ_PFN_SHIFT (BITS_PER_LONG - _PFN_BITS) + + #define HUGE_BITS 1 + #define FULLNESS_BITS 4 +@@ -98,9 +117,61 @@ + + #define ZS_MAX_PAGES_PER_ZSPAGE (_AC(CONFIG_ZSMALLOC_CHAIN_SIZE, UL)) + ++/* ++ * Bits to index a page within a zspage = ceil(log2(ZS_MAX_PAGES_PER_ZSPAGE)). ++ * Computed at preprocessor time, for use in #if below. Kconfig ++ * restricts ZSMALLOC_CHAIN_SIZE to [4, 16]. ++ */ ++#if ZS_MAX_PAGES_PER_ZSPAGE <= 4 ++#define ZS_PAGES_PER_ZSPAGE_BITS 2 ++#elif ZS_MAX_PAGES_PER_ZSPAGE <= 8 ++#define ZS_PAGES_PER_ZSPAGE_BITS 3 ++#elif ZS_MAX_PAGES_PER_ZSPAGE <= 16 ++#define ZS_PAGES_PER_ZSPAGE_BITS 4 ++#else ++#error "ZSMALLOC_CHAIN_SIZE out of expected range [4,16]" ++#endif ++ ++/* ++ * Bits to index an object within a single PAGE_SIZE at the smallest ++ * possible object size: log2(PAGE_SIZE / 32) = PAGE_SHIFT - 5. ++ * 32 is the hard floor of ZS_MIN_ALLOC_SIZE. ++ */ ++#define ZS_OBJS_PER_PAGE_BITS (PAGE_SHIFT - 5) ++ ++/* ++ * Bits to index any object in the densest possible zspage. Below this, ++ * ZS_MIN_ALLOC_SIZE is auto-raised by the MAX(32, ...) formula -- still ++ * correct, but objects are coarser. ++ */ ++#define ZS_OBJS_PER_ZSPAGE_BITS \ ++ (ZS_PAGES_PER_ZSPAGE_BITS + ZS_OBJS_PER_PAGE_BITS) ++ ++/* ++ * Encode class_idx only when obj has spare bits; otherwise ++ * ZS_OBJ_CLASS_BITS folds to 0 (32-bit, or 64-bit UML/fallback). ++ */ ++#if BITS_PER_LONG >= 64 && \ ++ ZS_OBJ_PFN_SHIFT >= (CLASS_BITS + 1) + ZS_OBJS_PER_ZSPAGE_BITS ++#define ZS_OBJ_CLASS_BITS (CLASS_BITS + 1) ++#else ++#define ZS_OBJ_CLASS_BITS 0 ++#endif ++#define ZS_OBJ_CLASS_MASK ((_AC(1, UL) << ZS_OBJ_CLASS_BITS) - 1) ++ ++#define ZS_OBJ_IDX_BITS (ZS_OBJ_PFN_SHIFT - ZS_OBJ_CLASS_BITS) ++#define ZS_OBJ_IDX_MASK ((_AC(1, UL) << ZS_OBJ_IDX_BITS) - 1) ++ ++/* ++ * Belt-and-suspenders: the #if above already guarantees this when ++ * class_idx is enabled. Catches future tweaks that bypass it. ++ */ ++static_assert(ZS_OBJ_IDX_BITS >= ZS_PAGES_PER_ZSPAGE_BITS, ++ "zsmalloc: ZS_MIN_ALLOC_SIZE would exceed ZS_MAX_ALLOC_SIZE"); ++ + /* ZS_MIN_ALLOC_SIZE must be multiple of ZS_ALIGN */ + #define ZS_MIN_ALLOC_SIZE \ +- MAX(32, (ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT >> OBJ_INDEX_BITS)) ++ MAX(32, (ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT >> ZS_OBJ_IDX_BITS)) + /* each chunk includes extra space to keep handle */ + #define ZS_MAX_ALLOC_SIZE PAGE_SIZE + +@@ -396,10 +467,13 @@ static void cache_free_zspage(struct zspage *zspage) + kmem_cache_free(zspage_cachep, zspage); + } + +-/* class->lock(which owns the handle) synchronizes races */ ++/* ++ * Pairs with READ_ONCE() in handle_to_obj(): zs_free() may read the ++ * handle locklessly, so prevent store tearing here. ++ */ + static void record_obj(unsigned long handle, unsigned long obj) + { +- *(unsigned long *)handle = obj; ++ WRITE_ONCE(*(unsigned long *)handle, obj); + } + + static inline bool __maybe_unused is_first_zpdesc(struct zpdesc *zpdesc) +@@ -725,33 +799,36 @@ static struct zpdesc *get_next_zpdesc(struct zpdesc *zpdesc) + static void obj_to_location(unsigned long obj, struct zpdesc **zpdesc, + unsigned int *obj_idx) + { +- *zpdesc = pfn_zpdesc(obj >> OBJ_INDEX_BITS); +- *obj_idx = (obj & OBJ_INDEX_MASK); ++ *zpdesc = pfn_zpdesc(obj >> ZS_OBJ_PFN_SHIFT); ++ *obj_idx = (obj & ZS_OBJ_IDX_MASK); + } + + static void obj_to_zpdesc(unsigned long obj, struct zpdesc **zpdesc) + { +- *zpdesc = pfn_zpdesc(obj >> OBJ_INDEX_BITS); ++ *zpdesc = pfn_zpdesc(obj >> ZS_OBJ_PFN_SHIFT); + } + + /** +- * location_to_obj - get obj value encoded from (, ) ++ * location_to_obj - encode (, , ) into obj value + * @zpdesc: zpdesc object resides in zspage + * @obj_idx: object index ++ * @class_idx: size class index; ignored when ZS_OBJ_CLASS_BITS == 0 + */ +-static unsigned long location_to_obj(struct zpdesc *zpdesc, unsigned int obj_idx) ++static unsigned long location_to_obj(struct zpdesc *zpdesc, unsigned int obj_idx, ++ unsigned int class_idx) + { + unsigned long obj; + +- obj = zpdesc_pfn(zpdesc) << OBJ_INDEX_BITS; +- obj |= obj_idx & OBJ_INDEX_MASK; ++ obj = zpdesc_pfn(zpdesc) << ZS_OBJ_PFN_SHIFT; ++ obj |= (unsigned long)(class_idx & ZS_OBJ_CLASS_MASK) << ZS_OBJ_IDX_BITS; ++ obj |= obj_idx & ZS_OBJ_IDX_MASK; + + return obj; + } + + static unsigned long handle_to_obj(unsigned long handle) + { +- return *(unsigned long *)handle; ++ return READ_ONCE(*(unsigned long *)handle); + } + + static inline bool obj_allocated(struct zpdesc *zpdesc, void *obj, +@@ -805,13 +882,26 @@ static int trylock_zspage(struct zspage *zspage) + return 0; + } + +-static void __free_zspage(struct zs_pool *pool, struct size_class *class, +- struct zspage *zspage) ++/* ++ * Three free helpers, kept apart here: ++ * ++ * __free_zspage_lockless(): bare core; walks zpdescs and returns pages ++ * to the buddy allocator. Caller owns all zpdesc locks and has ++ * removed the zspage from its class list. Used by zs_free() outside ++ * class->lock so the buddy-side work does not stall the class. ++ * ++ * __free_zspage(): __free_zspage_lockless() + per-class accounting, ++ * under class->lock. Used by async_free_zspage(), the worker for ++ * zspages whose trylock_zspage() failed. ++ * ++ * free_zspage(): full wrapper - trylock zpdescs, remove from class ++ * list, call __free_zspage(); kicks deferred free on contention. ++ * Used by compaction. ++ */ ++static inline void __free_zspage_lockless(struct zspage *zspage) + { + struct zpdesc *zpdesc, *next; + +- assert_spin_locked(&class->lock); +- + VM_BUG_ON(get_zspage_inuse(zspage)); + VM_BUG_ON(zspage->fullness != ZS_INUSE_RATIO_0); + +@@ -827,7 +917,13 @@ static void __free_zspage(struct zs_pool *pool, struct size_class *class, + } while (zpdesc != NULL); + + cache_free_zspage(zspage); ++} + ++static void __free_zspage(struct zs_pool *pool, struct size_class *class, ++ struct zspage *zspage) ++{ ++ assert_spin_locked(&class->lock); ++ __free_zspage_lockless(zspage); + class_stat_sub(class, ZS_OBJS_ALLOCATED, class->objs_per_zspage); + atomic_long_sub(class->pages_per_zspage, &pool->pages_allocated); + } +@@ -1280,7 +1376,7 @@ static unsigned long obj_malloc(struct zs_pool *pool, + kunmap_local(vaddr); + mod_zspage_inuse(zspage, 1); + +- obj = location_to_obj(m_zpdesc, obj); ++ obj = location_to_obj(m_zpdesc, obj, zspage->class); + record_obj(handle, obj); + + return obj; +@@ -1383,37 +1479,97 @@ static void obj_free(int class_size, unsigned long obj) + mod_zspage_inuse(zspage, -1); + } + ++#if (ZS_OBJ_CLASS_BITS > 0) || defined(CONFIG_COMPACTION) ++/* Folds to 0 when ZS_OBJ_CLASS_BITS == 0; no ifdef needed at callers. */ ++static unsigned int obj_to_class_idx(unsigned long obj) ++{ ++ return (obj >> ZS_OBJ_IDX_BITS) & ZS_OBJ_CLASS_MASK; ++} ++#endif ++ ++/* ++ * Resolve @handle to its zspage / size_class and acquire class->lock. ++ * ++ * When class_idx is encoded in obj (ZS_OBJ_CLASS_BITS > 0), it is ++ * invariant under page migration, so the handle can be read locklessly ++ * to pick the size_class. Once class->lock is held migration is ++ * blocked and the handle is re-read to obtain a stable PFN. ++ * ++ * Otherwise (32-bit, or 64-bit fallback paths like UML where the ++ * encoding is disabled), fall back to pool->lock for the lookup. ++ */ ++#if ZS_OBJ_CLASS_BITS > 0 ++static inline void obj_class_get_and_lock(struct zs_pool *pool, unsigned long handle, ++ unsigned long *objp, struct zspage **zspagep, ++ struct size_class **classp) ++ __acquires(&(*classp)->lock) ++{ ++ struct zpdesc *f_zpdesc; ++ unsigned long obj; ++ ++ obj = handle_to_obj(handle); ++ *classp = pool->size_class[obj_to_class_idx(obj)]; ++ spin_lock(&(*classp)->lock); ++ /* Re-read under class->lock: PFN is now stable vs migration. */ ++ obj = handle_to_obj(handle); ++ obj_to_zpdesc(obj, &f_zpdesc); ++ *zspagep = get_zspage(f_zpdesc); ++ *objp = obj; ++} ++#else ++static inline void obj_class_get_and_lock(struct zs_pool *pool, unsigned long handle, ++ unsigned long *objp, struct zspage **zspagep, ++ struct size_class **classp) ++ __acquires(&(*classp)->lock) ++{ ++ struct zpdesc *f_zpdesc; ++ unsigned long obj; ++ ++ read_lock(&pool->lock); ++ obj = handle_to_obj(handle); ++ obj_to_zpdesc(obj, &f_zpdesc); ++ *zspagep = get_zspage(f_zpdesc); ++ *classp = zspage_class(pool, *zspagep); ++ spin_lock(&(*classp)->lock); ++ read_unlock(&pool->lock); ++ *objp = obj; ++} ++#endif ++ + void zs_free(struct zs_pool *pool, unsigned long handle) + { + struct zspage *zspage; +- struct zpdesc *f_zpdesc; + unsigned long obj; + struct size_class *class; + int fullness; ++ struct zspage *zspage_to_free = NULL; + + if (IS_ERR_OR_NULL((void *)handle)) + return; + +- /* +- * The pool->lock protects the race with zpage's migration +- * so it's safe to get the page from handle. +- */ +- read_lock(&pool->lock); +- obj = handle_to_obj(handle); +- obj_to_zpdesc(obj, &f_zpdesc); +- zspage = get_zspage(f_zpdesc); +- class = zspage_class(pool, zspage); +- spin_lock(&class->lock); +- read_unlock(&pool->lock); ++ obj_class_get_and_lock(pool, handle, &obj, &zspage, &class); + + class_stat_sub(class, ZS_OBJS_INUSE, 1); + obj_free(class->size, obj); + + fullness = fix_fullness_group(class, zspage); +- if (fullness == ZS_INUSE_RATIO_0) +- free_zspage(pool, class, zspage); ++ if (fullness == ZS_INUSE_RATIO_0) { ++ if (trylock_zspage(zspage)) { ++ remove_zspage(class, zspage); ++ class_stat_sub(class, ZS_OBJS_ALLOCATED, ++ class->objs_per_zspage); ++ zspage_to_free = zspage; ++ } else { ++ kick_deferred_free(pool); ++ } ++ } + + spin_unlock(&class->lock); ++ ++ if (zspage_to_free) { ++ __free_zspage_lockless(zspage_to_free); ++ atomic_long_sub(class->pages_per_zspage, &pool->pages_allocated); ++ } + cache_free_handle(handle); + } + EXPORT_SYMBOL_GPL(zs_free); +@@ -1646,9 +1802,6 @@ static void lock_zspage(struct zspage *zspage) + } + zspage_read_unlock(zspage); + } +-#endif /* CONFIG_COMPACTION */ +- +-#ifdef CONFIG_COMPACTION + + static void replace_sub_page(struct size_class *class, struct zspage *zspage, + struct zpdesc *newzpdesc, struct zpdesc *oldzpdesc) +@@ -1715,8 +1868,8 @@ static int zs_page_migrate(struct page *newpage, struct page *page, + pool = zspage->pool; + + /* +- * The pool migrate_lock protects the race between zpage migration +- * and zs_free. ++ * The pool migrate_lock protects against races between zpage migration ++ * and zs_free(), but only when ZS_OBJ_CLASS_BITS does not apply. + */ + write_lock(&pool->lock); + class = zspage_class(pool, zspage); +@@ -1764,7 +1917,8 @@ static int zs_page_migrate(struct page *newpage, struct page *page, + + old_obj = handle_to_obj(handle); + obj_to_location(old_obj, &dummy, &obj_idx); +- new_obj = (unsigned long)location_to_obj(newzpdesc, obj_idx); ++ new_obj = location_to_obj(newzpdesc, obj_idx, ++ obj_to_class_idx(old_obj)); + record_obj(handle, new_obj); + } + } +@@ -1775,9 +1929,9 @@ static int zs_page_migrate(struct page *newpage, struct page *page, + * Since we complete the data copy and set up new zspage structure, + * it's okay to release migration_lock. + */ +- write_unlock(&pool->lock); +- spin_unlock(&class->lock); + zspage_write_unlock(zspage); ++ spin_unlock(&class->lock); ++ write_unlock(&pool->lock); + + zpdesc_get(newzpdesc); + if (zpdesc_zone(newzpdesc) != zpdesc_zone(zpdesc)) { +@@ -1894,8 +2048,9 @@ static unsigned long __zs_compact(struct zs_pool *pool, + unsigned long pages_freed = 0; + + /* +- * protect the race between zpage migration and zs_free +- * as well as zpage allocation/free ++ * Protect against races between zpage migration and zs_free() ++ * (only when ZS_OBJ_CLASS_BITS does not apply), as well as ++ * zpage allocation and free. + */ + write_lock(&pool->lock); + spin_lock(&class->lock); diff --git a/pkgbuilds/linux-omarchy-bore/0260-mglru-exec-protect.patch b/pkgbuilds/linux-omarchy-bore/0260-mglru-exec-protect.patch new file mode 100644 index 0000000..f3b0e56 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0260-mglru-exec-protect.patch @@ -0,0 +1,313 @@ +diff --git a/include/linux/rmap.h b/include/linux/rmap.h +--- a/include/linux/rmap.h ++++ b/include/linux/rmap.h +@@ -843,7 +843,7 @@ static inline int folio_try_share_anon_rmap_pmd(struct folio *folio, + * Called from mm/vmscan.c to handle paging out + */ + int folio_referenced(struct folio *, int is_locked, +- struct mem_cgroup *memcg, vm_flags_t *vm_flags); ++ struct mem_cgroup *memcg, vma_flags_t *vma_flags); + + void try_to_migrate(struct folio *folio, enum ttu_flags flags); + void try_to_unmap(struct folio *, enum ttu_flags flags); +@@ -975,10 +975,9 @@ struct anon_vma *folio_lock_anon_vma_read(const struct folio *folio, + #define anon_vma_prepare(vma) (0) + + static inline int folio_referenced(struct folio *folio, int is_locked, +- struct mem_cgroup *memcg, +- vm_flags_t *vm_flags) ++ struct mem_cgroup *memcg, vma_flags_t *vma_flags) + { +- *vm_flags = 0; ++ vma_flags_clear_all(vma_flags); + return 0; + } + +diff --git a/mm/rmap.c b/mm/rmap.c +--- a/mm/rmap.c ++++ b/mm/rmap.c +@@ -907,7 +907,7 @@ pmd_t *mm_find_pmd(struct mm_struct *mm, unsigned long address) + struct folio_referenced_arg { + int mapcount; + int referenced; +- vm_flags_t vm_flags; ++ vma_flags_t vma_flags; + struct mem_cgroup *memcg; + }; + +@@ -926,7 +926,7 @@ static bool folio_referenced_one(struct folio *folio, + address = pvmw.address; + nr = 1; + +- if (vma->vm_flags & VM_LOCKED) { ++ if (vma_test(vma, VMA_LOCKED_BIT)) { + ptes++; + pra->mapcount--; + +@@ -947,7 +947,7 @@ static bool folio_referenced_one(struct folio *folio, + /* Restore the mlock which got missed */ + mlock_vma_folio(folio, vma); + page_vma_mapped_walk_done(&pvmw); +- pra->vm_flags |= VM_LOCKED; ++ vma_flags_set(&pra->vma_flags, VMA_LOCKED_BIT); + return false; /* To break the loop */ + } + +@@ -1015,8 +1015,11 @@ static bool folio_referenced_one(struct folio *folio, + referenced++; + + if (referenced) { ++ vma_flags_t vma_flags = vma->flags; ++ + pra->referenced++; +- pra->vm_flags |= vma->vm_flags & ~VM_LOCKED; ++ vma_flags_clear(&vma_flags, VMA_LOCKED_BIT); ++ vma_flags_set_mask(&pra->vma_flags, vma_flags); + } + + if (!pra->mapcount) +@@ -1054,7 +1057,7 @@ static bool invalid_folio_referenced_vma(struct vm_area_struct *vma, void *arg) + * @folio: The folio to test. + * @is_locked: Caller holds lock on the folio. + * @memcg: target memory cgroup +- * @vm_flags: A combination of all the vma->vm_flags which referenced the folio. ++ * @vma_flags: A combination of all the vma->flags which referenced the folio. + * + * Quick test_and_clear_referenced for all mappings of a folio, + * +@@ -1062,7 +1065,7 @@ static bool invalid_folio_referenced_vma(struct vm_area_struct *vma, void *arg) + * the function bailed out due to rmap lock contention. + */ + int folio_referenced(struct folio *folio, int is_locked, +- struct mem_cgroup *memcg, vm_flags_t *vm_flags) ++ struct mem_cgroup *memcg, vma_flags_t *vma_flags) + { + bool we_locked = false; + struct folio_referenced_arg pra = { +@@ -1078,7 +1081,7 @@ int folio_referenced(struct folio *folio, int is_locked, + }; + + VM_WARN_ON_ONCE_FOLIO(folio_is_zone_device(folio), folio); +- *vm_flags = 0; ++ vma_flags_clear_all(vma_flags); + if (!pra.mapcount) + return 0; + +@@ -1092,7 +1095,7 @@ int folio_referenced(struct folio *folio, int is_locked, + } + + rmap_walk(folio, &rwc); +- *vm_flags = pra.vm_flags; ++ vma_flags_set_mask(vma_flags, pra.vma_flags); + + if (we_locked) + folio_unlock(folio); +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -262,6 +262,12 @@ static bool writeback_throttling_sane(struct scan_control *sc) + } + #endif + ++static inline bool is_exec_file_folio(const struct folio *folio, ++ const vma_flags_t *vma_flags) ++{ ++ return vma_flags_test(vma_flags, VMA_EXEC_BIT) && folio_is_file_lru(folio); ++} ++ + static void set_task_reclaim_state(struct task_struct *task, + struct reclaim_state *rs) + { +@@ -830,10 +836,16 @@ enum folio_references { + * with PG_active set. In contrast, the aging (page table walk) path uses + * folio_update_gen(). + */ +-static bool lru_gen_set_refs(struct folio *folio) ++static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_flags) + { + /* see the comment on LRU_REFS_FLAGS */ + if (!folio_test_referenced(folio) && !folio_test_workingset(folio)) { ++ /* Activate file-backed executable folios after first usage. */ ++ if (is_exec_file_folio(folio, vma_flags)) { ++ set_mask_bits(&folio->flags.f, LRU_REFS_FLAGS, BIT(PG_workingset)); ++ return true; ++ } ++ + set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced)); + return false; + } +@@ -846,7 +858,7 @@ static bool lru_gen_set_refs(struct folio *folio) + return true; + } + #else +-static bool lru_gen_set_refs(struct folio *folio) ++static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_flags) + { + return false; + } +@@ -856,16 +868,16 @@ static enum folio_references folio_check_references(struct folio *folio, + struct scan_control *sc) + { + int referenced_ptes, referenced_folio; +- vm_flags_t vm_flags; ++ vma_flags_t vma_flags; + + referenced_ptes = folio_referenced(folio, 1, sc->target_mem_cgroup, +- &vm_flags); ++ &vma_flags); + + /* + * The supposedly reclaimable folio was found to be in a VM_LOCKED vma. + * Let the folio, now marked Mlocked, be moved to the unevictable list. + */ +- if (vm_flags & VM_LOCKED) ++ if (vma_flags_test(&vma_flags, VMA_LOCKED_BIT)) + return FOLIOREF_ACTIVATE; + + /* +@@ -881,7 +893,7 @@ static enum folio_references folio_check_references(struct folio *folio, + if (!referenced_ptes) + return FOLIOREF_RECLAIM; + +- return lru_gen_set_refs(folio) ? FOLIOREF_ACTIVATE : FOLIOREF_KEEP; ++ return lru_gen_set_refs(folio, &vma_flags) ? FOLIOREF_ACTIVATE : FOLIOREF_KEEP; + } + + referenced_folio = folio_test_clear_referenced(folio); +@@ -909,7 +921,7 @@ static enum folio_references folio_check_references(struct folio *folio, + /* + * Activate file-backed executable folios after first usage. + */ +- if ((vm_flags & VM_EXEC) && folio_is_file_lru(folio)) ++ if (is_exec_file_folio(folio, &vma_flags)) + return FOLIOREF_ACTIVATE; + + return FOLIOREF_KEEP; +@@ -2067,7 +2079,7 @@ static void shrink_active_list(unsigned long nr_to_scan, + { + unsigned long nr_taken; + unsigned long nr_scanned; +- vm_flags_t vm_flags; ++ vma_flags_t vma_flags; + LIST_HEAD(l_hold); /* The folios which were snipped off */ + LIST_HEAD(l_active); + LIST_HEAD(l_inactive); +@@ -2111,7 +2123,7 @@ static void shrink_active_list(unsigned long nr_to_scan, + + /* Referenced or rmap lock contention: rotate */ + if (folio_referenced(folio, 0, sc->target_mem_cgroup, +- &vm_flags) != 0) { ++ &vma_flags) != 0) { + /* + * Identify referenced, file-backed active folios and + * give them one more trip around the active list. So +@@ -2121,7 +2133,7 @@ static void shrink_active_list(unsigned long nr_to_scan, + * IO, plus JVM can create lots of anon VM_EXEC folios, + * so we ignore them here. + */ +- if ((vm_flags & VM_EXEC) && folio_is_file_lru(folio)) { ++ if (is_exec_file_folio(folio, &vma_flags)) { + nr_rotated += folio_nr_pages(folio); + list_add(&folio->lru, &l_active); + continue; +@@ -3190,14 +3202,19 @@ static bool positive_ctrl_err(struct ctrl_pos *sp, struct ctrl_pos *pv) + ******************************************************************************/ + + /* promote pages accessed through page tables */ +-static int folio_update_gen(struct folio *folio, int gen) ++static int folio_update_gen(struct folio *folio, int gen, const vma_flags_t *vma_flags) + { + unsigned long new_flags, old_flags = READ_ONCE(folio->flags.f); + + VM_WARN_ON_ONCE(gen >= MAX_NR_GENS); + +- /* see the comment on LRU_REFS_FLAGS */ +- if (!folio_test_referenced(folio) && !folio_test_workingset(folio)) { ++ /* ++ * See the comment on LRU_REFS_FLAGS, and activate file-backed ++ * executable folios after first usage to avoid typical IO ++ * thrashing from reclaiming. ++ */ ++ if (!folio_test_referenced(folio) && !folio_test_workingset(folio) && ++ !is_exec_file_folio(folio, vma_flags)) { + set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced)); + return -1; + } +@@ -3430,8 +3447,8 @@ static bool suitable_to_scan(int total, int young) + return young * n >= total; + } + +-static void walk_update_folio(struct lru_gen_mm_walk *walk, struct folio *folio, +- int new_gen, bool dirty) ++static void walk_update_folio(struct lru_gen_mm_walk *walk, struct vm_area_struct *vma, ++ struct folio *folio, int new_gen, bool dirty) + { + int old_gen; + +@@ -3444,10 +3461,10 @@ static void walk_update_folio(struct lru_gen_mm_walk *walk, struct folio *folio, + folio_mark_dirty(folio); + + if (walk) { +- old_gen = folio_update_gen(folio, new_gen); ++ old_gen = folio_update_gen(folio, new_gen, &vma->flags); + if (old_gen >= 0 && old_gen != new_gen) + update_batch_size(walk, folio, old_gen, new_gen); +- } else if (lru_gen_set_refs(folio)) { ++ } else if (lru_gen_set_refs(folio, &vma->flags)) { + old_gen = folio_lru_gen(folio); + if (old_gen >= 0 && old_gen != new_gen) + folio_activate(folio); +@@ -3520,7 +3537,7 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end, + continue; + + if (last != folio) { +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, args->vma, last, gen, dirty); + + last = folio; + dirty = false; +@@ -3533,7 +3550,7 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end, + walk->mm_stats[MM_LEAF_YOUNG] += nr; + } + +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, args->vma, last, gen, dirty); + last = NULL; + + if (i < PTRS_PER_PTE && get_next_vma(PMD_MASK, PAGE_SIZE, args, &start, &end)) +@@ -3611,7 +3628,7 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area + goto next; + + if (last != folio) { +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + last = folio; + dirty = false; +@@ -3625,7 +3642,7 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area + i = i > MIN_LRU_BATCH ? 0 : find_next_bit(bitmap, MIN_LRU_BATCH, i) + 1; + } while (i <= MIN_LRU_BATCH); + +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + lazy_mmu_mode_disable(); + spin_unlock(ptl); +@@ -4260,7 +4277,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr) + continue; + + if (last != folio) { +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + last = folio; + dirty = false; +@@ -4272,7 +4289,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr) + young += nr; + } + +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + lazy_mmu_mode_disable(); + diff --git a/pkgbuilds/linux-omarchy-bore/0270-ksm-rmap-walk.patch b/pkgbuilds/linux-omarchy-bore/0270-ksm-rmap-walk.patch new file mode 100644 index 0000000..afe8241 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0270-ksm-rmap-walk.patch @@ -0,0 +1,161 @@ +diff --git a/mm/ksm.c b/mm/ksm.c +--- a/mm/ksm.c ++++ b/mm/ksm.c +@@ -195,22 +195,28 @@ struct ksm_stable_node { + * @node: rb node of this rmap_item in the unstable tree + * @head: pointer to stable_node heading this list in the stable tree + * @hlist: link into hlist of rmap_items hanging off that stable_node +- * @age: number of scan iterations since creation +- * @remaining_skips: how many scans to skip ++ * @age: number of scan iterations since creation (unstable node) ++ * @remaining_skips: how many scans to skip (unstable node) ++ * @linear_page_index: the original page's index before merged by KSM (stable node) + */ + struct ksm_rmap_item { + struct ksm_rmap_item *rmap_list; + union { +- struct anon_vma *anon_vma; /* when stable */ ++ struct anon_vma *anon_vma; /* for reverse mapping, when stable */ + #ifdef CONFIG_NUMA + int nid; /* when node of unstable tree */ + #endif + }; + struct mm_struct *mm; + unsigned long address; /* + low bits used for flags below */ +- unsigned int oldchecksum; /* when unstable */ +- rmap_age_t age; +- rmap_age_t remaining_skips; ++ union { ++ struct { ++ unsigned int oldchecksum; ++ rmap_age_t age; ++ rmap_age_t remaining_skips; ++ }; /* when unstable */ ++ unsigned long linear_page_index; /* for reverse mapping, when stable */ ++ }; + union { + struct rb_node node; /* when node of unstable tree */ + struct { /* when listed from stable tree */ +@@ -776,6 +782,11 @@ static struct vm_area_struct *find_mergeable_vma(struct mm_struct *mm, + return vma; + } + ++/* ++ * break_cow: actively break COW, replacing the KSM page by a fresh anonymous ++ * page. This is called when rmap_item has not yet become stable, but page ++ * has been merged. ++ */ + static void break_cow(struct ksm_rmap_item *rmap_item) + { + struct mm_struct *mm = rmap_item->mm; +@@ -787,6 +798,11 @@ static void break_cow(struct ksm_rmap_item *rmap_item) + * to undo, we also need to drop a reference to the anon_vma. + */ + put_anon_vma(rmap_item->anon_vma); ++ /* ++ * Reset linear_page_index that might overlay age-related ++ * information. (it's still unstable node) ++ */ ++ rmap_item->linear_page_index = 0; + + mmap_read_lock(mm); + vma = find_mergeable_vma(mm, addr); +@@ -899,6 +915,8 @@ static void remove_node_from_stable_tree(struct ksm_stable_node *stable_node) + VM_BUG_ON(stable_node->rmap_hlist_len <= 0); + stable_node->rmap_hlist_len--; + put_anon_vma(rmap_item->anon_vma); ++ /* Reset linear_page_index that might overlay age-related information. */ ++ rmap_item->linear_page_index = 0; + rmap_item->address &= PAGE_MASK; + cond_resched(); + } +@@ -1052,6 +1070,8 @@ static void remove_rmap_item_from_tree(struct ksm_rmap_item *rmap_item) + stable_node->rmap_hlist_len--; + + put_anon_vma(rmap_item->anon_vma); ++ /* Reset linear_page_index that might overlay age-related information. */ ++ rmap_item->linear_page_index = 0; + rmap_item->head = NULL; + rmap_item->address &= PAGE_MASK; + +@@ -1598,8 +1618,15 @@ static int try_to_merge_with_ksm_page(struct ksm_rmap_item *rmap_item, + /* Unstable nid is in union with stable anon_vma: remove first */ + remove_rmap_item_from_tree(rmap_item); + +- /* Must get reference to anon_vma while still holding mmap_lock */ ++ /* ++ * We can consider the VMA only while still holding the mmap lock, ++ * so lock, so reference the anon_vma and calculate the linear ++ * page index early, before stable_tree_append(). If anything goes ++ * wrong that prevents the rmap_item from being added to the ++ * stable_tree, break_cow() will clean it up. ++ */ + rmap_item->anon_vma = vma->anon_vma; ++ rmap_item->linear_page_index = linear_page_index(vma, rmap_item->address); + get_anon_vma(vma->anon_vma); + out: + mmap_read_unlock(mm); +@@ -2458,6 +2485,13 @@ static bool should_skip_rmap_item(struct folio *folio, + if (folio_test_ksm(folio)) + return false; + ++ /* ++ * There is no age information in stable-tree nodes. We might end up ++ * here without a KSM page for example after COW. ++ */ ++ if (rmap_item->address & STABLE_FLAG) ++ return false; ++ + age = rmap_item->age; + if (age != U8_MAX) + rmap_item->age++; +@@ -3173,6 +3207,7 @@ void rmap_walk_ksm(struct folio *folio, struct rmap_walk_control *rwc) + hlist_for_each_entry(rmap_item, &stable_node->hlist, hlist) { + /* Ignore the stable/unstable/sqnr flags */ + const unsigned long addr = rmap_item->address & PAGE_MASK; ++ const unsigned long index = rmap_item->linear_page_index; + struct anon_vma *anon_vma = rmap_item->anon_vma; + struct anon_vma_chain *vmac; + struct vm_area_struct *vma; +@@ -3186,8 +3221,18 @@ void rmap_walk_ksm(struct folio *folio, struct rmap_walk_control *rwc) + anon_vma_lock_read(anon_vma); + } + ++ /* ++ * Currently, KSM folios are always small folios, so it's ++ * sufficient to search for a single page. We can simply use ++ * the linear_page_index of the original de-duplicate ++ * anonymous page that we remembered in the rmap_item while ++ * de-duplicating. Note that mremap() always de-duplicates KSM ++ * folios: so if there was mremap() in our parent or our child, ++ * we wouldn't have the KSM folio mapped in these processes ++ * anymore. ++ */ + anon_vma_interval_tree_foreach(vmac, &anon_vma->rb_root, +- 0, ULONG_MAX) { ++ index, index) { + + cond_resched(); + vma = vmac->vma; +@@ -3243,17 +3288,17 @@ void collect_procs_ksm(const struct folio *folio, const struct page *page, + rcu_read_lock(); + for_each_process(tsk) { + struct anon_vma_chain *vmac; +- unsigned long addr; ++ const unsigned long addr = rmap_item->address & PAGE_MASK; ++ const unsigned long index = rmap_item->linear_page_index; + struct task_struct *t = + task_early_kill(tsk, force_early); + if (!t) + continue; +- anon_vma_interval_tree_foreach(vmac, &av->rb_root, 0, +- ULONG_MAX) ++ anon_vma_interval_tree_foreach(vmac, &av->rb_root, index, ++ index) + { + vma = vmac->vma; + if (vma->vm_mm == t->mm) { +- addr = rmap_item->address & PAGE_MASK; + add_to_kill_ksm(t, page, vma, to_kill, + addr); + } diff --git a/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch b/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch new file mode 100644 index 0000000..46a53c5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch @@ -0,0 +1,1270 @@ +diff --git a/drivers/block/zram/backend_842.c b/drivers/block/zram/backend_842.c +--- a/drivers/block/zram/backend_842.c ++++ b/drivers/block/zram/backend_842.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "842: " fmt ++ + #include + #include + #include +@@ -13,6 +15,14 @@ static void release_params_842(struct zcomp_params *params) + + static int setup_params_842(struct zcomp_params *params) + { ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ if (params->level != ZCOMP_PARAM_NOT_SET) { ++ pr_err("compression level is not supported\n"); ++ return -EOPNOTSUPP; ++ } + return 0; + } + +diff --git a/drivers/block/zram/backend_deflate.c b/drivers/block/zram/backend_deflate.c +--- a/drivers/block/zram/backend_deflate.c ++++ b/drivers/block/zram/backend_deflate.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "deflate: " fmt ++ + #include + #include + #include +@@ -22,15 +24,26 @@ static void deflate_release_params(struct zcomp_params *params) + + static int deflate_setup_params(struct zcomp_params *params) + { +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = Z_DEFAULT_COMPRESSION; ++ } else if (params->level < Z_DEFAULT_COMPRESSION || ++ params->level > Z_BEST_COMPRESSION) { ++ pr_err("invalid compression level %d\n", params->level); ++ return -EINVAL; ++ } ++ + if (params->deflate.winbits == ZCOMP_PARAM_NOT_SET) { + params->deflate.winbits = DEFLATE_DEF_WINBITS; + } else { + s32 wb = params->deflate.winbits; + + if ((wb < -15 || wb > -9) && (wb < 9 || wb > 15)) { +- pr_err("invalid deflate winbits: %d\n", wb); ++ pr_err("invalid winbits %d\n", wb); + return -EINVAL; + } + } +diff --git a/drivers/block/zram/backend_lz4.c b/drivers/block/zram/backend_lz4.c +--- a/drivers/block/zram/backend_lz4.c ++++ b/drivers/block/zram/backend_lz4.c +@@ -1,3 +1,7 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#define pr_fmt(fmt) "lz4: " fmt ++ + #include + #include + #include +@@ -28,8 +32,12 @@ static int lz4_setup_params(struct zcomp_params *params) + LZ4_stream_t *dict_stream; + int ret; + +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = LZ4_ACCELERATION_DEFAULT; ++ } else if (params->level < LZ4_ACCELERATION_DEFAULT) { ++ pr_err("invalid compression level %d\n", params->level); ++ return -EINVAL; ++ } + + if (!params->dict || !params->dict_sz) + return 0; +diff --git a/drivers/block/zram/backend_lz4hc.c b/drivers/block/zram/backend_lz4hc.c +--- a/drivers/block/zram/backend_lz4hc.c ++++ b/drivers/block/zram/backend_lz4hc.c +@@ -1,3 +1,7 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#define pr_fmt(fmt) "lz4hc: " fmt ++ + #include + #include + #include +@@ -18,8 +22,18 @@ static void lz4hc_release_params(struct zcomp_params *params) + + static int lz4hc_setup_params(struct zcomp_params *params) + { +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = LZ4HC_DEFAULT_CLEVEL; ++ } else if (params->level < 1 || params->level > LZ4HC_MAX_CLEVEL) { ++ /* ++ * Use < 1 rather than < LZ4HC_MIN_CLEVEL here because ++ * LZ4HC_compress_generic() only clamps levels below 1 ++ * (levels 1 and 2 are valid). LZ4HC_MIN_CLEVEL (3) is ++ * advisory and not enforced by the library. ++ */ ++ pr_err("invalid compression level %d\n", params->level); ++ return -EINVAL; ++ } + + return 0; + } +diff --git a/drivers/block/zram/backend_lzo.c b/drivers/block/zram/backend_lzo.c +--- a/drivers/block/zram/backend_lzo.c ++++ b/drivers/block/zram/backend_lzo.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "lzo: " fmt ++ + #include + #include + #include +@@ -12,6 +14,14 @@ static void lzo_release_params(struct zcomp_params *params) + + static int lzo_setup_params(struct zcomp_params *params) + { ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ if (params->level != ZCOMP_PARAM_NOT_SET) { ++ pr_err("compression level is not supported\n"); ++ return -EOPNOTSUPP; ++ } + return 0; + } + +diff --git a/drivers/block/zram/backend_lzorle.c b/drivers/block/zram/backend_lzorle.c +--- a/drivers/block/zram/backend_lzorle.c ++++ b/drivers/block/zram/backend_lzorle.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "lzo-rle: " fmt ++ + #include + #include + #include +@@ -12,6 +14,14 @@ static void lzorle_release_params(struct zcomp_params *params) + + static int lzorle_setup_params(struct zcomp_params *params) + { ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ if (params->level != ZCOMP_PARAM_NOT_SET) { ++ pr_err("compression level is not supported\n"); ++ return -EOPNOTSUPP; ++ } + return 0; + } + +diff --git a/drivers/block/zram/backend_zstd.c b/drivers/block/zram/backend_zstd.c +--- a/drivers/block/zram/backend_zstd.c ++++ b/drivers/block/zram/backend_zstd.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "zstd: " fmt ++ + #include + #include + #include +@@ -58,8 +60,13 @@ static int zstd_setup_params(struct zcomp_params *params) + return -ENOMEM; + + params->drv_data = zp; +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = zstd_default_clevel(); ++ } else if (params->level < zstd_min_clevel() || ++ params->level > zstd_max_clevel()) { ++ pr_err("invalid compression level %d\n", params->level); ++ goto error; ++ } + + zp->cprm = zstd_get_params(params->level, PAGE_SIZE); + +@@ -85,7 +92,6 @@ static int zstd_setup_params(struct zcomp_params *params) + return 0; + + error: +- zstd_release_params(params); + return -EINVAL; + } + +@@ -161,7 +167,6 @@ static int zstd_create(struct zcomp_params *params, struct zcomp_ctx *ctx) + return 0; + + error: +- zstd_release_params(params); + zstd_destroy(ctx); + return -EINVAL; + } +diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c +--- a/drivers/block/zram/zram_drv.c ++++ b/drivers/block/zram/zram_drv.c +@@ -1664,6 +1664,17 @@ static void comp_algorithm_set(struct zram *zram, u32 prio, const char *alg) + zram->comp_algs[prio] = alg; + } + ++static void comp_params_reset(struct zram *zram, u32 prio) ++{ ++ struct zcomp_params *params = &zram->params[prio]; ++ ++ vfree(params->dict); ++ params->level = ZCOMP_PARAM_NOT_SET; ++ params->deflate.winbits = ZCOMP_PARAM_NOT_SET; ++ params->dict_sz = 0; ++ params->dict = NULL; ++} ++ + static int __comp_algorithm_store(struct zram *zram, u32 prio, const char *buf) + { + const char *alg; +@@ -1684,20 +1695,10 @@ static int __comp_algorithm_store(struct zram *zram, u32 prio, const char *buf) + } + + comp_algorithm_set(zram, prio, alg); ++ comp_params_reset(zram, prio); + return 0; + } + +-static void comp_params_reset(struct zram *zram, u32 prio) +-{ +- struct zcomp_params *params = &zram->params[prio]; +- +- vfree(params->dict); +- params->level = ZCOMP_PARAM_NOT_SET; +- params->deflate.winbits = ZCOMP_PARAM_NOT_SET; +- params->dict_sz = 0; +- params->dict = NULL; +-} +- + static int comp_params_store(struct zram *zram, u32 prio, s32 level, + const char *dict_path, + struct deflate_params *deflate_params) +@@ -1712,8 +1713,16 @@ static int comp_params_store(struct zram *zram, u32 prio, s32 level, + INT_MAX, + NULL, + READING_POLICY); +- if (sz < 0) ++ if (sz < 0) { ++ pr_err("failed to load dictionary %s (err=%zd)\n", ++ dict_path, sz); ++ return sz; ++ } ++ if (sz == 0) { ++ pr_err("failed to load dictionary %s (empty file)\n", ++ dict_path); + return -EINVAL; ++ } + } + + zram->params[prio].dict_sz = sz; +diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h +--- a/include/linux/memcontrol.h ++++ b/include/linux/memcontrol.h +@@ -270,10 +270,15 @@ struct mem_cgroup { + #endif + int kmemcg_id; + +- struct memcg_vmstats_percpu __percpu *vmstats_percpu; +- + #ifdef CONFIG_CGROUP_WRITEBACK + struct list_head cgwb_list; ++#endif ++ ++ /* Keep the hot per-CPU stats pointer away from memory event counters. */ ++ struct memcg_vmstats_percpu __percpu *vmstats_percpu ++ ____cacheline_aligned_in_smp; ++ ++#ifdef CONFIG_CGROUP_WRITEBACK + struct wb_domain cgwb_domain; + struct memcg_cgwb_frn cgwb_frn[MEMCG_CGWB_FRN_CNT]; + #endif +@@ -947,6 +952,8 @@ unsigned long memcg_page_state_output(struct mem_cgroup *memcg, int item); + bool memcg_stat_item_valid(int idx); + bool memcg_vm_event_item_valid(enum vm_event_item idx); + unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx); ++unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec, ++ enum node_stat_item idx); + unsigned long lruvec_page_state_local(struct lruvec *lruvec, + enum node_stat_item idx); + +@@ -1399,6 +1406,12 @@ static inline unsigned long lruvec_page_state(struct lruvec *lruvec, + return node_page_state(lruvec_pgdat(lruvec), idx); + } + ++static inline unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec, ++ enum node_stat_item idx) ++{ ++ return node_page_state_monotonic(lruvec_pgdat(lruvec), idx); ++} ++ + static inline unsigned long lruvec_page_state_local(struct lruvec *lruvec, + enum node_stat_item idx) + { +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h +@@ -323,6 +323,8 @@ enum node_stat_item { + PGSCAN_PROACTIVE, + PGSCAN_ANON, + PGSCAN_FILE, ++ PGROTATE_ANON, ++ PGROTATE_FILE, + PGREFILL, + #ifdef CONFIG_HUGETLB_PAGE + NR_HUGETLB, +@@ -755,6 +757,12 @@ void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, + + #endif /* CONFIG_LRU_GEN */ + ++struct lru_cost { ++ unsigned long count; ++ unsigned long last_rotated; ++ unsigned long last_io; ++}; ++ + struct lruvec { + struct list_head lists[NR_LRU_LISTS]; + /* per lruvec lru_lock for memcg */ +@@ -763,9 +771,12 @@ struct lruvec { + * These track the cost of reclaiming one LRU - file or anon - + * over the other. As the observed cost of reclaiming one LRU + * increases, the reclaim scan balance tips toward the other. ++ * Updated and decayed at prepare_scan_control() time; cost_lock ++ * serialises that update. + */ +- unsigned long anon_cost; +- unsigned long file_cost; ++ struct lru_cost cost[ANON_AND_FILE]; ++ /* Protects cost[]. */ ++ spinlock_t cost_lock; + /* Non-resident age, driven by LRU movement */ + atomic_long_t nonresident_age; + /* Refaults at the time of last reclaim cycle */ +diff --git a/include/linux/swap.h b/include/linux/swap.h +--- a/include/linux/swap.h ++++ b/include/linux/swap.h +@@ -309,9 +309,6 @@ extern unsigned long totalreserve_pages; + + + /* linux/mm/swap.c */ +-void lru_note_cost_unlock_irq(struct lruvec *lruvec, bool file, +- unsigned int nr_io, unsigned int nr_rotated); +-void lru_note_cost_refault(struct folio *); + void folio_add_lru(struct folio *); + void folio_add_lru_vma(struct folio *, struct vm_area_struct *); + void mark_page_accessed(struct page *); +diff --git a/include/linux/vmstat.h b/include/linux/vmstat.h +--- a/include/linux/vmstat.h ++++ b/include/linux/vmstat.h +@@ -20,7 +20,6 @@ struct reclaim_stat { + unsigned nr_congested; + unsigned nr_writeback; + unsigned nr_immediate; +- unsigned nr_pageout; + unsigned nr_activate[ANON_AND_FILE]; + unsigned nr_ref_keep; + unsigned nr_unmap_fail; +@@ -194,6 +193,19 @@ unsigned long global_node_page_state_pages(enum node_stat_item item) + return x; + } + ++/* ++ * Non-clamping variant of global_node_page_state() intended for callers that ++ * snapshot a monotonically-incremented counter and subtract two samples. ++ * Returns the raw wrapping value so that unsigned modular subtraction stays ++ * correct across a signed-long overflow (a real hazard on 32-bit) that the ++ * clamp in global_node_page_state() would otherwise turn into a huge spurious ++ * delta. Do NOT use for non-monotonic page-count reads. ++ */ ++static inline unsigned long global_node_page_state_monotonic(enum node_stat_item item) ++{ ++ return (unsigned long)atomic_long_read(&vm_node_stat[item]); ++} ++ + static inline unsigned long global_node_page_state(enum node_stat_item item) + { + VM_WARN_ON_ONCE(vmstat_item_in_bytes(item)); +@@ -259,11 +271,14 @@ extern unsigned long node_page_state(struct pglist_data *pgdat, + enum node_stat_item item); + extern unsigned long node_page_state_pages(struct pglist_data *pgdat, + enum node_stat_item item); ++extern unsigned long node_page_state_monotonic(struct pglist_data *pgdat, ++ enum node_stat_item item); + extern void fold_vm_numa_events(void); + #else + #define sum_zone_node_page_state(node, item) global_zone_page_state(item) + #define node_page_state(node, item) global_node_page_state(item) + #define node_page_state_pages(node, item) global_node_page_state_pages(item) ++#define node_page_state_monotonic(node, item) global_node_page_state_monotonic(item) + static inline void fold_vm_numa_events(void) + { + } +diff --git a/lib/maple_tree.c b/lib/maple_tree.c +--- a/lib/maple_tree.c ++++ b/lib/maple_tree.c +@@ -1501,14 +1501,26 @@ static inline void mas_parent_gap(struct ma_state *mas, unsigned char offset, + goto ascend; + } + ++static __always_inline void mas_update_gap_known(struct ma_state *mas, ++ unsigned long gap) ++{ ++ unsigned char pslot; ++ unsigned long p_gap; ++ ++ pslot = mte_parent_slot(mas->node); ++ p_gap = ma_gaps(mte_parent(mas->node), ++ mas_parent_type(mas, mas->node))[pslot]; ++ ++ if (p_gap != gap) ++ mas_parent_gap(mas, pslot, gap); ++} ++ + /* + * mas_update_gap() - Update a nodes gaps and propagate up if necessary. + * @mas: the maple state. + */ + static inline void mas_update_gap(struct ma_state *mas) + { +- unsigned char pslot; +- unsigned long p_gap; + unsigned long max_gap; + + if (!mt_is_alloc(mas->tree)) +@@ -1518,13 +1530,7 @@ static inline void mas_update_gap(struct ma_state *mas) + return; + + max_gap = mas_max_gap(mas); +- +- pslot = mte_parent_slot(mas->node); +- p_gap = ma_gaps(mte_parent(mas->node), +- mas_parent_type(mas, mas->node))[pslot]; +- +- if (p_gap != max_gap) +- mas_parent_gap(mas, pslot, max_gap); ++ mas_update_gap_known(mas, max_gap); + } + + /* +@@ -2081,8 +2087,8 @@ static inline void mas_wmb_replace(struct ma_state *mas, struct maple_copy *cp) + mas->node = mt_slot_locked(mas->tree, cp->slot, 0); + /* Insert the new data in the tree */ + mas_topiary_replace(mas, old_enode, cp->height); +- if (!mte_is_leaf(mas->node)) +- mas_update_gap(mas); ++ if (mt_is_alloc(mas->tree) && !mte_is_root(mas->node)) ++ mas_update_gap_known(mas, cp->gap[0]); + + mtree_range_walk(mas); + } +@@ -3125,7 +3131,7 @@ static void mas_wr_spanning_store(struct ma_wr_state *wr_mas) + static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + { + unsigned char dst_offset, offset_end; +- unsigned char copy_size, node_pivots; ++ unsigned char copy_size, node_pivots, node_slots; + struct maple_node reuse, *newnode; + unsigned long *dst_pivots; + void __rcu **dst_slots; +@@ -3138,6 +3144,7 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + in_rcu = mt_in_rcu(mas->tree); + offset_end = wr_mas->offset_end; + node_pivots = mt_pivots[wr_mas->type]; ++ node_slots = mt_slots[wr_mas->type]; + /* Assume last adds an entry */ + new_end = mas->end + 1 - offset_end + mas->offset; + if (mas->last == wr_mas->end_piv) { +@@ -3149,7 +3156,6 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + if (in_rcu) { + newnode = mas_pop_node(mas); + } else { +- memset(&reuse, 0, sizeof(struct maple_node)); + newnode = &reuse; + } + +@@ -3193,7 +3199,21 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + dst_pivots[new_end] = mas->max; + + done: +- mas_leaf_set_meta(newnode, maple_leaf_64, new_end); ++ if (!in_rcu && new_end + 2 < node_slots) { ++ unsigned char clear_from = new_end + 1; ++ ++ /* ++ * Note that the last slot is never cleared, since the metadata ++ * will be stored there or it has a value. ++ */ ++ memset(dst_slots + clear_from, 0, ++ sizeof(void __rcu *) * (node_slots - clear_from)); ++ if (clear_from < node_pivots) ++ memset(dst_pivots + clear_from, 0, ++ sizeof(unsigned long) * (node_pivots - clear_from)); ++ } ++ ++ mas_leaf_set_meta(newnode, wr_mas->type, new_end); + if (in_rcu) { + struct maple_enode *old_enode = mas->node; + +@@ -3218,7 +3238,7 @@ static inline void mas_wr_slot_store(struct ma_wr_state *wr_mas) + void __rcu **slots = wr_mas->slots; + bool gap = false; + +- gap |= !mt_slot_locked(mas->tree, slots, offset); ++ gap |= !wr_mas->content; + gap |= !mt_slot_locked(mas->tree, slots, offset + 1); + + if (wr_mas->offset_end - offset == 1) { +@@ -3661,6 +3681,9 @@ static inline enum store_type mas_wr_store_type(struct ma_wr_state *wr_mas) + { + struct ma_state *mas = wr_mas->mas; + unsigned char new_end; ++ bool appending; ++ bool one_slot; ++ bool in_rcu; + + if (unlikely(mas_is_none(mas) || mas_is_ptr(mas))) + return wr_store_root; +@@ -3680,21 +3703,30 @@ static inline enum store_type mas_wr_store_type(struct ma_wr_state *wr_mas) + return wr_new_root; + + new_end = mas_wr_new_end(wr_mas); ++ in_rcu = mt_in_rcu(mas->tree); ++ appending = mas->offset == mas->end; ++ one_slot = wr_mas->offset_end - mas->offset == 1; ++ + /* Potential spanning rebalance collapsing a node */ + if (new_end < mt_min_slots[wr_mas->type]) { + if (!mte_is_root(mas->node)) + return wr_rebalance; ++ if (!in_rcu) { ++ if (appending) ++ return wr_append; ++ else if (mas->end == new_end && one_slot) ++ return wr_slot_store; ++ } + return wr_node_store; + } + + if (new_end >= mt_slots[wr_mas->type]) + return wr_split_store; + +- if (!mt_in_rcu(mas->tree) && (mas->offset == mas->end)) ++ if (!in_rcu && appending) + return wr_append; + +- if ((new_end == mas->end) && (!mt_in_rcu(mas->tree) || +- (wr_mas->offset_end - mas->offset == 1))) ++ if (new_end == mas->end && (!in_rcu || one_slot)) + return wr_slot_store; + + return wr_node_store; +@@ -3793,35 +3825,40 @@ int mas_alloc_cyclic(struct ma_state *mas, unsigned long *startp, + void *entry, unsigned long range_lo, unsigned long range_hi, + unsigned long *next, gfp_t gfp) + { +- unsigned long min = range_lo; +- int ret = 0; ++ int ret; ++ unsigned long min; ++ ++ min = range_lo; ++ do { ++ range_lo = max(min, *next); ++ ret = mas_empty_area(mas, range_lo, range_hi, 1); ++ if (ret < 0 && range_lo > min) { ++ mas_reset(mas); ++ ret = mas_empty_area(mas, min, range_hi, 1); ++ if (ret == 0) ++ ret = 1; ++ } ++ if (ret < 0) ++ goto out; ++ ++ mas_insert(mas, entry); ++ } while (mas_nomem(mas, gfp)); ++ ++ if (mas_is_err(mas)) { ++ ret = xa_err(mas->node); ++ goto out; ++ } + +- range_lo = max(min, *next); +- ret = mas_empty_area(mas, range_lo, range_hi, 1); + if ((mas->tree->ma_flags & MT_FLAGS_ALLOC_WRAPPED) && ret == 0) { + mas->tree->ma_flags &= ~MT_FLAGS_ALLOC_WRAPPED; + ret = 1; + } +- if (ret < 0 && range_lo > min) { +- mas_reset(mas); +- ret = mas_empty_area(mas, min, range_hi, 1); +- if (ret == 0) +- ret = 1; +- } +- if (ret < 0) +- return ret; +- +- do { +- mas_insert(mas, entry); +- } while (mas_nomem(mas, gfp)); +- if (mas_is_err(mas)) +- return xa_err(mas->node); +- + *startp = mas->index; + *next = *startp + 1; + if (*next == 0) + mas->tree->ma_flags |= MT_FLAGS_ALLOC_WRAPPED; + ++out: + mas_destroy(mas); + return ret; + } +diff --git a/mm/khugepaged.c b/mm/khugepaged.c +--- a/mm/khugepaged.c ++++ b/mm/khugepaged.c +@@ -1442,10 +1442,10 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s + + result = SCAN_SUCCEED; + out_up_write: +- if (anon_vma_locked) +- anon_vma_unlock_write(vma->anon_vma); + if (pte) + pte_unmap(pte); ++ if (anon_vma_locked) ++ anon_vma_unlock_write(vma->anon_vma); + mmap_write_unlock(mm); + out_nolock: + if (folio) +diff --git a/mm/ksm.c b/mm/ksm.c +--- a/mm/ksm.c ++++ b/mm/ksm.c +@@ -3061,10 +3061,9 @@ int __ksm_enter(struct mm_struct *mm) + + slot = &mm_slot->slot; + ++ spin_lock(&ksm_mmlist_lock); + /* Check ksm_run too? Would need tighter locking */ + needs_wakeup = list_empty(&ksm_mm_head.slot.mm_node); +- +- spin_lock(&ksm_mmlist_lock); + mm_slot_insert(mm_slots_hash, mm, slot); + /* + * When KSM_RUN_MERGE (or KSM_RUN_STOP), +diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c +--- a/mm/memcontrol-v1.c ++++ b/mm/memcontrol-v1.c +@@ -1998,8 +1998,8 @@ void memcg1_stat_format(struct mem_cgroup *memcg, struct seq_buf *s) + for_each_online_pgdat(pgdat) { + mz = memcg->nodeinfo[pgdat->node_id]; + +- anon_cost += mz->lruvec.anon_cost; +- file_cost += mz->lruvec.file_cost; ++ anon_cost += mz->lruvec.cost[WORKINGSET_ANON].count; ++ file_cost += mz->lruvec.cost[WORKINGSET_FILE].count; + } + seq_buf_printf(s, "anon_cost %lu\n", anon_cost); + seq_buf_printf(s, "file_cost %lu\n", file_cost); +diff --git a/mm/memcontrol.c b/mm/memcontrol.c +--- a/mm/memcontrol.c ++++ b/mm/memcontrol.c +@@ -393,6 +393,7 @@ static const unsigned int memcg_node_stat_items[] = { + NR_SHMEM_THPS, + NR_FILE_THPS, + NR_ANON_THPS, ++ NR_VMSCAN_WRITE, + NR_VMALLOC, + NR_KERNEL_STACK_KB, + NR_PAGETABLE, +@@ -419,6 +420,8 @@ static const unsigned int memcg_node_stat_items[] = { + PGSCAN_PROACTIVE, + PGSCAN_ANON, + PGSCAN_FILE, ++ PGROTATE_ANON, ++ PGROTATE_FILE, + PGREFILL, + #ifdef CONFIG_HUGETLB_PAGE + NR_HUGETLB, +@@ -502,6 +505,42 @@ unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx) + return x; + } + ++/** ++ * lruvec_page_state_monotonic - non-clamping lruvec stat read for delta sampling ++ * @lruvec: the LRU vector to read from ++ * @idx: the node_stat_item to read ++ * ++ * Returns the raw state[idx] value cast to unsigned long, skipping the ++ * clamp-negative-to-zero step in lruvec_page_state(). Intended for callers ++ * that snapshot a monotonically-incremented counter and subtract two ++ * samples: unsigned modular arithmetic then yields the correct delta across ++ * a signed-long wraparound (a real hazard on 32-bit) that the clamp would ++ * otherwise turn into a huge spurious delta. ++ * ++ * Do NOT use for non-monotonic page-count reads where a transient negative ++ * reading from per-CPU delta skew must present as zero. ++ * ++ * XXX: This helper (and its node/global peers) exists because some ++ * monotonically-incremented event counters are stored in ++ * enum node_stat_item. ++ */ ++unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec, ++ enum node_stat_item idx) ++{ ++ struct mem_cgroup_per_node *pn; ++ int i; ++ ++ if (mem_cgroup_disabled()) ++ return node_page_state_monotonic(lruvec_pgdat(lruvec), idx); ++ ++ i = memcg_stats_index(idx); ++ if (WARN_ONCE(BAD_STAT_IDX(i), "%s: missing stat item %d\n", __func__, idx)) ++ return 0; ++ ++ pn = container_of(lruvec, struct mem_cgroup_per_node, lruvec); ++ return (unsigned long)READ_ONCE(pn->lruvec_stats->state[i]); ++} ++ + unsigned long lruvec_page_state_local(struct lruvec *lruvec, + enum node_stat_item idx) + { +@@ -2095,7 +2134,12 @@ static bool consume_stock(struct mem_cgroup *memcg, unsigned int nr_pages) + + stock_pages = READ_ONCE(stock->nr_pages[i]); + if (stock_pages >= nr_pages) { +- WRITE_ONCE(stock->nr_pages[i], stock_pages - nr_pages); ++ stock_pages -= nr_pages; ++ WRITE_ONCE(stock->nr_pages[i], stock_pages); ++ if (!stock_pages) { ++ css_put(&memcg->css); ++ WRITE_ONCE(stock->cached[i], NULL); ++ } + ret = true; + } + break; +diff --git a/mm/migrate_device.c b/mm/migrate_device.c +--- a/mm/migrate_device.c ++++ b/mm/migrate_device.c +@@ -872,7 +872,7 @@ static int migrate_vma_insert_huge_pmd_page(struct migrate_vma *migrate, + + if (flush) { + pte_free(vma->vm_mm, pgtable); +- flush_cache_page(vma, addr, addr + HPAGE_PMD_SIZE); ++ flush_cache_range(vma, addr, addr + HPAGE_PMD_SIZE); + pmdp_invalidate(vma, addr, pmdp); + } else { + pgtable_trans_huge_deposit(vma->vm_mm, pmdp, pgtable); +diff --git a/mm/mmzone.c b/mm/mmzone.c +--- a/mm/mmzone.c ++++ b/mm/mmzone.c +@@ -78,6 +78,7 @@ void lruvec_init(struct lruvec *lruvec) + + memset(lruvec, 0, sizeof(struct lruvec)); + spin_lock_init(&lruvec->lru_lock); ++ spin_lock_init(&lruvec->cost_lock); + zswap_lruvec_state_init(lruvec); + + for_each_lru(lru) +diff --git a/mm/mremap.c b/mm/mremap.c +--- a/mm/mremap.c ++++ b/mm/mremap.c +@@ -264,7 +264,7 @@ static int move_ptes(struct pagetable_move_control *pmc, + + for (; old_addr < old_end; old_ptep += nr_ptes, old_addr += nr_ptes * PAGE_SIZE, + new_ptep += nr_ptes, new_addr += nr_ptes * PAGE_SIZE) { +- VM_WARN_ON_ONCE(!pte_none(*new_ptep)); ++ VM_WARN_ON_ONCE(!pte_none(ptep_get(new_ptep))); + + nr_ptes = 1; + max_nr_ptes = (old_end - old_addr) >> PAGE_SHIFT; +diff --git a/mm/shmem.c b/mm/shmem.c +--- a/mm/shmem.c ++++ b/mm/shmem.c +@@ -3612,6 +3612,7 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset, + struct shmem_inode_info *info = SHMEM_I(inode); + struct shmem_falloc shmem_falloc; + pgoff_t start, index, end, undo_fallocend; ++ loff_t aligned_end; + int error; + + if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE)) +@@ -3668,8 +3669,15 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset, + goto out; + } + ++ /* Check for wraparound */ ++ if (check_add_overflow(offset + len, (loff_t)PAGE_SIZE - 1, ++ &aligned_end)) { ++ error = -EFBIG; ++ goto out; ++ } ++ + start = offset >> PAGE_SHIFT; +- end = (offset + len + PAGE_SIZE - 1) >> PAGE_SHIFT; ++ end = aligned_end >> PAGE_SHIFT; + /* Try to avoid a swapstorm if len is impossible to satisfy */ + if (sbinfo->max_blocks && end - start > sbinfo->max_blocks) { + error = -ENOSPC; +diff --git a/mm/swap.c b/mm/swap.c +--- a/mm/swap.c ++++ b/mm/swap.c +@@ -272,73 +272,6 @@ void folio_rotate_reclaimable(struct folio *folio) + folio_batch_add_and_move(folio, lru_move_tail); + } + +-void lru_note_cost_unlock_irq(struct lruvec *lruvec, bool file, +- unsigned int nr_io, unsigned int nr_rotated) +- __releases(lruvec->lru_lock) +- __releases(rcu) +-{ +- unsigned long cost; +- +- /* +- * Reflect the relative cost of incurring IO and spending CPU +- * time on rotations. This doesn't attempt to make a precise +- * comparison, it just says: if reloads are about comparable +- * between the LRU lists, or rotations are overwhelmingly +- * different between them, adjust scan balance for CPU work. +- */ +- cost = nr_io * SWAP_CLUSTER_MAX + nr_rotated; +- if (!cost) { +- spin_unlock_irq(&lruvec->lru_lock); +- rcu_read_unlock(); +- return; +- } +- +- for (;;) { +- unsigned long lrusize; +- +- /* Record cost event */ +- if (file) +- lruvec->file_cost += cost; +- else +- lruvec->anon_cost += cost; +- +- /* +- * Decay previous events +- * +- * Because workloads change over time (and to avoid +- * overflow) we keep these statistics as a floating +- * average, which ends up weighing recent refaults +- * more than old ones. +- */ +- lrusize = lruvec_page_state(lruvec, NR_INACTIVE_ANON) + +- lruvec_page_state(lruvec, NR_ACTIVE_ANON) + +- lruvec_page_state(lruvec, NR_INACTIVE_FILE) + +- lruvec_page_state(lruvec, NR_ACTIVE_FILE); +- +- if (lruvec->file_cost + lruvec->anon_cost > lrusize / 4) { +- lruvec->file_cost /= 2; +- lruvec->anon_cost /= 2; +- } +- +- spin_unlock_irq(&lruvec->lru_lock); +- lruvec = parent_lruvec(lruvec); +- if (!lruvec) { +- rcu_read_unlock(); +- break; +- } +- spin_lock_irq(&lruvec->lru_lock); +- } +-} +- +-void lru_note_cost_refault(struct folio *folio) +-{ +- struct lruvec *lruvec; +- +- lruvec = folio_lruvec_lock_irq(folio); +- lru_note_cost_unlock_irq(lruvec, folio_is_file_lru(folio), +- folio_nr_pages(folio), 0); +-} +- + static void lru_activate(struct lruvec *lruvec, struct folio *folio) + { + long nr_pages = folio_nr_pages(folio); +@@ -1153,7 +1086,16 @@ static void lruvec_reparent_lru(struct lruvec *child_lruvec, + for_each_managed_zone_pgdat(zone, NODE_DATA(nid), zid, MAX_NR_ZONES - 1) { + unsigned long size = mem_cgroup_get_zone_lru_size(child_lruvec, lru, zid); + ++ if (!size) ++ continue; ++ ++ /* ++ * The folios are accounted to the parent from now on, so the ++ * size has to be moved, not just copied. Leaving it behind ++ * makes the dying child describe folios it no longer owns. ++ */ + mem_cgroup_update_lru_size(parent_lruvec, lru, zid, size); ++ mem_cgroup_update_lru_size(child_lruvec, lru, zid, -(long)size); + } + } + +@@ -1164,8 +1106,6 @@ void lru_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, int + + child_lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(nid)); + parent_lruvec = mem_cgroup_lruvec(parent, NODE_DATA(nid)); +- parent_lruvec->anon_cost += child_lruvec->anon_cost; +- parent_lruvec->file_cost += child_lruvec->file_cost; + + for_each_lru(lru) + lruvec_reparent_lru(child_lruvec, parent_lruvec, lru, nid); +diff --git a/mm/vma.c b/mm/vma.c +--- a/mm/vma.c ++++ b/mm/vma.c +@@ -1866,10 +1866,10 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, + bool *need_rmap_locks) + { + struct vm_area_struct *vma = *vmap; +- unsigned long vma_start = vma->vm_start; ++ unsigned long old_vma_start = vma->vm_start; + struct mm_struct *mm = vma->vm_mm; + struct vm_area_struct *new_vma; +- bool faulted_in_anon_vma = true; ++ bool can_self_merge = false; + VMA_ITERATOR(vmi, mm, addr); + VMG_VMA_STATE(vmg, &vmi, NULL, vma, addr, addr + len); + +@@ -1879,7 +1879,7 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, + */ + if (unlikely(vma_is_anonymous(vma) && !vma->anon_vma)) { + pgoff = addr >> PAGE_SHIFT; +- faulted_in_anon_vma = false; ++ can_self_merge = true; + } + + /* +@@ -1899,24 +1899,21 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, + new_vma = vma_merge_copied_range(&vmg); + + if (new_vma) { +- /* +- * Source vma may have been merged into new_vma +- */ +- if (unlikely(vma_start >= new_vma->vm_start && +- vma_start < new_vma->vm_end)) { ++ /* Self-merged and VMA replaced. */ ++ if (unlikely(new_vma->vm_start < old_vma_start && ++ new_vma->vm_end > old_vma_start)) { + /* +- * The only way we can get a vma_merge with +- * self during an mremap is if the vma hasn't +- * been faulted in yet and we were allowed to +- * reset the dst vma->vm_pgoff to the +- * destination address of the mremap to allow +- * the merge to happen. mremap must change the +- * vm_pgoff linearity between src and dst vmas +- * (in turn preventing a vma_merge) to be +- * safe. It is only safe to keep the vm_pgoff +- * linear if there are no pages mapped yet. ++ * The only way a VMA can both self-merge and be ++ * replaced is if the remap places the new VMA ++ * immediately prior to its old self ('next') and ++ * immediately after another VMA ('prev') causing the ++ * next to be removed and prev to be expanded to cover ++ * the entire range. ++ * ++ * This should only be possible if the page offset was ++ * updated, i.e. the VMA is unfaulted. + */ +- VM_BUG_ON_VMA(faulted_in_anon_vma, new_vma); ++ VM_WARN_ON_ONCE_VMA(!can_self_merge, new_vma); + *vmap = vma = new_vma; + } + *need_rmap_locks = (new_vma->vm_pgoff <= vma->vm_pgoff); +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -669,7 +669,7 @@ static pageout_t pageout(struct folio *folio, struct address_space *mapping, + folio_clear_reclaim(folio); + + trace_mm_vmscan_write_folio(folio); +- node_stat_add_folio(folio, NR_VMSCAN_WRITE); ++ lruvec_stat_mod_folio(folio, NR_VMSCAN_WRITE, folio_nr_pages(folio)); + return PAGE_SUCCESS; + } + +@@ -1420,8 +1420,6 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, + sc->nr_scanned -= (nr_pages - 1); + nr_pages = 1; + } +- stat->nr_pageout += nr_pages; +- + if (folio_test_writeback(folio)) + goto keep; + if (folio_test_dirty(folio)) +@@ -2045,10 +2043,10 @@ static unsigned long shrink_inactive_list(unsigned long nr_to_scan, + item = PGSTEAL_KSWAPD + reclaimer_offset(sc); + mod_lruvec_state(lruvec, item, nr_reclaimed); + mod_lruvec_state(lruvec, PGSTEAL_ANON + file, nr_reclaimed); ++ if (nr_scanned > nr_reclaimed) ++ mod_lruvec_state(lruvec, PGROTATE_ANON + file, ++ nr_scanned - nr_reclaimed); + +- lruvec_lock_irq(lruvec); +- lru_note_cost_unlock_irq(lruvec, file, stat.nr_pageout, +- nr_scanned - nr_reclaimed); + handle_reclaim_writeback(nr_taken, pgdat, sc, &stat); + trace_mm_vmscan_lru_shrink_inactive(pgdat->node_id, + nr_scanned, nr_reclaimed, &stat, sc->priority, file); +@@ -2154,9 +2152,9 @@ static void shrink_active_list(unsigned long nr_to_scan, + count_vm_events(PGDEACTIVATE, nr_deactivate); + count_memcg_events(lruvec_memcg(lruvec), PGDEACTIVATE, nr_deactivate); + mod_node_page_state(pgdat, NR_ISOLATED_ANON + file, -nr_taken); ++ if (nr_rotated) ++ mod_lruvec_state(lruvec, PGROTATE_ANON + file, nr_rotated); + +- lruvec_lock_irq(lruvec); +- lru_note_cost_unlock_irq(lruvec, file, 0, nr_rotated); + trace_mm_vmscan_lru_shrink_active(pgdat->node_id, nr_taken, nr_activate, + nr_deactivate, nr_rotated, sc->priority, file); + } +@@ -2289,8 +2287,10 @@ enum scan_balance { + + static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc) + { +- unsigned long file; ++ struct lru_cost *anon_cost, *file_cost; + struct lruvec *target_lruvec; ++ unsigned long lrusize; ++ unsigned long file; + + if (lru_gen_enabled() && !lru_gen_switching()) + return; +@@ -2306,11 +2306,69 @@ static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc) + + /* + * Determine the scan balance between anon and file LRUs. ++ * ++ * The cost model is based on rotations, refaults and ++ * reclaim-driven writes (anon only) on each side. ++ * ++ * These event counters are monotonic, so each reclaim cycle ++ * the delta since the last scan is extracted and incorporated ++ * into a decaying average. This ensures currency, as workloads ++ * change over time, and avoids overflow in the calculations. ++ * ++ * Use lruvec_page_state_monotonic() so unsigned subtraction ++ * yields the correct delta across a signed-long wraparound of ++ * the underlying counter (a real hazard on 32-bit that the ++ * clamp in lruvec_page_state() would otherwise turn into a huge ++ * spurious delta). + */ +- spin_lock_irq(&target_lruvec->lru_lock); +- sc->anon_cost = target_lruvec->anon_cost; +- sc->file_cost = target_lruvec->file_cost; +- spin_unlock_irq(&target_lruvec->lru_lock); ++ spin_lock(&target_lruvec->cost_lock); ++ ++ for (int f = 0; f <= 1; f++) { ++ struct lru_cost *cost = &target_lruvec->cost[f]; ++ unsigned long rotated, io, nr_rotated, nr_io; ++ ++ rotated = lruvec_page_state_monotonic(target_lruvec, ++ PGROTATE_ANON + f); ++ io = lruvec_page_state_monotonic(target_lruvec, ++ WORKINGSET_RESTORE_BASE + f); ++ if (f == WORKINGSET_ANON) ++ io += lruvec_page_state_monotonic(target_lruvec, ++ NR_VMSCAN_WRITE); ++ ++ nr_rotated = rotated - cost->last_rotated; ++ nr_io = io - cost->last_io; ++ ++ /* ++ * Reflect the relative cost of incurring IO and spending ++ * CPU time on rotations. This doesn't attempt to make a ++ * precise comparison, it just says: if reloads are about ++ * comparable between the LRU lists, or rotations are ++ * overwhelmingly different between them, adjust scan ++ * balance for CPU work. ++ */ ++ cost->count += nr_io * SWAP_CLUSTER_MAX + nr_rotated; ++ ++ cost->last_rotated = rotated; ++ cost->last_io = io; ++ } ++ ++ anon_cost = &target_lruvec->cost[WORKINGSET_ANON]; ++ file_cost = &target_lruvec->cost[WORKINGSET_FILE]; ++ ++ lrusize = lruvec_page_state(target_lruvec, NR_INACTIVE_ANON) + ++ lruvec_page_state(target_lruvec, NR_ACTIVE_ANON) + ++ lruvec_page_state(target_lruvec, NR_INACTIVE_FILE) + ++ lruvec_page_state(target_lruvec, NR_ACTIVE_FILE); ++ ++ while (anon_cost->count + file_cost->count > lrusize / 4) { ++ anon_cost->count /= 2; ++ file_cost->count /= 2; ++ } ++ ++ sc->anon_cost = anon_cost->count; ++ sc->file_cost = file_cost->count; ++ ++ spin_unlock(&target_lruvec->cost_lock); + + /* + * Target desirable inactive:active list ratios for the anon +@@ -4197,7 +4255,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr) + unsigned long end; + struct lru_gen_mm_walk *walk; + struct folio *last = NULL; +- int young = 1; ++ int young = nr; + pte_t *pte = pvmw->pte; + unsigned long addr = pvmw->address; + struct vm_area_struct *vma = pvmw->vma; +@@ -4571,7 +4629,12 @@ void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, + for_each_managed_zone_pgdat(zone, NODE_DATA(nid), zid, MAX_NR_ZONES - 1) { + unsigned long size = mem_cgroup_get_zone_lru_size(child_lruvec, lru, zid); + ++ if (!size) ++ continue; ++ ++ /* Move the accounting, do not duplicate it. */ + mem_cgroup_update_lru_size(parent_lruvec, lru, zid, size); ++ mem_cgroup_update_lru_size(child_lruvec, lru, zid, -(long)size); + } + } + } +@@ -4814,7 +4877,8 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + struct reclaim_stat stat; + struct lru_gen_mm_walk *walk; + int scanned, reclaimed; +- int isolated = 0, type, type_scanned; ++ int isolated = 0, nr_isolated = 0, type, type_scanned; ++ unsigned long total_reclaimed = 0; + bool skip_retry = false; + struct mem_cgroup *memcg = lruvec_memcg(lruvec); + struct pglist_data *pgdat = lruvec_pgdat(lruvec); +@@ -4826,6 +4890,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + + scanned = isolate_folios(nr_to_scan, lruvec, sc, swappiness, + &list, &isolated, &type, &type_scanned); ++ nr_isolated = isolated; + + /* Scanning may have emptied the oldest gen, flush it */ + if (scanned) +@@ -4838,6 +4903,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + retry: + reclaimed = shrink_folio_list(&list, pgdat, sc, &stat, false, memcg); + sc->nr_reclaimed += reclaimed; ++ total_reclaimed += reclaimed; + /* Retry pass is only meant for clean folios without new isolation */ + if (isolated) + handle_reclaim_writeback(isolated, pgdat, sc, &stat); +@@ -4887,6 +4953,10 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + goto retry; + } + ++ if (nr_isolated > total_reclaimed) ++ mod_lruvec_state(lruvec, PGROTATE_ANON + type, ++ nr_isolated - total_reclaimed); ++ + return scanned; + } + +diff --git a/mm/vmstat.c b/mm/vmstat.c +--- a/mm/vmstat.c ++++ b/mm/vmstat.c +@@ -1024,6 +1024,17 @@ unsigned long node_page_state(struct pglist_data *pgdat, + + return node_page_state_pages(pgdat, item); + } ++ ++/* ++ * Non-clamping variant of node_page_state() intended for callers that ++ * snapshot a monotonically-incremented counter and subtract two samples. ++ * See global_node_page_state_monotonic() for the rationale. ++ */ ++unsigned long node_page_state_monotonic(struct pglist_data *pgdat, ++ enum node_stat_item item) ++{ ++ return (unsigned long)atomic_long_read(&pgdat->vm_stat[item]); ++} + #endif + + /* +@@ -1289,6 +1300,8 @@ const char * const vmstat_text[] = { + [I(PGSCAN_PROACTIVE)] = "pgscan_proactive", + [I(PGSCAN_ANON)] = "pgscan_anon", + [I(PGSCAN_FILE)] = "pgscan_file", ++ [I(PGROTATE_ANON)] = "pgrotate_anon", ++ [I(PGROTATE_FILE)] = "pgrotate_file", + [I(PGREFILL)] = "pgrefill", + #ifdef CONFIG_HUGETLB_PAGE + [I(NR_HUGETLB)] = "nr_hugetlb", +diff --git a/mm/workingset.c b/mm/workingset.c +--- a/mm/workingset.c ++++ b/mm/workingset.c +@@ -584,11 +584,6 @@ void workingset_refault(struct folio *folio, void *shadow) + /* Folio was active prior to eviction */ + if (workingset) { + folio_set_workingset(folio); +- /* +- * XXX: Move to folio_add_lru() when it supports new vs +- * putback +- */ +- lru_note_cost_refault(folio); + mod_lruvec_state(lruvec, WORKINGSET_RESTORE_BASE + file, nr); + } + out: +diff --git a/mm/zswap.c b/mm/zswap.c +--- a/mm/zswap.c ++++ b/mm/zswap.c +@@ -1217,7 +1217,7 @@ static unsigned long zswap_shrinker_count(struct shrinker *shrinker, + * Without memcg, use the zswap pool-wide metrics. + */ + if (!mem_cgroup_disabled()) { +- mem_cgroup_flush_stats(memcg); ++ mem_cgroup_flush_stats_ratelimited(memcg); + nr_backing = memcg_page_state(memcg, MEMCG_ZSWAP_B) >> PAGE_SHIFT; + nr_stored = memcg_page_state(memcg, MEMCG_ZSWAPPED); + } else { +@@ -1275,6 +1275,14 @@ static struct shrinker *zswap_alloc_shrinker(void) + return shrinker; + } + ++/* ++ * Scan up to SWAP_CLUSTER_MAX pages on each per-node zswap LRU of @memcg ++ * and write back the reclaimable ones. ++ * ++ * Return: 0 if at least one entry was written back, -EAGAIN if entries ++ * were scanned but none could be written back, or -ENOENT if @memcg has ++ * writeback disabled, is a zombie cgroup, or has empty zswap LRUs. ++ */ + static int shrink_memcg(struct mem_cgroup *memcg) + { + int nid, shrunk = 0, scanned = 0; +@@ -1290,13 +1298,14 @@ static int shrink_memcg(struct mem_cgroup *memcg) + return -ENOENT; + + for_each_node_state(nid, N_NORMAL_MEMORY) { +- unsigned long nr_to_walk = 1; ++ unsigned long nr_to_walk = SWAP_CLUSTER_MAX; + + shrunk += list_lru_walk_one(&zswap_list_lru, nid, memcg, + &shrink_memcg_cb, NULL, &nr_to_walk); +- scanned += 1 - nr_to_walk; ++ scanned += SWAP_CLUSTER_MAX - nr_to_walk; + } + ++ /* Nothing was scanned: every LRU under @memcg was empty. */ + if (!scanned) + return -ENOENT; + diff --git a/pkgbuilds/linux-omarchy-bore/0290-zstd-bmi2-fallback-aliases.patch b/pkgbuilds/linux-omarchy-bore/0290-zstd-bmi2-fallback-aliases.patch new file mode 100644 index 0000000..a14613c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0290-zstd-bmi2-fallback-aliases.patch @@ -0,0 +1,97 @@ +diff --git a/lib/zstd/common/entropy_common.c b/lib/zstd/common/entropy_common.c +--- a/lib/zstd/common/entropy_common.c ++++ b/lib/zstd/common/entropy_common.c +@@ -202,6 +202,8 @@ BMI2_TARGET_ATTRIBUTE static size_t FSE_readNCount_body_bmi2( + { + return FSE_readNCount_body(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize); + } ++#else ++#define FSE_readNCount_body_bmi2 FSE_readNCount_body_default + #endif + + size_t FSE_readNCount_bmi2( +@@ -323,6 +325,8 @@ static BMI2_TARGET_ATTRIBUTE size_t HUF_readStats_body_bmi2(BYTE* huffWeight, si + { + return HUF_readStats_body(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize, 1); + } ++#else ++#define HUF_readStats_body_bmi2 HUF_readStats_body_default + #endif + + size_t HUF_readStats_wksp(BYTE* huffWeight, size_t hwSize, U32* rankStats, +diff --git a/lib/zstd/common/fse_decompress.c b/lib/zstd/common/fse_decompress.c +--- a/lib/zstd/common/fse_decompress.c ++++ b/lib/zstd/common/fse_decompress.c +@@ -300,6 +300,8 @@ BMI2_TARGET_ATTRIBUTE static size_t FSE_decompress_wksp_body_bmi2(void* dst, siz + { + return FSE_decompress_wksp_body(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize, 1); + } ++#else ++#define FSE_decompress_wksp_body_bmi2 FSE_decompress_wksp_body_default + #endif + + size_t FSE_decompress_wksp_bmi2(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize, int bmi2) +diff --git a/lib/zstd/compress/zstd_compress_sequences.c b/lib/zstd/compress/zstd_compress_sequences.c +--- a/lib/zstd/compress/zstd_compress_sequences.c ++++ b/lib/zstd/compress/zstd_compress_sequences.c +@@ -415,6 +415,10 @@ ZSTD_encodeSequences_bmi2( + sequences, nbSeq, longOffsets); + } + ++#else ++ ++#define ZSTD_encodeSequences_bmi2 ZSTD_encodeSequences_default ++ + #endif + + size_t ZSTD_encodeSequences( +diff --git a/lib/zstd/decompress/huf_decompress.c b/lib/zstd/decompress/huf_decompress.c +--- a/lib/zstd/decompress/huf_decompress.c ++++ b/lib/zstd/decompress/huf_decompress.c +@@ -700,6 +700,8 @@ size_t HUF_decompress4X1_usingDTable_internal_bmi2(void* dst, size_t dstSize, vo + size_t cSrcSize, HUF_DTable const* DTable) { + return HUF_decompress4X1_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable); + } ++#else ++#define HUF_decompress4X1_usingDTable_internal_bmi2 HUF_decompress4X1_usingDTable_internal_default + #endif + + static +@@ -1503,6 +1505,8 @@ size_t HUF_decompress4X2_usingDTable_internal_bmi2(void* dst, size_t dstSize, vo + size_t cSrcSize, HUF_DTable const* DTable) { + return HUF_decompress4X2_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable); + } ++#else ++#define HUF_decompress4X2_usingDTable_internal_bmi2 HUF_decompress4X2_usingDTable_internal_default + #endif + + static +diff --git a/lib/zstd/decompress/zstd_decompress_block.c b/lib/zstd/decompress/zstd_decompress_block.c +--- a/lib/zstd/decompress/zstd_decompress_block.c ++++ b/lib/zstd/decompress/zstd_decompress_block.c +@@ -622,6 +622,8 @@ BMI2_TARGET_ATTRIBUTE static void ZSTD_buildFSETable_body_bmi2(ZSTD_seqSymbol* d + ZSTD_buildFSETable_body(dt, normalizedCounter, maxSymbolValue, + baseValue, nbAdditionalBits, tableLog, wksp, wkspSize); + } ++#else ++#define ZSTD_buildFSETable_body_bmi2 ZSTD_buildFSETable_body_default + #endif + + void ZSTD_buildFSETable(ZSTD_seqSymbol* dt, +@@ -1934,6 +1936,16 @@ ZSTD_decompressSequencesLong_bmi2(ZSTD_DCtx* dctx, + } + #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */ + ++#else ++ ++#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG ++#define ZSTD_decompressSequences_bmi2 ZSTD_decompressSequences_default ++#define ZSTD_decompressSequencesSplitLitBuffer_bmi2 ZSTD_decompressSequencesSplitLitBuffer_default ++#endif ++#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT ++#define ZSTD_decompressSequencesLong_bmi2 ZSTD_decompressSequencesLong_default ++#endif ++ + #endif /* DYNAMIC_BMI2 */ + + #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG diff --git a/pkgbuilds/linux-omarchy-bore/0291-zstd-bmi2-cpu-feature-dispatch.patch b/pkgbuilds/linux-omarchy-bore/0291-zstd-bmi2-cpu-feature-dispatch.patch new file mode 100644 index 0000000..9f0b872 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0291-zstd-bmi2-cpu-feature-dispatch.patch @@ -0,0 +1,413 @@ +diff --git a/lib/zstd/common/compiler.h b/lib/zstd/common/compiler.h +--- a/lib/zstd/common/compiler.h ++++ b/lib/zstd/common/compiler.h +@@ -14,6 +14,7 @@ + + #include + ++#include "zstd_deps.h" + #include "portability_macros.h" + + /*-******************************************************* +@@ -96,6 +97,17 @@ + */ + #define BMI2_TARGET_ATTRIBUTE TARGET_ATTRIBUTE("lzcnt,bmi,bmi2") + ++#if !DYNAMIC_BMI2 ++# define ZSTD_USE_BMI2(bmi2) 0 ++# define ZSTD_SET_BMI2(state, value) do { } while (0) ++#elif defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++# define ZSTD_USE_BMI2(bmi2) cpu_feature_enabled(X86_FEATURE_BMI2) ++# define ZSTD_SET_BMI2(state, value) do { } while (0) ++#else ++# define ZSTD_USE_BMI2(bmi2) (bmi2) ++# define ZSTD_SET_BMI2(state, value) do { (state) = (value); } while (0) ++#endif ++ + /* prefetch + * can be disabled, by declaring NO_PREFETCH build macro */ + #if ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) ) +diff --git a/lib/zstd/common/entropy_common.c b/lib/zstd/common/entropy_common.c +--- a/lib/zstd/common/entropy_common.c ++++ b/lib/zstd/common/entropy_common.c +@@ -16,6 +16,10 @@ + /* ************************************* + * Dependencies + ***************************************/ ++#include "zstd_deps.h" ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "mem.h" + #include "error_private.h" /* ERR_*, ERROR */ + #define FSE_STATIC_LINKING_ONLY /* FSE_MIN_TABLELOG */ +@@ -210,11 +214,9 @@ size_t FSE_readNCount_bmi2( + short* normalizedCounter, unsigned* maxSVPtr, unsigned* tableLogPtr, + const void* headerBuffer, size_t hbSize, int bmi2) + { +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + return FSE_readNCount_body_bmi2(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize); + } +-#endif + (void)bmi2; + return FSE_readNCount_body_default(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize); + } +@@ -335,11 +337,9 @@ size_t HUF_readStats_wksp(BYTE* huffWeight, size_t hwSize, U32* rankStats, + void* workSpace, size_t wkspSize, + int flags) + { +-#if DYNAMIC_BMI2 +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + return HUF_readStats_body_bmi2(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize); + } +-#endif + (void)flags; + return HUF_readStats_body_default(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize); + } +diff --git a/lib/zstd/common/fse_decompress.c b/lib/zstd/common/fse_decompress.c +--- a/lib/zstd/common/fse_decompress.c ++++ b/lib/zstd/common/fse_decompress.c +@@ -24,6 +24,9 @@ + #include "fse.h" + #include "error_private.h" + #include "zstd_deps.h" /* ZSTD_memcpy */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "bits.h" /* ZSTD_highbit32 */ + + +@@ -306,11 +309,9 @@ BMI2_TARGET_ATTRIBUTE static size_t FSE_decompress_wksp_body_bmi2(void* dst, siz + + size_t FSE_decompress_wksp_bmi2(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize, int bmi2) + { +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + return FSE_decompress_wksp_body_bmi2(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize); + } +-#endif + (void)bmi2; + return FSE_decompress_wksp_body_default(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize); + } +diff --git a/lib/zstd/common/zstd_deps.h b/lib/zstd/common/zstd_deps.h +--- a/lib/zstd/common/zstd_deps.h ++++ b/lib/zstd/common/zstd_deps.h +@@ -26,6 +26,11 @@ + #ifndef ZSTD_DEPS_COMMON + #define ZSTD_DEPS_COMMON + ++#if defined(__KERNEL__) && defined(CONFIG_X86) && \ ++ !defined(__DISABLE_EXPORTS) ++#define ZSTD_USE_KERNEL_CPU_FEATURES ++#endif ++ + #include + #include + +diff --git a/lib/zstd/compress/huf_compress.c b/lib/zstd/compress/huf_compress.c +--- a/lib/zstd/compress/huf_compress.c ++++ b/lib/zstd/compress/huf_compress.c +@@ -22,6 +22,9 @@ + * Includes + ****************************************************************/ + #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memset */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "../common/compiler.h" + #include "../common/bitstream.h" + #include "hist.h" +@@ -1138,9 +1141,10 @@ HUF_compress1X_usingCTable_internal(void* dst, size_t dstSize, + const void* src, size_t srcSize, + const HUF_CElt* CTable, const int flags) + { +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + return HUF_compress1X_usingCTable_internal_bmi2(dst, dstSize, src, srcSize, CTable); + } ++ (void)flags; + return HUF_compress1X_usingCTable_internal_default(dst, dstSize, src, srcSize, CTable); + } + +diff --git a/lib/zstd/compress/zstd_compress.c b/lib/zstd/compress/zstd_compress.c +--- a/lib/zstd/compress/zstd_compress.c ++++ b/lib/zstd/compress/zstd_compress.c +@@ -102,7 +102,7 @@ static void ZSTD_initCCtx(ZSTD_CCtx* cctx, ZSTD_customMem memManager) + assert(cctx != NULL); + ZSTD_memset(cctx, 0, sizeof(*cctx)); + cctx->customMem = memManager; +- cctx->bmi2 = ZSTD_cpuSupportsBmi2(); ++ ZSTD_SET_BMI2(cctx->bmi2, ZSTD_cpuSupportsBmi2()); + { size_t const err = ZSTD_CCtx_reset(cctx, ZSTD_reset_parameters); + assert(!ZSTD_isError(err)); + (void)err; +@@ -142,7 +142,7 @@ ZSTD_CCtx* ZSTD_initStaticCCtx(void* workspace, size_t workspaceSize) + cctx->blockState.nextCBlock = (ZSTD_compressedBlockState_t*)ZSTD_cwksp_reserve_object(&cctx->workspace, sizeof(ZSTD_compressedBlockState_t)); + cctx->tmpWorkspace = ZSTD_cwksp_reserve_object(&cctx->workspace, TMP_WORKSPACE_SIZE); + cctx->tmpWkspSize = TMP_WORKSPACE_SIZE; +- cctx->bmi2 = ZSTD_cpuid_bmi2(ZSTD_cpuid()); ++ ZSTD_SET_BMI2(cctx->bmi2, ZSTD_cpuid_bmi2(ZSTD_cpuid())); + return cctx; + } + +@@ -4042,7 +4042,7 @@ ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx* zc, + op + ZSTD_blockHeaderSize, dstCapacity - ZSTD_blockHeaderSize, + srcSize, + zc->tmpWorkspace, zc->tmpWkspSize /* statically allocated in resetCCtx */, +- zc->bmi2); ++ ZSTD_CCtx_get_bmi2(zc)); + FORWARD_IF_ERROR(cSeqsSize, "ZSTD_entropyCompressSeqStore failed!"); + + if (!zc->isFirstBlock && +@@ -4332,7 +4332,7 @@ ZSTD_compressBlock_internal(ZSTD_CCtx* zc, + dst, dstCapacity, + srcSize, + zc->tmpWorkspace, zc->tmpWkspSize /* statically allocated in resetCCtx */, +- zc->bmi2); ++ ZSTD_CCtx_get_bmi2(zc)); + + if (frame && + /* We don't want to emit our first block as a RLE even if it qualifies because +@@ -6796,7 +6796,7 @@ ZSTD_compressSequences_internal(ZSTD_CCtx* cctx, + op + ZSTD_blockHeaderSize /* Leave space for block header */, dstCapacity - ZSTD_blockHeaderSize, + blockSize, + cctx->tmpWorkspace, cctx->tmpWkspSize /* statically allocated in resetCCtx */, +- cctx->bmi2); ++ ZSTD_CCtx_get_bmi2(cctx)); + FORWARD_IF_ERROR(compressedSeqsSize, "Compressing sequences of block failed"); + DEBUGLOG(5, "Compressed sequences size: %zu", compressedSeqsSize); + +@@ -7321,7 +7321,7 @@ ZSTD_compressSequencesAndLiterals_internal(ZSTD_CCtx* cctx, + &cctx->blockState.prevCBlock->entropy, &cctx->blockState.nextCBlock->entropy, + &cctx->appliedParams, + cctx->tmpWorkspace, cctx->tmpWkspSize /* statically allocated in resetCCtx */, +- cctx->bmi2); ++ ZSTD_CCtx_get_bmi2(cctx)); + FORWARD_IF_ERROR(compressedSeqsSize, "Compressing sequences of block failed"); + /* note: the spec forbids for any compressed block to be larger than maximum block size */ + if (compressedSeqsSize > cctx->blockSizeMax) compressedSeqsSize = 0; +diff --git a/lib/zstd/compress/zstd_compress_internal.h b/lib/zstd/compress/zstd_compress_internal.h +--- a/lib/zstd/compress/zstd_compress_internal.h ++++ b/lib/zstd/compress/zstd_compress_internal.h +@@ -537,6 +537,15 @@ struct ZSTD_CCtx_s { + size_t extSeqBufCapacity; + }; + ++MEM_STATIC int ZSTD_CCtx_get_bmi2(const struct ZSTD_CCtx_s *cctx) { ++#if DYNAMIC_BMI2 && !defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++ return cctx->bmi2; ++#else ++ (void)cctx; ++ return 0; ++#endif ++} ++ + typedef enum { ZSTD_dtlm_fast, ZSTD_dtlm_full } ZSTD_dictTableLoadMethod_e; + typedef enum { ZSTD_tfp_forCCtx, ZSTD_tfp_forCDict } ZSTD_tableFillPurpose_e; + +diff --git a/lib/zstd/compress/zstd_compress_sequences.c b/lib/zstd/compress/zstd_compress_sequences.c +--- a/lib/zstd/compress/zstd_compress_sequences.c ++++ b/lib/zstd/compress/zstd_compress_sequences.c +@@ -12,6 +12,10 @@ + /*-************************************* + * Dependencies + ***************************************/ ++#include "../common/zstd_deps.h" ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "zstd_compress_sequences.h" + + /* +@@ -429,15 +433,13 @@ size_t ZSTD_encodeSequences( + SeqDef const* sequences, size_t nbSeq, int longOffsets, int bmi2) + { + DEBUGLOG(5, "ZSTD_encodeSequences: dstCapacity = %u", (unsigned)dstCapacity); +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + return ZSTD_encodeSequences_bmi2(dst, dstCapacity, + CTable_MatchLength, mlCodeTable, + CTable_OffsetBits, ofCodeTable, + CTable_LitLength, llCodeTable, + sequences, nbSeq, longOffsets); + } +-#endif + (void)bmi2; + return ZSTD_encodeSequences_default(dst, dstCapacity, + CTable_MatchLength, mlCodeTable, +diff --git a/lib/zstd/compress/zstd_compress_superblock.c b/lib/zstd/compress/zstd_compress_superblock.c +--- a/lib/zstd/compress/zstd_compress_superblock.c ++++ b/lib/zstd/compress/zstd_compress_superblock.c +@@ -684,6 +684,6 @@ size_t ZSTD_compressSuperBlock(ZSTD_CCtx* zc, + &zc->appliedParams, + dst, dstCapacity, + src, srcSize, +- zc->bmi2, lastBlock, ++ ZSTD_CCtx_get_bmi2(zc), lastBlock, + zc->tmpWorkspace, zc->tmpWkspSize /* statically allocated in resetCCtx */); + } +diff --git a/lib/zstd/decompress/huf_decompress.c b/lib/zstd/decompress/huf_decompress.c +--- a/lib/zstd/decompress/huf_decompress.c ++++ b/lib/zstd/decompress/huf_decompress.c +@@ -17,6 +17,9 @@ + * Dependencies + ****************************************************************/ + #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memset */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "../common/compiler.h" + #include "../common/bitstream.h" /* BIT_* */ + #include "../common/fse.h" /* to compress headers */ +@@ -113,9 +116,10 @@ typedef size_t (*HUF_DecompressUsingDTableFn)(void *dst, size_t dstSize, + static size_t fn(void* dst, size_t dstSize, void const* cSrc, \ + size_t cSrcSize, HUF_DTable const* DTable, int flags) \ + { \ +- if (flags & HUF_flags_bmi2) { \ ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { \ + return fn##_bmi2(dst, dstSize, cSrc, cSrcSize, DTable); \ + } \ ++ (void)flags; \ + return fn##_default(dst, dstSize, cSrc, cSrcSize, DTable); \ + } + +@@ -899,18 +903,16 @@ static size_t HUF_decompress4X1_usingDTable_internal(void* dst, size_t dstSize, + HUF_DecompressUsingDTableFn fallbackFn = HUF_decompress4X1_usingDTable_internal_default; + HUF_DecompressFastLoopFn loopFn = HUF_decompress4X1_usingDTable_internal_fast_c_loop; + +-#if DYNAMIC_BMI2 +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + fallbackFn = HUF_decompress4X1_usingDTable_internal_bmi2; + # if ZSTD_ENABLE_ASM_X86_64_BMI2 + if (!(flags & HUF_flags_disableAsm)) { + loopFn = HUF_decompress4X1_usingDTable_internal_fast_asm_loop; + } + # endif +- } else { ++ } else if (DYNAMIC_BMI2) { + return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); + } +-#endif + + #if ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__) + if (!(flags & HUF_flags_disableAsm)) { +@@ -1723,18 +1725,16 @@ static size_t HUF_decompress4X2_usingDTable_internal(void* dst, size_t dstSize, + HUF_DecompressUsingDTableFn fallbackFn = HUF_decompress4X2_usingDTable_internal_default; + HUF_DecompressFastLoopFn loopFn = HUF_decompress4X2_usingDTable_internal_fast_c_loop; + +-#if DYNAMIC_BMI2 +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + fallbackFn = HUF_decompress4X2_usingDTable_internal_bmi2; + # if ZSTD_ENABLE_ASM_X86_64_BMI2 + if (!(flags & HUF_flags_disableAsm)) { + loopFn = HUF_decompress4X2_usingDTable_internal_fast_asm_loop; + } + # endif +- } else { ++ } else if (DYNAMIC_BMI2) { + return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); + } +-#endif + + #if ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__) + if (!(flags & HUF_flags_disableAsm)) { +diff --git a/lib/zstd/decompress/zstd_decompress.c b/lib/zstd/decompress/zstd_decompress.c +--- a/lib/zstd/decompress/zstd_decompress.c ++++ b/lib/zstd/decompress/zstd_decompress.c +@@ -259,9 +259,7 @@ static void ZSTD_initDCtx_internal(ZSTD_DCtx* dctx) + dctx->noForwardProgress = 0; + dctx->oversizedDuration = 0; + dctx->isFrameDecompression = 1; +-#if DYNAMIC_BMI2 +- dctx->bmi2 = ZSTD_cpuSupportsBmi2(); +-#endif ++ ZSTD_SET_BMI2(dctx->bmi2, ZSTD_cpuSupportsBmi2()); + dctx->ddictSet = NULL; + ZSTD_DCtx_resetParameters(dctx); + #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +diff --git a/lib/zstd/decompress/zstd_decompress_block.c b/lib/zstd/decompress/zstd_decompress_block.c +--- a/lib/zstd/decompress/zstd_decompress_block.c ++++ b/lib/zstd/decompress/zstd_decompress_block.c +@@ -16,6 +16,9 @@ + * Dependencies + *********************************************************/ + #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memmove, ZSTD_memset */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "../common/compiler.h" /* prefetch */ + #include "../common/cpu.h" /* bmi2 */ + #include "../common/mem.h" /* low level memory routines */ +@@ -631,13 +634,11 @@ void ZSTD_buildFSETable(ZSTD_seqSymbol* dt, + const U32* baseValue, const U8* nbAdditionalBits, + unsigned tableLog, void* wksp, size_t wkspSize, int bmi2) + { +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + ZSTD_buildFSETable_body_bmi2(dt, normalizedCounter, maxSymbolValue, + baseValue, nbAdditionalBits, tableLog, wksp, wkspSize); + return; + } +-#endif + (void)bmi2; + ZSTD_buildFSETable_body_default(dt, normalizedCounter, maxSymbolValue, + baseValue, nbAdditionalBits, tableLog, wksp, wkspSize); +@@ -1955,11 +1956,9 @@ ZSTD_decompressSequences(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, + const ZSTD_longOffset_e isLongOffset) + { + DEBUGLOG(5, "ZSTD_decompressSequences"); +-#if DYNAMIC_BMI2 +- if (ZSTD_DCtx_get_bmi2(dctx)) { ++ if (ZSTD_USE_BMI2(ZSTD_DCtx_get_bmi2(dctx))) { + return ZSTD_decompressSequences_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } +-#endif + return ZSTD_decompressSequences_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } + static size_t +@@ -1968,11 +1967,9 @@ ZSTD_decompressSequencesSplitLitBuffer(ZSTD_DCtx* dctx, void* dst, size_t maxDst + const ZSTD_longOffset_e isLongOffset) + { + DEBUGLOG(5, "ZSTD_decompressSequencesSplitLitBuffer"); +-#if DYNAMIC_BMI2 +- if (ZSTD_DCtx_get_bmi2(dctx)) { ++ if (ZSTD_USE_BMI2(ZSTD_DCtx_get_bmi2(dctx))) { + return ZSTD_decompressSequencesSplitLitBuffer_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } +-#endif + return ZSTD_decompressSequencesSplitLitBuffer_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } + #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG */ +@@ -1991,11 +1988,9 @@ ZSTD_decompressSequencesLong(ZSTD_DCtx* dctx, + const ZSTD_longOffset_e isLongOffset) + { + DEBUGLOG(5, "ZSTD_decompressSequencesLong"); +-#if DYNAMIC_BMI2 +- if (ZSTD_DCtx_get_bmi2(dctx)) { ++ if (ZSTD_USE_BMI2(ZSTD_DCtx_get_bmi2(dctx))) { + return ZSTD_decompressSequencesLong_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } +-#endif + return ZSTD_decompressSequencesLong_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } + #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */ +diff --git a/lib/zstd/decompress/zstd_decompress_internal.h b/lib/zstd/decompress/zstd_decompress_internal.h +--- a/lib/zstd/decompress/zstd_decompress_internal.h ++++ b/lib/zstd/decompress/zstd_decompress_internal.h +@@ -204,7 +204,7 @@ struct ZSTD_DCtx_s + }; /* typedef'd to ZSTD_DCtx within "zstd.h" */ + + MEM_STATIC int ZSTD_DCtx_get_bmi2(const struct ZSTD_DCtx_s *dctx) { +-#if DYNAMIC_BMI2 ++#if DYNAMIC_BMI2 && !defined(ZSTD_USE_KERNEL_CPU_FEATURES) + return dctx->bmi2; + #else + (void)dctx; diff --git a/pkgbuilds/linux-omarchy-bore/0292-crypto-zstd-defer-cstream-init.patch b/pkgbuilds/linux-omarchy-bore/0292-crypto-zstd-defer-cstream-init.patch new file mode 100644 index 0000000..4007e61 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0292-crypto-zstd-defer-cstream-init.patch @@ -0,0 +1,44 @@ +diff --git a/crypto/zstd.c b/crypto/zstd.c +--- a/crypto/zstd.c ++++ b/crypto/zstd.c +@@ -96,6 +96,7 @@ static int zstd_compress_one(struct acomp_req *req, struct zstd_ctx *ctx, + + static int zstd_compress(struct acomp_req *req) + { ++ bool stream_initialized = false; + struct crypto_acomp_stream *s; + unsigned int pos, scur, dcur; + unsigned int total_out = 0; +@@ -115,12 +116,6 @@ static int zstd_compress(struct acomp_req *req) + if (ret) + goto out; + +- ctx->cctx = zstd_init_cstream(&ctx->params, 0, ctx->wksp, ctx->wksp_size); +- if (!ctx->cctx) { +- ret = -EINVAL; +- goto out; +- } +- + do { + dcur = acomp_walk_next_dst(&walk); + if (!dcur) { +@@ -142,6 +137,19 @@ static int zstd_compress(struct acomp_req *req) + goto out; + } + ++ if (!stream_initialized) { ++ ctx->cctx = zstd_init_cstream(&ctx->params, 0, ++ ctx->wksp, ctx->wksp_size); ++ if (!ctx->cctx) { ++ /* Release in the reverse of the map order. */ ++ acomp_walk_done_src(&walk, 0); ++ acomp_walk_done_dst(&walk, 0); ++ ret = -EINVAL; ++ goto out; ++ } ++ stream_initialized = true; ++ } ++ + if (scur) { + inbuf.pos = 0; + inbuf.src = walk.src.virt.addr; diff --git a/pkgbuilds/linux-omarchy-bore/0293-crypto-zstd-defer-dstream-init.patch b/pkgbuilds/linux-omarchy-bore/0293-crypto-zstd-defer-dstream-init.patch new file mode 100644 index 0000000..224f1ad --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0293-crypto-zstd-defer-dstream-init.patch @@ -0,0 +1,44 @@ +diff --git a/crypto/zstd.c b/crypto/zstd.c +--- a/crypto/zstd.c ++++ b/crypto/zstd.c +@@ -215,6 +215,7 @@ static int zstd_decompress_one(struct acomp_req *req, struct zstd_ctx *ctx, + + static int zstd_decompress(struct acomp_req *req) + { ++ bool stream_initialized = false; + struct crypto_acomp_stream *s; + unsigned int total_out = 0; + unsigned int scur, dcur; +@@ -232,12 +233,6 @@ static int zstd_decompress(struct acomp_req *req) + if (ret) + goto out; + +- ctx->dctx = zstd_init_dstream(ZSTD_MAX_SIZE, ctx->wksp, ctx->wksp_size); +- if (!ctx->dctx) { +- ret = -EINVAL; +- goto out; +- } +- + do { + scur = acomp_walk_next_src(&walk); + if (scur) { +@@ -263,6 +258,19 @@ static int zstd_decompress(struct acomp_req *req) + goto out; + } + ++ if (!stream_initialized) { ++ ctx->dctx = zstd_init_dstream(ZSTD_MAX_SIZE, ctx->wksp, ++ ctx->wksp_size); ++ if (!ctx->dctx) { ++ /* Release in the reverse of the map order. */ ++ acomp_walk_done_dst(&walk, 0); ++ acomp_walk_done_src(&walk, 0); ++ ret = -EINVAL; ++ goto out; ++ } ++ stream_initialized = true; ++ } ++ + outbuf.pos = 0; + outbuf.dst = (u8 *)walk.dst.virt.addr; + outbuf.size = dcur; diff --git a/pkgbuilds/linux-omarchy-bore/0295-af-alg-restrict.patch b/pkgbuilds/linux-omarchy-bore/0295-af-alg-restrict.patch new file mode 100644 index 0000000..2700fc9 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0295-af-alg-restrict.patch @@ -0,0 +1,363 @@ +--- a/crypto/af_alg.c ++++ b/crypto/af_alg.c +@@ -8,6 +8,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -22,10 +23,28 @@ + #include + #include + #include ++#include ++#include + #include + #include + #include + ++static int af_alg_restrict = 1; ++ ++static const struct ctl_table af_alg_table[] = { ++ { ++ .procname = "af_alg_restrict", ++ .data = &af_alg_restrict, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_TWO, ++ }, ++}; ++ ++static struct ctl_table_header *af_alg_header; ++ + struct alg_type_list { + const struct af_alg_type *type; + struct list_head list; +@@ -110,6 +129,43 @@ + } + EXPORT_SYMBOL_GPL(af_alg_unregister_type); + ++static bool af_alg_capable(void) ++{ ++ return ns_capable_noaudit(&init_user_ns, CAP_NET_ADMIN) || ++ capable(CAP_SYS_ADMIN); ++} ++ ++int af_alg_check_restriction(const char *name, ++ const struct af_alg_allowlist_entry allowlist[]) ++{ ++ int level = READ_ONCE(af_alg_restrict); ++ ++ if (level == 0) ++ return 0; ++ if (level == 1) { ++ for (const struct af_alg_allowlist_entry *ent = allowlist; ++ ent->name; ent++) { ++ if (strcmp(name, ent->name) == 0) { ++ if ((ent->flags & AF_ALG_UNPRIVILEGED) || ++ af_alg_capable()) ++ return 0; ++ /* List contains at most one entry per name. */ ++ break; ++ } ++ } ++ } ++ /* ++ * Use -ENOENT (the error code for "algorithm not found") instead of ++ * -EACCES or -EPERM, for the highest chance of correctly triggering ++ * fallback code paths in userspace programs. ++ * ++ * Don't log a warning, since it would be noisy. iwd tries to bind a ++ * bunch of algorithms that it never uses. ++ */ ++ return -ENOENT; ++} ++EXPORT_SYMBOL_GPL(af_alg_check_restriction); ++ + static void alg_do_release(const struct af_alg_type *type, void *private) + { + if (!type) +@@ -506,6 +562,9 @@ + struct sock *sk; + int err; + ++ if (READ_ONCE(af_alg_restrict) == 2) ++ return -EAFNOSUPPORT; ++ + if (sock->type != SOCK_SEQPACKET) + return -ESOCKTNOSUPPORT; + if (protocol != 0) +@@ -1222,27 +1281,32 @@ + + static int __init af_alg_init(void) + { +- int err = proto_register(&alg_proto, 0); ++ int err; ++ ++ af_alg_header = register_sysctl("crypto", af_alg_table); + ++ err = proto_register(&alg_proto, 0); + if (err) +- goto out; ++ goto out_unregister_sysctl; + + err = sock_register(&alg_family); +- if (err != 0) ++ if (err) + goto out_unregister_proto; + +-out: +- return err; ++ return 0; + + out_unregister_proto: + proto_unregister(&alg_proto); +- goto out; ++out_unregister_sysctl: ++ unregister_sysctl_table(af_alg_header); ++ return err; + } + + static void __exit af_alg_exit(void) + { + sock_unregister(PF_ALG); + proto_unregister(&alg_proto); ++ unregister_sysctl_table(af_alg_header); + } + + module_init(af_alg_init); +--- a/crypto/algif_aead.c ++++ b/crypto/algif_aead.c +@@ -34,6 +34,11 @@ + #include + #include + ++static const struct af_alg_allowlist_entry aead_allowlist[] = { ++ { "ccm(aes)" }, /* bluez */ ++ {}, ++}; ++ + static inline bool aead_sufficient_data(struct sock *sk) + { + struct alg_sock *ask = alg_sk(sk); +@@ -344,6 +349,12 @@ + + static void *aead_bind(const char *name) + { ++ int err; ++ ++ err = af_alg_check_restriction(name, aead_allowlist); ++ if (err) ++ return ERR_PTR(err); ++ + return crypto_alloc_aead(name, 0, AF_ALG_CRYPTOAPI_MASK); + } + +--- a/crypto/algif_hash.c ++++ b/crypto/algif_hash.c +@@ -16,6 +16,24 @@ + #include + #include + ++static const struct af_alg_allowlist_entry hash_allowlist[] = { ++ { "cmac(aes)" }, /* iwd, bluez */ ++ { "hmac(md5)" }, /* iwd */ ++ { "hmac(sha1)" }, /* iwd */ ++ { "hmac(sha224)" }, /* iwd */ ++ { "hmac(sha256)" }, /* iwd */ ++ { "hmac(sha384)" }, /* iwd */ ++ { "hmac(sha512)" }, /* iwd, sha512hmac */ ++ { "md4" }, /* iwd */ ++ { "md5" }, /* iwd */ ++ { "sha1", AF_ALG_UNPRIVILEGED }, /* iwd, iproute2 < 7.0 */ ++ { "sha224" }, /* iwd */ ++ { "sha256" }, /* iwd */ ++ { "sha384" }, /* iwd */ ++ { "sha512" }, /* iwd */ ++ {}, ++}; ++ + struct hash_ctx { + struct af_alg_sgl sgl; + +@@ -382,6 +400,12 @@ + + static void *hash_bind(const char *name) + { ++ int err; ++ ++ err = af_alg_check_restriction(name, hash_allowlist); ++ if (err) ++ return ERR_PTR(err); ++ + return crypto_alloc_ahash(name, 0, AF_ALG_CRYPTOAPI_MASK); + } + +--- a/crypto/algif_rng.c ++++ b/crypto/algif_rng.c +@@ -50,6 +50,10 @@ + MODULE_AUTHOR("Stephan Mueller "); + MODULE_DESCRIPTION("User-space interface for random number generators"); + ++static const struct af_alg_allowlist_entry rng_allowlist[] = { ++ {}, ++}; ++ + struct rng_ctx { + #define MAXSIZE 128 + unsigned int len; +@@ -201,6 +205,11 @@ + { + struct rng_parent_ctx *pctx; + struct crypto_rng *rng; ++ int err; ++ ++ err = af_alg_check_restriction(name, rng_allowlist); ++ if (err) ++ return ERR_PTR(err); + + pctx = kzalloc_obj(*pctx); + if (!pctx) +--- a/crypto/algif_skcipher.c ++++ b/crypto/algif_skcipher.c +@@ -35,6 +35,24 @@ + #include + #include + ++static const struct af_alg_allowlist_entry skcipher_allowlist[] = { ++ { "adiantum(xchacha12,aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ { "adiantum(xchacha20,aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ { "cbc(aes)" }, /* iwd */ ++ { "cbc(des)" }, /* iwd */ ++ { "cbc(des3_ede)" }, /* iwd */ ++ { "cbc(paes)" }, /* caam and others */ ++ { "ctr(aes)" }, /* iwd */ ++ { "ecb(aes)" }, /* iwd, bluez */ ++ { "ecb(des)" }, /* iwd */ ++ { "hctr2(aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ { "xts(aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup benchmark */ ++ { "xts(camellia)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ { "xts(serpent)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ { "xts(twofish)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ {}, ++}; ++ + static int skcipher_sendmsg(struct socket *sock, struct msghdr *msg, + size_t size) + { +@@ -311,6 +329,11 @@ + static void *skcipher_bind(const char *name) + { + u32 mask = AF_ALG_CRYPTOAPI_MASK; ++ int err; ++ ++ err = af_alg_check_restriction(name, skcipher_allowlist); ++ if (err) ++ return ERR_PTR(err); + + if (strcmp(name, "cbc(paes)") == 0) + mask = 0; +--- a/include/crypto/if_alg.h ++++ b/include/crypto/if_alg.h +@@ -8,6 +8,7 @@ + #ifndef _CRYPTO_IF_ALG_H + #define _CRYPTO_IF_ALG_H + ++#include + #include + #include + #include +@@ -121,7 +122,7 @@ + * @iv: IV for cipher operation + * @state: Existing state for continuing operation + * @aead_assoclen: Length of AAD for AEAD cipher operations +- * @completion: Work queue for synchronous operation ++ * @wait: For waiting for completion of async crypto ops + * @used: TX bytes sent to kernel. This variable is used to + * ensure that user space cannot cause the kernel + * to allocate too much memory in sendmsg operation. +@@ -161,9 +162,20 @@ + unsigned int inflight; + }; + ++/* Flags for af_alg_allowlist_entry::flags: */ ++#define AF_ALG_UNPRIVILEGED BIT(0) /* Unprivileged use is allowed */ ++ ++struct af_alg_allowlist_entry { ++ const char *name; ++ u32 flags; ++}; ++ + int af_alg_register_type(const struct af_alg_type *type); + int af_alg_unregister_type(const struct af_alg_type *type); + ++int af_alg_check_restriction(const char *name, ++ const struct af_alg_allowlist_entry allowlist[]); ++ + int af_alg_release(struct socket *sock); + void af_alg_release_parent(struct sock *sk); + int af_alg_accept(struct sock *sk, struct socket *newsock, +@@ -177,10 +189,11 @@ + } + + /** +- * Size of available buffer for sending data from user space to kernel. ++ * af_alg_sndbuf - Size of available buffer for sending data from user space to kernel. + * +- * @sk socket of connection to user space +- * @return number of bytes still available ++ * @sk: socket of connection to user space ++ * ++ * Returns: number of bytes still available + */ + static inline int af_alg_sndbuf(struct sock *sk) + { +@@ -192,10 +205,11 @@ + } + + /** +- * Can the send buffer still be written to? ++ * af_alg_writable - Can the send buffer still be written to? ++ * ++ * @sk: socket of connection to user space + * +- * @sk socket of connection to user space +- * @return true => writable, false => not writable ++ * Returns: true => writable, false => not writable + */ + static inline bool af_alg_writable(struct sock *sk) + { +@@ -203,10 +217,11 @@ + } + + /** +- * Size of available buffer used by kernel for the RX user space operation. ++ * af_alg_rcvbuf - Size of available buffer used by kernel for the RX user space operation. + * +- * @sk socket of connection to user space +- * @return number of bytes still available ++ * @sk: socket of connection to user space ++ * ++ * Returns: number of bytes still available + */ + static inline int af_alg_rcvbuf(struct sock *sk) + { +@@ -218,10 +233,11 @@ + } + + /** +- * Can the RX buffer still be written to? ++ * af_alg_readable - Can the RX buffer still be read from? ++ * ++ * @sk: socket of connection to user space + * +- * @sk socket of connection to user space +- * @return true => writable, false => not writable ++ * Returns: true => readable, false => not readable + */ + static inline bool af_alg_readable(struct sock *sk) + { diff --git a/pkgbuilds/linux-omarchy-bore/0296-x86-mm-pmd-modify-keep-dirty-bit.patch b/pkgbuilds/linux-omarchy-bore/0296-x86-mm-pmd-modify-keep-dirty-bit.patch new file mode 100644 index 0000000..602ef86 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0296-x86-mm-pmd-modify-keep-dirty-bit.patch @@ -0,0 +1,12 @@ +diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h +--- a/arch/x86/include/asm/pgtable.h ++++ b/arch/x86/include/asm/pgtable.h +@@ -806,7 +806,7 @@ static inline pmd_t pmd_modify(pmd_t pmd, pgprot_t newprot) + pmdval_t val = pmd_val(pmd), oldval = val; + pmd_t pmd_result; + +- val &= (_HPAGE_CHG_MASK & ~_PAGE_DIRTY); ++ val &= _HPAGE_CHG_MASK; + val |= check_pgprot(newprot) & ~_HPAGE_CHG_MASK; + val = flip_protnone_guard(oldval, val, PHYSICAL_PMD_PAGE_MASK); + diff --git a/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch b/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch new file mode 100644 index 0000000..0cb9f8d --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch @@ -0,0 +1,5023 @@ +diff --git a/fs/btrfs/acl.c b/fs/btrfs/acl.c +--- a/fs/btrfs/acl.c ++++ b/fs/btrfs/acl.c +@@ -15,6 +15,7 @@ + #include "xattr.h" + #include "acl.h" + #include "misc.h" ++#include "btrfs_inode.h" + + struct posix_acl *btrfs_get_acl(struct inode *inode, int type, bool rcu) + { +@@ -107,6 +108,9 @@ int btrfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, + struct inode *inode = d_inode(dentry); + umode_t old_mode = inode->i_mode; + ++ if (btrfs_root_readonly(BTRFS_I(inode)->root)) ++ return -EROFS; ++ + if (type == ACL_TYPE_ACCESS && acl) { + ret = posix_acl_update_mode(idmap, inode, + &inode->i_mode, &acl); +diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c +--- a/fs/btrfs/block-group.c ++++ b/fs/btrfs/block-group.c +@@ -2047,6 +2047,11 @@ static int btrfs_reclaim_block_group(struct btrfs_block_group *bg, int *reclaime + + trace_btrfs_reclaim_block_group(bg); + ret = btrfs_relocate_chunk(fs_info, bg->start, false); ++ if (btrfs_is_zoned(fs_info) && ret == -EAGAIN) { ++ btrfs_dec_block_group_ro(bg); ++ btrfs_debug(fs_info, "deferring reclaim of chunk %llu", bg->start); ++ return ret; ++ } + if (ret) { + btrfs_dec_block_group_ro(bg); + btrfs_err(fs_info, "error relocating chunk %llu", +@@ -2113,7 +2118,8 @@ void btrfs_reclaim_block_groups(struct btrfs_fs_info *fs_info, unsigned int limi + spin_unlock(&fs_info->unused_bgs_lock); + ret = btrfs_reclaim_block_group(bg, &reclaimed); + +- if (ret && !READ_ONCE(space_info->periodic_reclaim)) ++ if ((btrfs_is_zoned(fs_info) && ret == -EAGAIN) || ++ (ret && !READ_ONCE(space_info->periodic_reclaim))) + btrfs_link_bg_list(bg, &retry_list); + btrfs_put_block_group(bg); + +@@ -2624,10 +2630,9 @@ static int fill_dummy_bgs(struct btrfs_fs_info *fs_info) + + /* Fill dummy cache as FULL */ + bg->length = map->chunk_len; +- bg->flags = map->type; ++ bg->flags = map->on_disk_type; + bg->cached = BTRFS_CACHE_FINISHED; + bg->used = map->chunk_len; +- bg->flags = map->type; + bg->space_info = btrfs_find_space_info(fs_info, bg->flags); + ret = btrfs_add_block_group_cache(bg); + /* +@@ -3916,7 +3921,7 @@ int btrfs_update_block_group(struct btrfs_trans_handle *trans, + old_val += num_bytes; + cache->used = old_val; + cache->reserved -= num_bytes; +- cache->reclaim_mark = 0; ++ cache->reclaim_mark = false; + space_info->bytes_reserved -= num_bytes; + space_info->bytes_used += num_bytes; + space_info->disk_used += num_bytes * factor; +diff --git a/fs/btrfs/block-group.h b/fs/btrfs/block-group.h +--- a/fs/btrfs/block-group.h ++++ b/fs/btrfs/block-group.h +@@ -263,6 +263,9 @@ struct btrfs_block_group { + + enum btrfs_block_group_size_class size_class:8; + ++ /* If set, this blockgroup is not used for allocation between two reclaim sweeps. */ ++ bool reclaim_mark; ++ + /* + * Number of extents in this block group used for swap files. + * All accesses protected by the spinlock 'lock'. +@@ -281,7 +284,6 @@ struct btrfs_block_group { + struct list_head active_bg_list; + struct work_struct zone_finish_work; + struct extent_buffer *last_eb; +- u64 reclaim_mark; + }; + + static inline u64 btrfs_block_group_end(const struct btrfs_block_group *block_group) +diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c +--- a/fs/btrfs/compression.c ++++ b/fs/btrfs/compression.c +@@ -651,9 +651,9 @@ struct heuristic_ws { + u8 *sample; + u32 sample_size; + /* Buckets store counters for each byte value */ +- struct bucket_item *bucket; ++ struct bucket_item bucket[BUCKET_SIZE]; + /* Sorting buffer */ +- struct bucket_item *bucket_b; ++ struct bucket_item bucket_b[BUCKET_SIZE]; + struct list_head list; + }; + +@@ -664,8 +664,6 @@ static void free_heuristic_ws(struct list_head *ws) + workspace = list_entry(ws, struct heuristic_ws, list); + + kvfree(workspace->sample); +- kfree(workspace->bucket); +- kfree(workspace->bucket_b); + kfree(workspace); + } + +@@ -681,14 +679,6 @@ static struct list_head *alloc_heuristic_ws(struct btrfs_fs_info *fs_info) + if (!ws->sample) + goto fail; + +- ws->bucket = kzalloc_objs(*ws->bucket, BUCKET_SIZE); +- if (!ws->bucket) +- goto fail; +- +- ws->bucket_b = kzalloc_objs(*ws->bucket_b, BUCKET_SIZE); +- if (!ws->bucket_b) +- goto fail; +- + INIT_LIST_HEAD(&ws->list); + return &ws->list; + fail: +diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c +--- a/fs/btrfs/ctree.c ++++ b/fs/btrfs/ctree.c +@@ -1460,6 +1460,7 @@ static noinline void unlock_up(struct btrfs_path *path, int level, + */ + static int + read_block_for_search(struct btrfs_root *root, struct btrfs_path *p, ++ struct btrfs_eb_prealloc *pa, + struct extent_buffer **eb_ret, int slot, + const struct btrfs_key *key) + { +@@ -1546,7 +1547,8 @@ read_block_for_search(struct btrfs_root *root, struct btrfs_path *p, + if (p->reada != READA_NONE) + reada_for_search(fs_info, p, parent_level, slot, key->objectid); + +- tmp = btrfs_find_create_tree_block(fs_info, blocknr, check.owner_root, check.level); ++ tmp = btrfs_find_create_tree_block(fs_info, pa, blocknr, ++ check.owner_root, check.level); + if (IS_ERR(tmp)) { + ret = PTR_ERR(tmp); + tmp = NULL; +@@ -2004,6 +2006,7 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + u8 lowest_level = 0; + int min_write_lock_level; + int prev_cmp; ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + + if (!root) + return -EINVAL; +@@ -2058,6 +2061,11 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + } + + again: ++ if (pa.needs_prealloc) { ++ ret = btrfs_init_eb_prealloc(fs_info, &pa, false); ++ if (ret) ++ goto done; ++ } + prev_cmp = -1; + b = btrfs_search_slot_get_root(root, p, write_lock_level); + if (IS_ERR(b)) { +@@ -2187,7 +2195,7 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + goto done; + } + +- ret2 = read_block_for_search(root, p, &b, slot, key); ++ ret2 = read_block_for_search(root, p, &pa, &b, slot, key); + if (ret2 == -EAGAIN && !p->nowait) { + trace_btrfs_search_slot_restart(root, level, "read_block"); + goto again; +@@ -2234,6 +2242,8 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + ret = ret2; + } + ++ btrfs_free_eb_prealloc(&pa); ++ + return ret; + } + ALLOW_ERROR_INJECTION(btrfs_search_slot, ERRNO); +@@ -2259,6 +2269,7 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + int level; + int lowest_unlock = 1; + u8 lowest_level = 0; ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + + lowest_level = p->lowest_level; + WARN_ON(p->nodes[0] != NULL); +@@ -2270,6 +2281,11 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + } + + again: ++ if (pa.needs_prealloc) { ++ ret = btrfs_init_eb_prealloc(fs_info, &pa, false); ++ if (ret) ++ goto done; ++ } + b = btrfs_get_old_root(root, time_seq); + if (unlikely(!b)) { + ret = -EIO; +@@ -2316,7 +2332,7 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + goto done; + } + +- ret2 = read_block_for_search(root, p, &b, slot, key); ++ ret2 = read_block_for_search(root, p, &pa, &b, slot, key); + if (ret2 == -EAGAIN && !p->nowait) + goto again; + if (ret2) { +@@ -2339,6 +2355,8 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + if (ret < 0) + btrfs_release_path(p); + ++ btrfs_free_eb_prealloc(&pa); ++ + return ret; + } + +@@ -4780,6 +4798,7 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + struct extent_buffer *next; + struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_key key; ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + bool need_commit_sem = false; + u32 nritems; + int ret; +@@ -4798,6 +4817,11 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + + btrfs_item_key_to_cpu(path->nodes[0], &key, nritems - 1); + again: ++ if (pa.needs_prealloc) { ++ ret = btrfs_init_eb_prealloc(fs_info, &pa, false); ++ if (ret) ++ goto done; ++ } + level = 1; + next = NULL; + btrfs_release_path(path); +@@ -4880,7 +4904,7 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + } + + next = c; +- ret = read_block_for_search(root, path, &next, slot, &key); ++ ret = read_block_for_search(root, path, &pa, &next, slot, &key); + if (ret == -EAGAIN && !path->nowait) + goto again; + +@@ -4923,7 +4947,7 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + if (!level) + break; + +- ret = read_block_for_search(root, path, &next, 0, &key); ++ ret = read_block_for_search(root, path, &pa, &next, 0, &key); + if (ret == -EAGAIN && !path->nowait) + goto again; + +@@ -4956,6 +4980,8 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + ret = ret2; + } + ++ btrfs_free_eb_prealloc(&pa); ++ + return ret; + } + +diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h +--- a/fs/btrfs/ctree.h ++++ b/fs/btrfs/ctree.h +@@ -131,7 +131,6 @@ enum { + BTRFS_ROOT_ORPHAN_ITEM_INSERTED, + BTRFS_ROOT_DEFRAG_RUNNING, + BTRFS_ROOT_FORCE_COW, +- BTRFS_ROOT_MULTI_LOG_TASKS, + BTRFS_ROOT_DIRTY, + BTRFS_ROOT_DELETING, + +@@ -196,9 +195,7 @@ struct btrfs_root { + struct list_head log_ctxs[2]; + /* Used only for log trees of subvolumes, not for the log root tree */ + atomic_t log_writers; +- atomic_t log_commit[2]; +- /* Used only for log trees of subvolumes, not for the log root tree */ +- atomic_t log_batch; ++ bool log_commit[2]; + /* + * Protected by the 'log_mutex' lock but can be read without holding + * that lock to avoid unnecessary lock contention, in which case it +@@ -216,7 +213,6 @@ struct btrfs_root { + * to access this field. + */ + int last_log_commit; +- pid_t log_start_pid; + + u64 last_trans; + +diff --git a/fs/btrfs/defrag.c b/fs/btrfs/defrag.c +--- a/fs/btrfs/defrag.c ++++ b/fs/btrfs/defrag.c +@@ -1093,7 +1093,7 @@ static int defrag_collect_targets(struct btrfs_inode *inode, + struct defrag_target_range *tmp; + + list_for_each_entry_safe(entry, tmp, target_list, list) { +- list_del_init(&entry->list); ++ list_del(&entry->list); + kfree(entry); + } + } +@@ -1130,20 +1130,15 @@ static_assert(PAGE_ALIGNED(CLUSTER_SIZE)); + * + * - Extent bits are locked + */ +-static int defrag_one_locked_target(struct btrfs_inode *inode, +- struct defrag_target_range *target, +- struct folio **folios, int nr_pages, +- struct extent_state **cached_state) ++static void defrag_one_locked_target(struct btrfs_inode *inode, ++ struct defrag_target_range *target, ++ struct folio **folios, int nr_pages, ++ struct extent_state **cached_state) + { + struct btrfs_fs_info *fs_info = inode->root->fs_info; +- struct extent_changeset *data_reserved = NULL; + const u64 start = target->start; + const u64 len = target->len; +- int ret = 0; + +- ret = btrfs_delalloc_reserve_space(inode, &data_reserved, start, len); +- if (ret < 0) +- return ret; + btrfs_clear_extent_bit(&inode->io_tree, start, start + len - 1, + EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | + EXTENT_DEFRAG, cached_state); +@@ -1164,10 +1159,6 @@ static int defrag_one_locked_target(struct btrfs_inode *inode, + continue; + btrfs_folio_clamp_set_dirty(fs_info, folio, start, len); + } +- btrfs_delalloc_release_extents(inode, len); +- extent_changeset_free(data_reserved); +- +- return ret; + } + + static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, +@@ -1178,11 +1169,13 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + struct defrag_target_range *entry; + struct defrag_target_range *tmp; + LIST_HEAD(target_list); +- struct folio **folios; ++ struct folio AUTO_KFREE(*folios); + const u32 sectorsize = inode->root->fs_info->sectorsize; + u64 cur = start; + const unsigned int nr_pages = ((start + len - 1) >> PAGE_SHIFT) - + (start >> PAGE_SHIFT) + 1; ++ struct extent_changeset *data_reserved = NULL; ++ u64 last_defrag_end = start; + int ret = 0; + + ASSERT(nr_pages <= CLUSTER_SIZE / PAGE_SIZE); +@@ -1192,6 +1185,20 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + if (!folios) + return -ENOMEM; + ++ /* ++ * Reserve delalloc space before locking the range and before locking ++ * and dirtying any folios - otherwise we could deadlock, for example ++ * after defrag of one range we dirty folios and keep them locked when ++ * we move to the next range, so reserving delalloc space right before ++ * each range could trigger flushing of delalloc and deadlock on the ++ * extent lock or trigger a transaction commit with flushoncommit, which ++ * can either deadlock on the lock of a folio made dirty in the previous ++ * range or the extent lock. ++ */ ++ ret = btrfs_delalloc_reserve_space(inode, &data_reserved, start, len); ++ if (ret < 0) ++ return ret; ++ + /* Prepare all pages */ + for (int i = 0; cur < start + len && i < nr_pages; i++) { + folios[i] = defrag_prepare_one_folio(inode, cur >> PAGE_SHIFT); +@@ -1225,15 +1232,12 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + if (ret < 0) + goto unlock_extent; + +- list_for_each_entry(entry, &target_list, list) { +- ret = defrag_one_locked_target(inode, entry, folios, nr_pages, +- &cached_state); +- if (ret < 0) +- break; +- } +- + list_for_each_entry_safe(entry, tmp, &target_list, list) { +- list_del_init(&entry->list); ++ defrag_one_locked_target(inode, entry, folios, nr_pages, &cached_state); ++ if (entry->start > last_defrag_end) ++ btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end, ++ entry->start - last_defrag_end, true); ++ last_defrag_end = entry->start + entry->len; + kfree(entry); + } + unlock_extent: +@@ -1245,7 +1249,12 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + folio_unlock(folios[i]); + folio_put(folios[i]); + } +- kfree(folios); ++ btrfs_delalloc_release_extents(inode, len); ++ if (last_defrag_end < start + len) ++ btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end, ++ start + len - last_defrag_end, true); ++ extent_changeset_free(data_reserved); ++ + return ret; + } + +@@ -1310,10 +1319,8 @@ static int defrag_one_cluster(struct btrfs_inode *inode, + inode->root->fs_info->sectorsize_bits; + } + out: +- list_for_each_entry_safe(entry, tmp, &target_list, list) { +- list_del_init(&entry->list); ++ list_for_each_entry_safe(entry, tmp, &target_list, list) + kfree(entry); +- } + if (ret >= 0) + *last_scanned_ret = max(*last_scanned_ret, start + len); + return ret; +diff --git a/fs/btrfs/delayed-inode.c b/fs/btrfs/delayed-inode.c +--- a/fs/btrfs/delayed-inode.c ++++ b/fs/btrfs/delayed-inode.c +@@ -1523,10 +1523,10 @@ int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans, + ret = __btrfs_add_delayed_item(delayed_node, delayed_item); + if (unlikely(ret)) { + btrfs_err(trans->fs_info, +-"error adding delayed dir index item, name: %.*s, index: %llu, root: %llu, dir: %llu, dir->index_cnt: %llu, delayed_node->index_cnt: %llu, error: %d", ++"error adding delayed dir index item, name: %.*s, index: %llu, root: %llu, dir: %llu, dir->index_cnt: %llu, delayed_node->index_cnt: %llu, error: %pe", + name_len, name, index, btrfs_root_id(delayed_node->root), + delayed_node->inode_id, dir->index_cnt, +- delayed_node->index_cnt, ret); ++ delayed_node->index_cnt, ERR_PTR(ret)); + btrfs_release_delayed_item(delayed_item); + btrfs_release_dir_index_item_space(trans); + mutex_unlock(&delayed_node->mutex); +@@ -1645,8 +1645,8 @@ int btrfs_delete_delayed_dir_index(struct btrfs_trans_handle *trans, + */ + if (ret < 0) { + btrfs_err(trans->fs_info, +-"metadata reservation failed for delayed dir item deletion, index: %llu, root: %llu, inode: %llu, error: %d", +- index, btrfs_root_id(node->root), node->inode_id, ret); ++"metadata reservation failed for delayed dir item deletion, index: %llu, root: %llu, inode: %llu, error: %pe", ++ index, btrfs_root_id(node->root), node->inode_id, ERR_PTR(ret)); + btrfs_release_delayed_item(item); + goto end; + } +@@ -1655,8 +1655,8 @@ int btrfs_delete_delayed_dir_index(struct btrfs_trans_handle *trans, + ret = __btrfs_add_delayed_item(node, item); + if (unlikely(ret)) { + btrfs_err(trans->fs_info, +-"failed to add delayed dir index item, root: %llu, inode: %llu, index: %llu, error: %d", +- btrfs_root_id(node->root), node->inode_id, index, ret); ++"failed to add delayed dir index item, root: %llu, inode: %llu, index: %llu, error: %pe", ++ btrfs_root_id(node->root), node->inode_id, index, ERR_PTR(ret)); + btrfs_delayed_item_release_metadata(dir->root, item); + btrfs_release_delayed_item(item); + } +diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c +--- a/fs/btrfs/direct-io.c ++++ b/fs/btrfs/direct-io.c +@@ -14,7 +14,6 @@ + #include "ordered-data.h" + + struct btrfs_dio_data { +- ssize_t submitted; + loff_t old_isize; + struct extent_changeset *data_reserved; + struct btrfs_ordered_extent *ordered; +@@ -151,7 +150,7 @@ static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode, + if (type != BTRFS_ORDERED_NOCOW) { + em = btrfs_create_io_em(inode, start, file_extent, type); + if (IS_ERR(em)) +- goto out; ++ return em; + } + + ordered = btrfs_alloc_ordered_extent(inode, start, file_extent, +@@ -168,7 +167,6 @@ static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode, + ASSERT(!dio_data->ordered); + dio_data->ordered = ordered; + } +- out: + + return em; + } +@@ -626,78 +624,81 @@ static int btrfs_dio_iomap_end(struct inode *inode, loff_t pos, loff_t length, + { + struct iomap_iter *iter = container_of(iomap, struct iomap_iter, iomap); + struct btrfs_dio_data *dio_data = iter->private; +- size_t submitted = dio_data->submitted; + const bool write = !!(flags & IOMAP_WRITE); + int ret = 0; + +- if (!write && (iomap->type == IOMAP_HOLE)) { +- /* If reading from a hole, unlock and return */ +- btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, +- pos + length - 1, NULL); ++ if (!write) { ++ /* ++ * Hole read, nothing is submitted, thus we have to unlock ++ * the whole range. ++ */ ++ if (iomap->type == IOMAP_HOLE) { ++ btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, ++ pos + length - 1, NULL); ++ return 0; ++ } ++ /* ++ * Short read, needs to unlock the remaining range, and ++ * return -ENOTBLK so we can later fault in the pages and retry. ++ */ ++ if (written < length) { ++ btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos + written, ++ pos + length - 1, NULL); ++ return -ENOTBLK; ++ } ++ /* The full range is submitted, endio will do the unlock. */ + return 0; + } + +- if (submitted < length) { +- pos += submitted; +- length -= submitted; +- if (write) { +- /* +- * Got a short write and have updated the isize, need to +- * revert the isize change. +- * +- * Normally we need to update isize with extent lock hold, +- * but we're safe due to the following factors: +- * +- * - Only a single writer can be enlarging isize +- * Enlarging isize will take the exclusive inode lock. +- * +- * - Buffered readers need to wait for the OE we're holding +- * Buffered readers will lock extent and wait for OE +- * of the folio range, and since page cache is invalidated +- * the OE wait can not be skipped. +- * +- * So here we are safe to revert the isize before +- * finishing the OE, and no reader of the remaining range +- * can see the enlarged size. +- * +- * TODO: Extend the DIO_LOCKED lifespan for direct writes, +- * and only enlarge isize after a successful write. +- */ +- if (dio_data->updated_isize) { +- u64 new_isize; ++ if (written < length) { ++ /* ++ * Got a short write and have updated the i_size, need to revert ++ * the i_size change. ++ * ++ * Normally we need to update i_size with extent lock held, but ++ * we're safe due to the following factors: ++ * ++ * - Only a single writer can be enlarging i_size ++ * Enlarging i_size will take the exclusive inode lock. ++ * ++ * - Buffered readers need to wait for the OE we're holding ++ * Buffered readers will lock extent and wait for OE ++ * of the folio range, and since page cache is invalidated ++ * the OE wait cannot be skipped. ++ * ++ * So here we are safe to revert the isize before finishing the ++ * OE, and no reader of the remaining range can see the enlarged ++ * size. ++ * ++ * TODO: Extend the DIO_LOCKED lifespan for direct writes, ++ * and only enlarge isize after a successful write. ++ */ ++ if (dio_data->updated_isize) { ++ u64 new_isize; + +- if (submitted == 0) +- new_isize = dio_data->old_isize; +- else +- new_isize = max(dio_data->old_isize, pos); +- i_size_write(inode, new_isize); +- dio_data->updated_isize = false; +- } +- /* +- * We have a short write, if there is any range +- * that is submitted properly, that part will have +- * its own OE split from the original one. +- * +- * So for the OE at dio_data->ordered, it's the part +- * that is not submitted, and should be marked +- * as fully truncated. +- */ +- btrfs_mark_ordered_extent_truncated(dio_data->ordered, 0); +- btrfs_finish_ordered_extent(dio_data->ordered, +- pos, length, true); +- } else { +- btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, +- pos + length - 1, NULL); ++ if (written == 0) ++ new_isize = dio_data->old_isize; ++ else ++ new_isize = max(dio_data->old_isize, pos + written); ++ i_size_write(inode, new_isize); ++ dio_data->updated_isize = false; + } ++ /* ++ * We have a short write, if there is any range that is submitted ++ * properly, that part will have its own OE split from the ++ * original one. ++ * ++ * So for the OE at dio_data->ordered, it's the part that is not ++ * submitted, and should be marked as fully truncated. ++ */ ++ btrfs_mark_ordered_extent_truncated(dio_data->ordered, 0); ++ btrfs_finish_ordered_extent(dio_data->ordered, ++ pos + written, length - written, true); + ret = -ENOTBLK; + } +- if (write) { +- btrfs_put_ordered_extent(dio_data->ordered); +- dio_data->ordered = NULL; +- } +- +- if (write) +- extent_changeset_free(dio_data->data_reserved); ++ btrfs_put_ordered_extent(dio_data->ordered); ++ dio_data->ordered = NULL; ++ extent_changeset_free(dio_data->data_reserved); + return ret; + } + +@@ -779,8 +780,6 @@ static void btrfs_dio_submit_io(const struct iomap_iter *iter, struct bio *bio, + dip->file_offset = file_offset; + dip->bytes = bio->bi_iter.bi_size; + +- dio_data->submitted += bio->bi_iter.bi_size; +- + /* + * Check if we are doing a partial write. If we are, we need to split + * the ordered extent to match the submitted bio. Hang on to the +@@ -824,13 +823,41 @@ static ssize_t btrfs_dio_read(struct kiocb *iocb, struct iov_iter *iter, + IOMAP_DIO_PARTIAL | IOMAP_DIO_FSBLOCK_ALIGNED, &data, done_before); + } + ++static bool need_stable_write(struct btrfs_inode *inode) ++{ ++ const u64 data_profile = btrfs_data_alloc_profile(inode->root->fs_info) & ++ BTRFS_BLOCK_GROUP_PROFILE_MASK; ++ ++ /* Data checksum requires stable buffer. */ ++ if (!(inode->flags & BTRFS_INODE_NODATASUM)) ++ return true; ++ /* ++ * Any profile with mirror/parity will require stable buffer. ++ * Otherwise the mirror may differ from each other. ++ * ++ * Thus only SINGLE and RAID0 doesn't require stable buffer. ++ */ ++ if (data_profile != 0 && data_profile != BTRFS_BLOCK_GROUP_RAID0) ++ return true; ++ return false; ++} ++ + static struct iomap_dio *btrfs_dio_write(struct kiocb *iocb, struct iov_iter *iter, + size_t done_before) + { + struct btrfs_dio_data data = { 0 }; ++ unsigned int dio_flags = IOMAP_DIO_PARTIAL | IOMAP_DIO_FSBLOCK_ALIGNED; ++ ++ if (need_stable_write(BTRFS_I(file_inode(iocb->ki_filp)))) { ++ /* For now no support for BOUNCE and NOWAIT direct write. */ ++ if (iocb->ki_flags & IOCB_NOWAIT) ++ return ERR_PTR(-EAGAIN); ++ ++ dio_flags |= IOMAP_DIO_BOUNCE; ++ } + + return __iomap_dio_rw(iocb, iter, &btrfs_dio_iomap_ops, &btrfs_dio_ops, +- IOMAP_DIO_PARTIAL | IOMAP_DIO_FSBLOCK_ALIGNED, &data, done_before); ++ dio_flags, &data, done_before); + } + + static ssize_t check_direct_IO(struct btrfs_fs_info *fs_info, +@@ -859,8 +886,6 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from) + ssize_t ret; + unsigned int ilock_flags = 0; + struct iomap_dio *dio; +- const u64 data_profile = btrfs_data_alloc_profile(fs_info) & +- BTRFS_BLOCK_GROUP_PROFILE_MASK; + + if (iocb->ki_flags & IOCB_NOWAIT) + ilock_flags |= BTRFS_ILOCK_TRY; +@@ -874,16 +899,6 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from) + if (iocb->ki_pos + iov_iter_count(from) <= i_size_read(inode) && IS_NOSEC(inode)) + ilock_flags |= BTRFS_ILOCK_SHARED; + +- /* +- * If our data profile has duplication (either extra mirrors or RAID56), +- * we can not trust the direct IO buffer, the content may change during +- * writeback and cause different contents written to different mirrors. +- * +- * Thus only RAID0 and SINGLE can go true zero-copy direct IO. +- */ +- if (data_profile != BTRFS_BLOCK_GROUP_RAID0 && data_profile != 0) +- goto buffered; +- + relock: + ret = btrfs_inode_lock(BTRFS_I(inode), ilock_flags); + if (ret < 0) +@@ -924,22 +939,6 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from) + btrfs_inode_unlock(BTRFS_I(inode), ilock_flags); + goto buffered; + } +- /* +- * We can't control the folios being passed in, applications can write +- * to them while a direct IO write is in progress. This means the +- * content might change after we calculated the data checksum. +- * Therefore we can end up storing a checksum that doesn't match the +- * persisted data. +- * +- * To be extra safe and avoid false data checksum mismatch, if the +- * inode requires data checksum, just fallback to buffered IO. +- * For buffered IO we have full control of page cache and can ensure +- * no one is modifying the content during writeback. +- */ +- if (!(BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM)) { +- btrfs_inode_unlock(BTRFS_I(inode), ilock_flags); +- goto buffered; +- } + + /* + * The iov_iter can be mapped to the same file range we are writing to. +diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c +--- a/fs/btrfs/disk-io.c ++++ b/fs/btrfs/disk-io.c +@@ -271,14 +271,15 @@ int btree_csum_one_bio(struct btrfs_bio *bbio) + return -EIO; + + /* +- * If an extent_buffer is marked as EXTENT_BUFFER_ZONED_ZEROOUT, don't +- * checksum it but zero-out its content. This is done to preserve +- * ordering of I/O without unnecessarily writing out data. ++ * An extent_buffer marked EXTENT_BUFFER_ZONED_ZEROOUT is written out as ++ * zeros to preserve ordering of I/O without persisting the now ++ * unnecessary block. The bio is fed from the shared zero page (see ++ * write_one_eb()), so there is nothing to checksum here. Crucially, the ++ * buffer's own content is left intact: it may still be referenced, e.g. ++ * btrfs_free_tree_block() reads its header to add a delayed reference. + */ +- if (test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) { +- memzero_extent_buffer(eb, 0, eb->len); ++ if (test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) + return 0; +- } + + if (WARN_ON_ONCE(found_start != eb->start)) + return -EIO; +@@ -590,12 +591,13 @@ static const struct address_space_operations btree_aops = { + + struct extent_buffer *btrfs_find_create_tree_block( + struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 bytenr, u64 owner_root, + int level) + { + if (btrfs_is_testing(fs_info)) + return alloc_test_extent_buffer(fs_info, bytenr); +- return alloc_extent_buffer(fs_info, bytenr, owner_root, level); ++ return alloc_extent_buffer(fs_info, pa, bytenr, owner_root, level); + } + + /* +@@ -608,12 +610,13 @@ struct extent_buffer *btrfs_find_create_tree_block( + struct extent_buffer *read_tree_block(struct btrfs_fs_info *fs_info, u64 bytenr, + struct btrfs_tree_parent_check *check) + { ++ struct btrfs_eb_prealloc pa = { 0 }; + struct extent_buffer *buf = NULL; + int ret; + + ASSERT(check); + +- buf = btrfs_find_create_tree_block(fs_info, bytenr, check->owner_root, ++ buf = btrfs_find_create_tree_block(fs_info, &pa, bytenr, check->owner_root, + check->level); + if (IS_ERR(buf)) + return buf; +@@ -666,10 +669,7 @@ static struct btrfs_root *btrfs_alloc_root(struct btrfs_fs_info *fs_info, + init_waitqueue_head(&root->log_commit_wait[1]); + INIT_LIST_HEAD(&root->log_ctxs[0]); + INIT_LIST_HEAD(&root->log_ctxs[1]); +- atomic_set(&root->log_commit[0], 0); +- atomic_set(&root->log_commit[1], 0); + atomic_set(&root->log_writers, 0); +- atomic_set(&root->log_batch, 0); + refcount_set(&root->refs, 1); + atomic_set(&root->snapshot_force_cow, 0); + atomic_set(&root->nr_swapfiles, 0); +@@ -2052,7 +2052,7 @@ static int btrfs_replay_log(struct btrfs_fs_info *fs_info, + if (IS_ERR(log_tree_root->node)) { + ret = PTR_ERR(log_tree_root->node); + log_tree_root->node = NULL; +- btrfs_err(fs_info, "failed to read log tree with error: %d", ret); ++ btrfs_err(fs_info, "failed to read log tree with error: %pe", ERR_PTR(ret)); + btrfs_put_root(log_tree_root); + return ret; + } +@@ -2062,7 +2062,7 @@ static int btrfs_replay_log(struct btrfs_fs_info *fs_info, + btrfs_put_root(log_tree_root); + if (unlikely(ret)) { + ASSERT(BTRFS_FS_ERROR(fs_info) != 0); +- btrfs_err(fs_info, "failed to recover log trees with error: %d", ret); ++ btrfs_err(fs_info, "failed to recover log trees with error: %pe", ERR_PTR(ret)); + return ret; + } + +@@ -2303,8 +2303,8 @@ static int btrfs_read_roots(struct btrfs_fs_info *fs_info) + + return 0; + out: +- btrfs_warn(fs_info, "failed to read root (objectid=%llu): %d", +- location.objectid, ret); ++ btrfs_warn(fs_info, "failed to read root (objectid=%llu): %pe", ++ location.objectid, ERR_PTR(ret)); + return ret; + } + +@@ -2395,8 +2395,8 @@ static int validate_sys_chunk_array(const struct btrfs_fs_info *fs_info, + int btrfs_validate_super(const struct btrfs_fs_info *fs_info, + const struct btrfs_super_block *sb, int mirror_num) + { +- u64 nodesize = btrfs_super_nodesize(sb); +- u64 sectorsize = btrfs_super_sectorsize(sb); ++ const u32 nodesize = btrfs_super_nodesize(sb); ++ const u32 sectorsize = btrfs_super_sectorsize(sb); + int ret = 0; + const bool ignore_flags = btrfs_test_opt(fs_info, IGNORESUPERFLAGS); + +@@ -2438,24 +2438,24 @@ int btrfs_validate_super(const struct btrfs_fs_info *fs_info, + */ + if (unlikely(!is_power_of_2(sectorsize) || sectorsize < BTRFS_MIN_BLOCKSIZE || + sectorsize > BTRFS_MAX_METADATA_BLOCKSIZE)) { +- btrfs_err(fs_info, "invalid sectorsize %llu", sectorsize); ++ btrfs_err(fs_info, "invalid sectorsize %u", sectorsize); + ret = -EINVAL; + } + + if (unlikely(!btrfs_supported_blocksize(sectorsize))) { + btrfs_err(fs_info, +- "sectorsize %llu not yet supported for page size %lu", ++ "sectorsize %u not yet supported for page size %lu", + sectorsize, PAGE_SIZE); + ret = -EINVAL; + } + + if (unlikely(!is_power_of_2(nodesize) || nodesize < sectorsize || + nodesize > BTRFS_MAX_METADATA_BLOCKSIZE)) { +- btrfs_err(fs_info, "invalid nodesize %llu", nodesize); ++ btrfs_err(fs_info, "invalid nodesize %u", nodesize); + ret = -EINVAL; + } + if (unlikely(nodesize != le32_to_cpu(sb->__unused_leafsize))) { +- btrfs_err(fs_info, "invalid leafsize %u, should be %llu", ++ btrfs_err(fs_info, "invalid leafsize %u, should be %u", + le32_to_cpu(sb->__unused_leafsize), nodesize); + ret = -EINVAL; + } +@@ -2905,7 +2905,6 @@ void btrfs_init_fs_info(struct btrfs_fs_info *fs_info) + fs_info->nodesize = 4096; + fs_info->sectorsize = 4096; + fs_info->sectorsize_bits = ilog2(4096); +- fs_info->stripesize = 4096; + + /* Default compress algorithm when user does -o compress */ + fs_info->compress_type = BTRFS_COMPRESS_ZLIB; +@@ -2979,8 +2978,8 @@ static int btrfs_uuid_rescan_kthread(void *data) + ret = btrfs_uuid_tree_iterate(fs_info); + if (ret < 0) { + if (ret != -EINTR) +- btrfs_warn(fs_info, "iterating uuid_tree failed %d", +- ret); ++ btrfs_warn(fs_info, "iterating uuid_tree failed %pe", ++ ERR_PTR(ret)); + up(&fs_info->uuid_tree_rescan_sem); + return ret; + } +@@ -3083,7 +3082,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_rebuild_free_space_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to rebuild free space tree: %d", ret); ++ "failed to rebuild free space tree: %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3094,7 +3093,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_delete_free_space_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to disable free space tree: %d", ret); ++ "failed to disable free space tree: %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3105,7 +3104,8 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + */ + ret = btrfs_delete_orphan_free_space_entries(fs_info); + if (ret < 0) { +- btrfs_err(fs_info, "failed to delete orphan free space tree entries: %d", ret); ++ btrfs_err(fs_info, "failed to delete orphan free space tree entries: %pe", ++ ERR_PTR(ret)); + return ret; + } + /* +@@ -3139,7 +3139,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_recover_relocation(fs_info); + mutex_unlock(&fs_info->cleaner_mutex); + if (ret < 0) { +- btrfs_warn(fs_info, "failed to recover relocation: %d", ret); ++ btrfs_warn(fs_info, "failed to recover relocation: %pe", ERR_PTR(ret)); + return ret; + } + +@@ -3149,7 +3149,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_create_free_space_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to create free space tree: %d", ret); ++ "failed to create free space tree: %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3177,7 +3177,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_create_uuid_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to create the UUID tree %d", ret); ++ "failed to create the UUID tree %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3314,6 +3314,8 @@ static void invalidate_and_check_btree_folios(struct btrfs_fs_info *fs_info) + */ + rcu_read_lock(); + xa_for_each(&fs_info->buffer_tree, index, eb) { ++ unsigned int refs; ++ + /* Increase the ref so that the eb won't disappear. */ + if (!refcount_inc_not_zero(&eb->refs)) + continue; +@@ -3323,17 +3325,27 @@ static void invalidate_and_check_btree_folios(struct btrfs_fs_info *fs_info) + if (test_bit(EXTENT_BUFFER_READING, &eb->bflags)) + wait_on_bit_io(&eb->bflags, EXTENT_BUFFER_READING, + TASK_UNINTERRUPTIBLE); ++ /* ++ * We hold the spinlock to make sure above ++ * EXTENT_BUFFER_READING flag is cleared with the held ++ * ref dropped. ++ * Or we can hit a race window and lead to false alerts. ++ */ ++ spin_lock(&eb->refs_lock); ++ refs = refcount_read(&eb->refs); ++ spin_unlock(&eb->refs_lock); ++ + /* + * The refs threshold is 2, one held by us at the beginning + * of the loop, one for the ownership in the buffer tree. + */ +- if (unlikely(refcount_read(&eb->refs) > 2 || extent_buffer_under_io(eb))) { ++ if (unlikely(refs > 2 || extent_buffer_under_io(eb))) { + WARN_ON_ONCE(IS_ENABLED(CONFIG_BTRFS_DEBUG)); + btrfs_warn(fs_info, + "unable to release extent buffer %llu owner %llu gen %llu refs %u flags 0x%lx", + eb->start, btrfs_header_owner(eb), + btrfs_header_generation(eb), +- refcount_read(&eb->refs), eb->bflags); ++ refs, eb->bflags); + } + free_extent_buffer(eb); + rcu_read_lock(); +@@ -3355,7 +3367,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + { + u32 sectorsize; + u32 nodesize; +- u32 stripesize; + u64 generation; + u16 csum_type; + struct btrfs_super_block *disk_super; +@@ -3464,7 +3475,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + /* Set up fs_info before parsing mount options */ + nodesize = btrfs_super_nodesize(disk_super); + sectorsize = btrfs_super_sectorsize(disk_super); +- stripesize = sectorsize; + fs_info->dirty_metadata_batch = nodesize * (1 + ilog2(nr_cpu_ids)); + fs_info->delalloc_batch = sectorsize * 512 * (1 + ilog2(nr_cpu_ids)); + +@@ -3483,7 +3493,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + else + fs_info->block_max_order = calc_block_max_order(fs_info->sectorsize_bits); + fs_info->csums_per_leaf = BTRFS_MAX_ITEM_SIZE(fs_info) / fs_info->csum_size; +- fs_info->stripesize = stripesize; + fs_info->fs_devices->fs_info = fs_info; + + if (fs_info->sectorsize > PAGE_SIZE) +@@ -3549,7 +3558,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + ret = btrfs_read_sys_array(fs_info); + mutex_unlock(&fs_info->chunk_mutex); + if (ret) { +- btrfs_err(fs_info, "failed to read the system array: %d", ret); ++ btrfs_err(fs_info, "failed to read the system array: %pe", ERR_PTR(ret)); + goto fail_sb_buffer; + } + +@@ -3568,7 +3577,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + + ret = btrfs_read_chunk_tree(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to read chunk tree: %d", ret); ++ btrfs_err(fs_info, "failed to read chunk tree: %pe", ERR_PTR(ret)); + goto fail_tree_roots; + } + +@@ -3598,7 +3607,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + ret = btrfs_get_dev_zone_info_all_devices(fs_info); + if (ret) { + btrfs_err(fs_info, +- "zoned: failed to read device zone info: %d", ret); ++ "zoned: failed to read device zone info: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + +@@ -3621,72 +3630,73 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + ret = btrfs_verify_dev_extents(fs_info); + if (ret) { + btrfs_err(fs_info, +- "failed to verify dev extents against chunks: %d", +- ret); ++ "failed to verify dev extents against chunks: %pe", ++ ERR_PTR(ret)); + goto fail_block_groups; + } + ret = btrfs_recover_balance(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to recover balance: %d", ret); ++ btrfs_err(fs_info, "failed to recover balance: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_init_dev_stats(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to init dev_stats: %d", ret); ++ btrfs_err(fs_info, "failed to init dev_stats: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_init_dev_replace(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to init dev_replace: %d", ret); ++ btrfs_err(fs_info, "failed to init dev_replace: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_check_zoned_mode(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to initialize zoned mode: %d", +- ret); ++ btrfs_err(fs_info, "failed to initialize zoned mode: %pe", ++ ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_sysfs_add_fsid(fs_devices); + if (ret) { +- btrfs_err(fs_info, "failed to init sysfs fsid interface: %d", +- ret); ++ btrfs_err(fs_info, "failed to init sysfs fsid interface: %pe", ++ ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_sysfs_add_mounted(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to init sysfs interface: %d", ret); ++ btrfs_err(fs_info, "failed to init sysfs interface: %pe", ERR_PTR(ret)); + goto fail_fsdev_sysfs; + } + + ret = btrfs_init_space_info(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to initialize space info: %d", ret); ++ btrfs_err(fs_info, "failed to initialize space info: %pe", ERR_PTR(ret)); + goto fail_sysfs; + } + + ret = btrfs_read_block_groups(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to read block groups: %d", ret); ++ btrfs_err(fs_info, "failed to read block groups: %pe", ERR_PTR(ret)); + goto fail_sysfs; + } + + if (btrfs_fs_incompat(fs_info, REMAP_TREE)) { + ret = btrfs_populate_fully_remapped_bgs_list(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to populate fully_remapped_bgs list: %d", ret); ++ btrfs_err(fs_info, "failed to populate fully_remapped_bgs list: %pe", ++ ERR_PTR(ret)); + goto fail_sysfs; + } + } + + ret = btrfs_init_writeback_bio_size(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to get optimum writeback size: %d", +- ret); ++ btrfs_err(fs_info, "failed to get optimum writeback size: %pe", ++ ERR_PTR(ret)); + goto fail_sysfs; + } + +@@ -3742,7 +3752,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + fs_info->fs_root = btrfs_get_fs_root(fs_info, BTRFS_FS_TREE_OBJECTID, true); + if (IS_ERR(fs_info->fs_root)) { + ret = PTR_ERR(fs_info->fs_root); +- btrfs_err(fs_info, "failed to read fs tree: %d", ret); ++ btrfs_err(fs_info, "failed to read fs tree: %pe", ERR_PTR(ret)); + fs_info->fs_root = NULL; + goto fail_qgroup; + } +@@ -3763,7 +3773,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + btrfs_info(fs_info, "checking UUID tree"); + ret = btrfs_check_uuid_tree(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to check the UUID tree: %d", ret); ++ btrfs_err(fs_info, "failed to check the UUID tree: %pe", ERR_PTR(ret)); + close_ctree(fs_info); + return ret; + } +@@ -3879,8 +3889,8 @@ static int write_dev_supers(struct btrfs_device *device, + continue; + } else if (ret < 0) { + btrfs_err(device->fs_info, +- "couldn't get super block location for mirror %d error %d", +- i, ret); ++ "couldn't get super block location for mirror %d error %pe", ++ i, ERR_PTR(ret)); + atomic_inc(&device->sb_write_errors); + continue; + } +@@ -3898,8 +3908,8 @@ static int write_dev_supers(struct btrfs_device *device, + GFP_NOFS); + if (IS_ERR(folio)) { + btrfs_err(device->fs_info, +- "couldn't get super block page for bytenr %llu error %ld", +- bytenr, PTR_ERR(folio)); ++ "couldn't get super block page for bytenr %llu error %pe", ++ bytenr, folio); + atomic_inc(&device->sb_write_errors); + continue; + } +@@ -4381,6 +4391,21 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) + */ + flush_workqueue(fs_info->fixup_workers); + ++ /* ++ * After we entered close_ctree() autodefrag could be running and before ++ * we parked the cleaner kthread, it dirtied folios of some inode. ++ * We don't want to leave any delalloc here, it may be flushed any time ++ * after this point and result in ordered extents that create delayed ++ * iputs after flushed the ordered extent queues further below, run ++ * delayed iputs and set BTRFS_FS_STATE_NO_DELAYED_IPUT. If we are ++ * mounted with flushoncommit, then btrfs_commit_super() called below ++ * will flush delalloc and wait for ordered extents but we end up ++ * getting delayed iputs than are never run. So flush delalloc and wait ++ * for ordered extents. ++ */ ++ btrfs_start_delalloc_roots(fs_info, LONG_MAX, false); ++ btrfs_wait_ordered_roots(fs_info, U64_MAX, NULL); ++ + /* + * Handle the error fs first, as it will flush and wait for all ordered + * extents. This will generate delayed iputs, thus we want to handle +@@ -4511,7 +4536,7 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) + if (!btrfs_is_shutdown(fs_info)) { + ret = btrfs_commit_super(fs_info); + if (ret) +- btrfs_err(fs_info, "commit super block returned %d", ret); ++ btrfs_err(fs_info, "commit super block returned %pe", ERR_PTR(ret)); + } + } + +@@ -4550,6 +4575,13 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) + free_root_pointers(fs_info, true); + btrfs_free_fs_roots(fs_info); + ++ /* ++ * Drop metadata left stranded ahead of a zone write pointer while the ++ * endio workqueues are still up, so the final iput() of the btree inode ++ * below does not hang submitting a write that can no longer complete. ++ */ ++ btrfs_zoned_release_dirty_metadata(fs_info); ++ + /* + * We must make sure there is not any read request to + * submit after we stop all workers. +@@ -4998,6 +5030,7 @@ static int btrfs_cleanup_transaction(struct btrfs_fs_info *fs_info) + btrfs_assert_delayed_root_empty(fs_info); + btrfs_destroy_all_delalloc_inodes(fs_info); + btrfs_drop_all_logs(fs_info); ++ btrfs_zoned_release_dirty_metadata(fs_info); + btrfs_free_all_qgroup_pertrans(fs_info); + mutex_unlock(&fs_info->transaction_kthread_mutex); + +diff --git a/fs/btrfs/disk-io.h b/fs/btrfs/disk-io.h +--- a/fs/btrfs/disk-io.h ++++ b/fs/btrfs/disk-io.h +@@ -15,6 +15,7 @@ + struct block_device; + struct super_block; + struct extent_buffer; ++struct btrfs_eb_prealloc; + struct btrfs_device; + struct btrfs_fs_devices; + struct btrfs_fs_info; +@@ -48,6 +49,7 @@ struct extent_buffer *read_tree_block(struct btrfs_fs_info *fs_info, u64 bytenr, + struct btrfs_tree_parent_check *check); + struct extent_buffer *btrfs_find_create_tree_block( + struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 bytenr, u64 owner_root, + int level); + int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info); +diff --git a/fs/btrfs/extent-io-tree.c b/fs/btrfs/extent-io-tree.c +--- a/fs/btrfs/extent-io-tree.c ++++ b/fs/btrfs/extent-io-tree.c +@@ -334,6 +334,21 @@ static inline struct extent_state *tree_search(struct extent_io_tree *tree, u64 + return tree_search_for_insert(tree, offset, NULL, NULL); + } + ++static void validate_extent_state(const struct extent_io_tree *tree, ++ const struct extent_state *state) ++{ ++ u32 blocksize; ++ ++ if (tree->owner != IO_TREE_INODE_IO) ++ return; ++ ++ blocksize = btrfs_extent_io_tree_to_fs_info(tree)->sectorsize; ++ ASSERT(IS_ALIGNED(state->start, blocksize) && ++ IS_ALIGNED(state->end + 1, blocksize), ++ "unaligned extent state, blocksize=%u start=%llu end=%llu state=0x%x", ++ blocksize, state->start, state->end, state->state); ++} ++ + #define extent_io_tree_panic(tree, state, opname, err) \ + btrfs_panic(btrfs_extent_io_tree_to_fs_info((tree)), (err), \ + "extent io tree error on %s state start %llu end %llu", \ +@@ -429,6 +444,8 @@ static struct extent_state *insert_state(struct extent_io_tree *tree, + const u64 end = state->end + 1; + const bool try_merge = !(bits & (EXTENT_LOCK_BITS | EXTENT_BOUNDARY)); + ++ validate_extent_state(tree, state); ++ + set_state_bits(tree, state, bits, changeset); + + node = &tree->state.rb_node; +@@ -481,6 +498,8 @@ static void insert_state_fast(struct extent_io_tree *tree, + struct rb_node *parent, unsigned bits, + struct extent_changeset *changeset) + { ++ validate_extent_state(tree, state); ++ + set_state_bits(tree, state, bits, changeset); + rb_link_node(&state->rb_node, parent, node); + rb_insert_color(&state->rb_node, &tree->state); +@@ -533,6 +552,8 @@ static int split_state(struct extent_io_tree *tree, struct extent_state *orig, + } + } + ++ validate_extent_state(tree, orig); ++ validate_extent_state(tree, prealloc); + rb_link_node(&prealloc->rb_node, parent, node); + rb_insert_color(&prealloc->rb_node, &tree->state); + +diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c +--- a/fs/btrfs/extent-tree.c ++++ b/fs/btrfs/extent-tree.c +@@ -4757,7 +4757,7 @@ static noinline int find_free_extent(struct btrfs_root *root, + + /* Checks */ + ffe_ctl->search_start = round_up(ffe_ctl->found_offset, +- fs_info->stripesize); ++ fs_info->sectorsize); + + /* move on to the next group */ + if (ffe_ctl->search_start + ffe_ctl->num_bytes > +@@ -5260,10 +5260,11 @@ btrfs_init_new_buffer(struct btrfs_trans_handle *trans, struct btrfs_root *root, + enum btrfs_lock_nesting nest) + { + struct btrfs_fs_info *fs_info = root->fs_info; ++ struct btrfs_eb_prealloc pa = { 0 }; + struct extent_buffer *buf; + u64 lockdep_owner = owner; + +- buf = btrfs_find_create_tree_block(fs_info, bytenr, owner, level); ++ buf = btrfs_find_create_tree_block(fs_info, &pa, bytenr, owner, level); + if (IS_ERR(buf)) + return buf; + +@@ -5880,8 +5881,8 @@ static int maybe_drop_reference(struct btrfs_trans_handle *trans, struct btrfs_r + ret = btrfs_qgroup_trace_subtree(trans, next, generation, level - 1); + if (ret) { + btrfs_err_rl(root->fs_info, +-"error %d accounting shared subtree, quota is out of sync, rescan required", +- ret); ++"error %pe accounting shared subtree, quota is out of sync, rescan required", ++ ERR_PTR(ret)); + } + } + +@@ -5917,6 +5918,7 @@ static noinline int do_walk_down(struct btrfs_trans_handle *trans, + struct walk_control *wc) + { + struct btrfs_fs_info *fs_info = root->fs_info; ++ struct btrfs_eb_prealloc pa = { 0 }; + u64 bytenr; + u64 generation; + u64 owner_root = 0; +@@ -5939,7 +5941,7 @@ static noinline int do_walk_down(struct btrfs_trans_handle *trans, + + bytenr = btrfs_node_blockptr(path->nodes[level], path->slots[level]); + +- next = btrfs_find_create_tree_block(fs_info, bytenr, btrfs_root_id(root), ++ next = btrfs_find_create_tree_block(fs_info, &pa, bytenr, btrfs_root_id(root), + level - 1); + if (IS_ERR(next)) + return PTR_ERR(next); +@@ -6096,8 +6098,8 @@ static noinline int walk_up_proc(struct btrfs_trans_handle *trans, + ret = btrfs_qgroup_trace_leaf_items(trans, eb); + if (ret) { + btrfs_err_rl(fs_info, +- "error %d accounting leaf items, quota is out of sync, rescan required", +- ret); ++ "error %pe accounting leaf items, quota is out of sync, rescan required", ++ ERR_PTR(ret)); + } + } + } +@@ -6498,8 +6500,8 @@ int btrfs_drop_snapshot(struct btrfs_root *root, bool update_ref, bool for_reloc + ret = btrfs_qgroup_cleanup_dropped_subvolume(fs_info, rootid); + if (ret < 0) + btrfs_warn_rl(fs_info, +- "failed to cleanup qgroup 0/%llu: %d", +- rootid, ret); ++ "failed to cleanup qgroup 0/%llu: %pe", ++ rootid, ERR_PTR(ret)); + ret = 0; + } + /* +@@ -6914,8 +6916,8 @@ int btrfs_trim_fs(struct btrfs_fs_info *fs_info, struct fstrim_range *range) + + if (bg_failed) + btrfs_warn(fs_info, +- "failed to trim %llu block group(s), first error %d", +- bg_failed, bg_ret); ++ "failed to trim %llu block group(s), first error %pe", ++ bg_failed, ERR_PTR(bg_ret)); + + if (ret == -ERESTARTSYS || ret == -EINTR) + return ret; +@@ -6925,8 +6927,8 @@ int btrfs_trim_fs(struct btrfs_fs_info *fs_info, struct fstrim_range *range) + + if (dev_failed) + btrfs_warn(fs_info, +- "failed to trim %llu device(s), first error %d", +- dev_failed, dev_ret); ++ "failed to trim %llu device(s), first error %pe", ++ dev_failed, ERR_PTR(dev_ret)); + range->len = trimmed; + if (ret == -ERESTARTSYS || ret == -EINTR) + return ret; +diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c +--- a/fs/btrfs/extent_io.c ++++ b/fs/btrfs/extent_io.c +@@ -1392,6 +1392,22 @@ static void lock_extents_for_read(struct btrfs_inode *inode, u64 start, u64 end, + } + } + ++static void assert_folio_range(const struct btrfs_inode *inode, ++ u64 start, u64 end) ++{ ++ const u32 blocksize = inode->root->fs_info->sectorsize; ++ ++ /* ++ * For btrfs page cache, a folio always contains at least one block, ++ * so the range should always be block size aligned. ++ */ ++ ASSERT(IS_ALIGNED(start, blocksize) && IS_ALIGNED(end + 1, blocksize), ++ "blocksize=%u root=%lld ino=%llu start=%llu end=%llu mapping min order=%u", ++ blocksize, btrfs_root_id(inode->root), btrfs_ino(inode), ++ start, end, ++ mapping_min_folio_order(inode->vfs_inode.i_mapping)); ++} ++ + int btrfs_read_folio(struct file *file, struct folio *folio) + { + struct inode *vfs_inode = folio->mapping->host; +@@ -1407,6 +1423,7 @@ int btrfs_read_folio(struct file *file, struct folio *folio) + struct fsverity_info *vi = NULL; + int ret; + ++ assert_folio_range(inode, start, end); + lock_extents_for_read(inode, start, end, &cached_state); + if (folio_pos(folio) < i_size_read(vfs_inode)) + vi = fsverity_get_info(vfs_inode); +@@ -1698,13 +1715,13 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode, + last_finished_delalloc_end = found_start + found_len; + if (unlikely(ret < 0)) + btrfs_err_rl(fs_info, +-"failed to run delalloc range, root=%lld ino=%llu folio=%llu submit_bitmap=%*pbl start=%llu len=%u: %d", ++"failed to run delalloc range, root=%lld ino=%llu folio=%llu submit_bitmap=%*pbl start=%llu len=%u: %pe", + btrfs_root_id(inode->root), + btrfs_ino(inode), + folio_pos(folio), + blocks_per_folio, + bio_ctrl->submit_bitmap, +- found_start, found_len, ret); ++ found_start, found_len, ERR_PTR(ret)); + } else { + /* + * We've hit an error during previous delalloc range, +@@ -1914,6 +1931,7 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, + ASSERT(start >= folio_start, "start=%llu folio_start=%llu", start, folio_start); + ASSERT(end <= folio_end, "start=%llu len=%u folio_start=%llu folio_size=%zu", + start, len, folio_start, folio_size(folio)); ++ assert_folio_range(inode, folio_start, folio_end - 1); + + /* + * We are about to checksum and write out the data, so it must not be +@@ -2081,10 +2099,10 @@ static int extent_writepage(struct folio *folio, struct btrfs_bio_ctrl *bio_ctrl + return 0; + if (unlikely(ret < 0)) + btrfs_err_rl(fs_info, +-"failed to submit blocks, root=%lld inode=%llu folio=%llu submit_bitmap=%*pbl: %d", ++"failed to submit blocks, root=%lld inode=%llu folio=%llu submit_bitmap=%*pbl: %pe", + btrfs_root_id(inode->root), btrfs_ino(inode), + folio_pos(folio), blocks_per_folio, +- bio_ctrl->submit_bitmap, ret); ++ bio_ctrl->submit_bitmap, ERR_PTR(ret)); + + bio_ctrl->wbc->nr_to_write--; + +@@ -2379,14 +2397,17 @@ static struct extent_buffer *find_extent_buffer_nolock( + static void end_bbio_meta_write(struct btrfs_bio *bbio) + { + struct extent_buffer *eb = bbio->private; +- struct folio_iter fi; + + if (bbio->bio.bi_status != BLK_STS_OK) + set_btree_ioerr(eb); + +- bio_for_each_folio_all(fi, &bbio->bio) { +- btrfs_meta_folio_clear_writeback(fi.folio, eb); +- } ++ /* ++ * Clear writeback on the buffer's own folios. The bio may carry the ++ * shared zero page instead (EXTENT_BUFFER_ZONED_ZEROOUT), so iterate ++ * the extent buffer folios rather than the bio folios. ++ */ ++ for (int i = 0; i < num_extent_folios(eb); i++) ++ btrfs_meta_folio_clear_writeback(eb->folios[i], eb); + + buffer_tree_clear_mark(eb, PAGECACHE_TAG_WRITEBACK); + clear_and_wake_up_bit(EXTENT_BUFFER_WRITEBACK, &eb->bflags); +@@ -2427,7 +2448,8 @@ static noinline_for_stack void write_one_eb(struct extent_buffer *eb, + struct btrfs_fs_info *fs_info = eb->fs_info; + struct btrfs_bio *bbio; + +- prepare_eb_write(eb); ++ if (!test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) ++ prepare_eb_write(eb); + + bbio = btrfs_bio_alloc(INLINE_EXTENT_BUFFER_PAGES, + REQ_OP_WRITE | REQ_META | wbc_to_write_flags(wbc), +@@ -2447,8 +2469,21 @@ static noinline_for_stack void write_one_eb(struct extent_buffer *eb, + btrfs_meta_folio_set_writeback(folio, eb); + if (!folio_test_dirty(folio)) + wbc->nr_to_write -= folio_nr_pages(folio); +- bio_add_folio_nofail(&bbio->bio, folio, range_len, +- offset_in_folio(folio, range_start)); ++ if (test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) { ++ u32 off = 0; ++ ++ while (off < range_len) { ++ u32 add = min_t(u32, PAGE_SIZE, range_len - off); ++ ++ bio_add_folio_nofail(&bbio->bio, ++ page_folio(ZERO_PAGE(0)), ++ add, 0); ++ off += add; ++ } ++ } else { ++ bio_add_folio_nofail(&bbio->bio, folio, range_len, ++ offset_in_folio(folio, range_start)); ++ } + wbc_account_cgroup_owner(wbc, folio, range_len); + folio_unlock(folio); + } +@@ -2496,6 +2531,76 @@ void btrfs_btree_wait_writeback_range(struct btrfs_fs_info *fs_info, u64 start, + } + } + ++static int write_meta_extent_buffer(struct btrfs_eb_write_context *ctx, ++ struct writeback_control *wbc) ++{ ++ struct extent_buffer *eb = ctx->eb; ++ int ret; ++ ++ ret = btrfs_check_meta_write_pointer(eb->fs_info, ctx); ++ if (ret) ++ return ret; ++ ++ if (!lock_extent_buffer_for_io(eb, wbc)) ++ return 0; ++ ++ /* Implies write in zoned mode. */ ++ if (ctx->zoned_bg) { ++ /* Mark the last eb in the block group. */ ++ btrfs_schedule_zone_finish_bg(ctx->zoned_bg, eb); ++ ctx->zoned_bg->meta_write_pointer += eb->len; ++ } ++ write_one_eb(eb, wbc); ++ return 0; ++} ++ ++/* ++ * On a zoned filesystem, write out the currently dirty metadata extent buffers ++ * of @bg. Used to flush the active metadata/system block group before the ++ * ascending-address walk in btree_writepages(), so that walk can pivot the ++ * active block group away (finishing it) instead of aborting the commit; see ++ * the caller for details. ++ */ ++static void flush_active_meta_bg(struct address_space *mapping, ++ struct writeback_control *wbc, ++ struct btrfs_eb_write_context *ctx, ++ struct btrfs_block_group *bg) ++{ ++ struct btrfs_fs_info *fs_info = inode_to_fs_info(mapping->host); ++ unsigned long index = bg->start >> fs_info->nodesize_bits; ++ unsigned long end = (btrfs_block_group_end(bg) - 1) >> fs_info->nodesize_bits; ++ struct eb_batch batch; ++ unsigned int nr_ebs; ++ ++ ASSERT(btrfs_is_zoned(fs_info)); ++ lockdep_assert_held(&fs_info->zoned_meta_io_lock); ++ ++ eb_batch_init(&batch); ++ while (index <= end && ++ (nr_ebs = buffer_tree_get_ebs_tag(fs_info, &index, end, ++ PAGECACHE_TAG_DIRTY, &batch))) { ++ struct extent_buffer *eb; ++ ++ while ((eb = eb_batch_next(&batch)) != NULL) { ++ ctx->eb = eb; ++ ++ /* ++ * If the eb is behind the write pointer (-EBUSY, e.g. ++ * already being written by someone else) skip it and ++ * carry on. Only a hole at the write pointer (-EAGAIN) ++ * stops the flush. The main walk in btree_writepages() ++ * then deals with it. ++ */ ++ if (write_meta_extent_buffer(ctx, wbc) == -EAGAIN) { ++ eb_batch_release(&batch); ++ return; ++ } ++ } ++ eb_batch_release(&batch); ++ cond_resched(); ++ } ++} ++ + int btree_writepages(struct address_space *mapping, struct writeback_control *wbc) + { + struct btrfs_eb_write_context ctx = { .wbc = wbc }; +@@ -2531,6 +2636,22 @@ int btree_writepages(struct address_space *mapping, struct writeback_control *wb + else + tag = PAGECACHE_TAG_DIRTY; + btrfs_zoned_meta_io_lock(fs_info); ++ ++ /* ++ * On a zoned filesystem, flush the currently active metadata/system ++ * block group(s) first, under this same lock, so the ascending-address ++ * walk below can pivot the active block group instead of aborting the ++ * transaction commit with -EAGAIN. ++ */ ++ if (btrfs_is_zoned(fs_info) && wbc->sync_mode == WB_SYNC_ALL && ++ !wbc->for_sync) { ++ if (fs_info->active_meta_bg) ++ flush_active_meta_bg(mapping, wbc, &ctx, ++ fs_info->active_meta_bg); ++ if (fs_info->active_system_bg) ++ flush_active_meta_bg(mapping, wbc, &ctx, ++ fs_info->active_system_bg); ++ } + retry: + if (wbc->sync_mode == WB_SYNC_ALL) + buffer_tree_tag_for_writeback(fs_info, index, end); +@@ -2541,28 +2662,13 @@ int btree_writepages(struct address_space *mapping, struct writeback_control *wb + while ((eb = eb_batch_next(&batch)) != NULL) { + ctx.eb = eb; + +- ret = btrfs_check_meta_write_pointer(eb->fs_info, &ctx); +- if (ret) { +- if (ret == -EBUSY) +- ret = 0; +- +- if (ret) { +- done = true; +- break; +- } +- continue; ++ ret = write_meta_extent_buffer(&ctx, wbc); ++ if (ret == -EBUSY) { ++ ret = 0; ++ } else if (ret) { ++ done = true; ++ break; + } +- +- if (!lock_extent_buffer_for_io(eb, wbc)) +- continue; +- +- /* Implies write in zoned mode. */ +- if (ctx.zoned_bg) { +- /* Mark the last eb in the block group. */ +- btrfs_schedule_zone_finish_bg(ctx.zoned_bg, eb); +- ctx.zoned_bg->meta_write_pointer += eb->len; +- } +- write_one_eb(eb, wbc); + } + nr_to_write_done = (wbc->nr_to_write <= 0); + eb_batch_release(&batch); +@@ -2888,13 +2994,25 @@ void btrfs_readahead(struct readahead_control *rac) + struct extent_map *em_cached = NULL; + struct fsverity_info *vi = NULL; + ++ assert_folio_range(inode, start, end); + lock_extents_for_read(inode, start, end, &cached_state); ++ /* We don't use cached state for a bulk unlock, just free it. */ ++ btrfs_free_extent_state(cached_state); + if (start < i_size_read(vfs_inode)) + vi = fsverity_get_info(vfs_inode); +- while ((folio = readahead_folio(rac)) != NULL) +- btrfs_do_readpage(folio, &em_cached, &bio_ctrl, vi); ++ while ((folio = readahead_folio(rac)) != NULL) { ++ /* ++ * Read start and end before btrfs_do_readpage(). It unlocks the ++ * folio, so our reference might not be valid after. ++ */ ++ const u64 folio_start = folio_pos(folio); ++ const u64 folio_end = folio_start + folio_size(folio) - 1; + +- btrfs_unlock_extent(&inode->io_tree, start, end, &cached_state); ++ btrfs_do_readpage(folio, &em_cached, &bio_ctrl, vi); ++ /* Only unlock the range we locked, even if readahead expands. */ ++ if (folio_start >= start && folio_end <= end) ++ btrfs_unlock_extent(&inode->io_tree, folio_start, folio_end, NULL); ++ } + + if (em_cached) + btrfs_free_extent_map(em_cached); +@@ -3129,47 +3247,71 @@ static inline void btrfs_release_extent_buffer(struct extent_buffer *eb) + kmem_cache_free(extent_buffer_cache, eb); + } + ++/* ++ * Claim a slot to track an extent buffer in, evicting the coldest tracked buffer ++ * when the array is full. ++ * ++ * Slots fill in order until the array is full. After that a CLOCK (second ++ * chance) scan advances the hand, clearing one reference bit per step, until ++ * it lands on an unreferenced slot whose buffer is evicted. Clearing a bit per ++ * step bounds the scan to BTRFS_INHIBITED_EBS_SLOTS iterations. ++ */ ++static int btrfs_inhibit_claim_slot(struct btrfs_trans_handle *trans) ++{ ++ int slot; ++ ++ if (trans->nr_inhibited_ebs < BTRFS_INHIBITED_EBS_SLOTS) ++ return trans->nr_inhibited_ebs++; ++ ++ while (trans->inhibited_ebs_referenced & (1U << trans->inhibited_ebs_hand)) { ++ trans->inhibited_ebs_referenced &= ~(1U << trans->inhibited_ebs_hand); ++ trans->inhibited_ebs_hand = ++ (trans->inhibited_ebs_hand + 1) % BTRFS_INHIBITED_EBS_SLOTS; ++ } ++ slot = trans->inhibited_ebs_hand; ++ trans->inhibited_ebs_hand = (trans->inhibited_ebs_hand + 1) % BTRFS_INHIBITED_EBS_SLOTS; ++ ++ atomic_dec(&trans->inhibited_ebs[slot]->writeback_inhibitors); ++ free_extent_buffer(trans->inhibited_ebs[slot]); ++ ++ return slot; ++} ++ + /* + * Inhibit writeback on buffer during transaction. + * + * @trans: transaction handle that will own the inhibitor + * @eb: extent buffer to inhibit writeback on + * +- * Attempt to track this extent buffer in the transaction's inhibited set. If +- * memory allocation fails, the buffer is simply not tracked. It may be written +- * back and need re-COW, which is the original behavior. This is acceptable +- * since inhibiting writeback is an optimization. ++ * Attempt to track this extent buffer in the transaction's inhibited set. When ++ * the set is full the coldest tracked buffer is evicted instead. An untracked ++ * buffer may be written back and need re-COW, which is the original behavior. ++ * This is acceptable since inhibiting writeback is an optimization. + */ + void btrfs_inhibit_eb_writeback(struct btrfs_trans_handle *trans, struct extent_buffer *eb) + { +- unsigned long index = eb->start >> trans->fs_info->nodesize_bits; +- void *old; ++ int slot; + + lockdep_assert_held(&eb->lock); +- /* Check if already inhibited by this handle. */ +- old = xa_load(&trans->writeback_inhibited_ebs, index); +- if (old == eb) +- return; + +- /* Take reference for the xarray entry. */ ++ /* Already tracked: set its reference bit (second chance) and return. */ ++ for (int i = 0; i < trans->nr_inhibited_ebs; i++) { ++ if (trans->inhibited_ebs[i] == eb) { ++ trans->inhibited_ebs_referenced |= 1U << i; ++ return; ++ } ++ } ++ ++ slot = btrfs_inhibit_claim_slot(trans); ++ ++ /* ++ * Pin the eb while the array holds a raw pointer to it; the counter is ++ * what lock_extent_buffer_for_io() checks. ++ */ + refcount_inc(&eb->refs); +- +- old = xa_store(&trans->writeback_inhibited_ebs, index, eb, GFP_NOFS); +- if (xa_is_err(old)) { +- /* Allocation failed, just skip inhibiting this buffer. */ +- free_extent_buffer(eb); +- return; +- } +- +- /* Handle replacement of different eb at same index. */ +- if (old && old != eb) { +- struct extent_buffer *old_eb = old; +- +- atomic_dec(&old_eb->writeback_inhibitors); +- free_extent_buffer(old_eb); +- } +- + atomic_inc(&eb->writeback_inhibitors); ++ trans->inhibited_ebs[slot] = eb; ++ trans->inhibited_ebs_referenced |= 1U << slot; + } + + /* +@@ -3177,22 +3319,18 @@ void btrfs_inhibit_eb_writeback(struct btrfs_trans_handle *trans, struct extent_ + */ + void btrfs_uninhibit_all_eb_writeback(struct btrfs_trans_handle *trans) + { +- struct extent_buffer *eb; +- unsigned long index; +- +- xa_for_each(&trans->writeback_inhibited_ebs, index, eb) { +- atomic_dec(&eb->writeback_inhibitors); +- free_extent_buffer(eb); ++ for (int i = 0; i < trans->nr_inhibited_ebs; i++) { ++ atomic_dec(&trans->inhibited_ebs[i]->writeback_inhibitors); ++ free_extent_buffer(trans->inhibited_ebs[i]); + } +- xa_destroy(&trans->writeback_inhibited_ebs); ++ trans->nr_inhibited_ebs = 0; ++ trans->inhibited_ebs_referenced = 0; ++ trans->inhibited_ebs_hand = 0; + } + +-static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info, +- u64 start) ++static void init_extent_buffer(struct btrfs_fs_info *fs_info, ++ struct extent_buffer *eb, u64 start) + { +- struct extent_buffer *eb = NULL; +- +- eb = kmem_cache_zalloc(extent_buffer_cache, GFP_NOFS|__GFP_NOFAIL); + eb->start = start; + eb->len = fs_info->nodesize; + eb->fs_info = fs_info; +@@ -3205,7 +3343,15 @@ static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info + refcount_set(&eb->refs, 1); + + ASSERT(eb->len <= BTRFS_MAX_METADATA_BLOCKSIZE); ++} + ++static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info, ++ u64 start) ++{ ++ struct extent_buffer *eb; ++ ++ eb = kmem_cache_zalloc(extent_buffer_cache, GFP_NOFS | __GFP_NOFAIL); ++ init_extent_buffer(fs_info, eb, start); + return eb; + } + +@@ -3502,7 +3648,7 @@ static bool check_eb_alignment(struct btrfs_fs_info *fs_info, u64 start) + * The caller needs to free the existing folios and retry using the same order. + */ + static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, +- struct btrfs_folio_state *prealloc, ++ struct btrfs_eb_prealloc *pa, + struct extent_buffer **found_eb_ret) + { + +@@ -3524,6 +3670,7 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + if (!ret) + goto finish; + ++ /* ret == -EEXIST: a folio already lives at this index. */ + existing_folio = filemap_lock_folio(mapping, index + i); + /* The page cache only exists for a very short time, just retry. */ + if (IS_ERR(existing_folio)) +@@ -3532,7 +3679,27 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + /* For now, we should only have single-page folios for btree inode. */ + ASSERT(folio_nr_pages(existing_folio) == 1); + ++ /* ++ * TODO: Special handling for a corner case where the order of ++ * folios mismatch between the new eb and filemap. ++ * ++ * This happens when: ++ * ++ * - the new eb is using higher order folio ++ * ++ * - the filemap is still using 0-order folios for the range ++ * This can happen at the previous eb allocation, and we don't ++ * have higher order folio for the call. ++ * ++ * - the existing eb has already been freed ++ * ++ * In this case, we have to free the existing folios first, and ++ * re-allocate using the same order. ++ * Thankfully this is not going to happen yet, as we're still ++ * using 0-order folios. ++ */ + if (folio_size(existing_folio) != eb->folio_size) { ++ DEBUG_WARN("folio order mismatch between new eb and filemap"); + folio_unlock(existing_folio); + folio_put(existing_folio); + return -EAGAIN; +@@ -3563,8 +3730,10 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + eb->folio_size = folio_size(eb->folios[i]); + eb->folio_shift = folio_shift(eb->folios[i]); + /* Should not fail, as we have preallocated the memory. */ +- ret = attach_extent_buffer_folio(eb, eb->folios[i], prealloc); ++ ret = attach_extent_buffer_folio(eb, eb->folios[i], pa->bfs); + ASSERT(!ret); ++ /* The subpage state, if any, is now attached to the folio or freed. */ ++ pa->bfs = NULL; + /* + * To inform we have an extra eb under allocation, so that + * detach_extent_buffer_page() won't release the folio private when the +@@ -3579,13 +3748,104 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + return 0; + } + ++/* ++ * Allocate the extent_buffer, its folios, and btrfs_folio_state, if needed. ++ * ++ * @pa: The holder struct to do the allocation in. ++ * @nowait: Whether to do a speculative GFP_NOWAIT allocation while holding locks. ++ * ++ * Return 0 on success and a negative errno otherwise. On failure, pa->eb/bfs ++ * will be NULL. If @nowait=true, then on ENOMEM, mark @pa->needs_prealloc and ++ * return -EAGAIN to signal the caller to unlock and retry. ++ */ ++int btrfs_init_eb_prealloc(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, bool nowait) ++{ ++ gfp_t gfp = nowait ? GFP_NOWAIT : GFP_NOFS | __GFP_NOFAIL; ++ int ret; ++ ++ ASSERT(!pa->eb, "unexpected non-null eb: %p", pa->eb); ++ ASSERT(!pa->bfs, "unexpected non-null bfs: %p", pa->bfs); ++ pa->needs_prealloc = false; ++ ++ pa->eb = kmem_cache_zalloc(extent_buffer_cache, gfp); ++ if (!pa->eb) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ /* alloc_eb_folio_array() needs len; init_extent_buffer() sets it again later. */ ++ pa->eb->len = fs_info->nodesize; ++ ++ /* ++ * Preallocate folio private for subpage case, so that we won't ++ * allocate memory with i_private_lock nor page lock hold. ++ * ++ * The memory will be freed by attach_extent_buffer_page() or freed ++ * manually if we exit earlier. ++ */ ++ if (btrfs_meta_is_subpage(fs_info)) { ++ pa->bfs = btrfs_alloc_folio_state(fs_info, PAGE_SIZE, ++ BTRFS_SUBPAGE_METADATA, gfp); ++ if (IS_ERR(pa->bfs)) { ++ ret = PTR_ERR(pa->bfs); ++ pa->bfs = NULL; ++ goto free_eb; ++ } ++ } ++ ++ /* ++ * Allocate pages without attaching them. Caller is ultimately responsible ++ * for attaching the folios to the mapping with attach_eb_folio_to_filemap(). ++ */ ++ ret = alloc_eb_folio_array(pa->eb, gfp | __GFP_MOVABLE); ++ if (ret < 0) ++ goto free_bfs; ++ ++ return 0; ++ ++free_bfs: ++ btrfs_free_folio_state(pa->bfs); ++ pa->bfs = NULL; ++free_eb: ++ kmem_cache_free(extent_buffer_cache, pa->eb); ++ pa->eb = NULL; ++out: ++ if (nowait && ret == -ENOMEM) { ++ pa->needs_prealloc = true; ++ ret = -EAGAIN; ++ } ++ return ret; ++} ++ ++/* ++ * Used to cleanup a btrfs_eb_prealloc which had its contents allocated but ++ * folios not yet attached and eb/bfs consumed, and refs still 0. ++ * ++ * Safe to call on a fully used btrfs_eb_prealloc as the internal structs will ++ * be null once they are owned by the context using them. ++ */ ++void btrfs_free_eb_prealloc(struct btrfs_eb_prealloc *pa) ++{ ++ if (!pa->eb) ++ return; ++ ++ for (int i = 0; i < num_extent_pages(pa->eb); i++) { ++ if (pa->eb->folios[i]) ++ folio_put(pa->eb->folios[i]); ++ } ++ btrfs_free_folio_state(pa->bfs); ++ kmem_cache_free(extent_buffer_cache, pa->eb); ++ pa->eb = NULL; ++ pa->bfs = NULL; ++} ++ + struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 start, u64 owner_root, int level) + { + int attached = 0; + struct extent_buffer *eb; + struct extent_buffer *existing_eb = NULL; +- struct btrfs_folio_state *prealloc = NULL; + u64 lockdep_owner = owner_root; + bool page_contig = true; + bool uptodate = true; +@@ -3609,9 +3869,14 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, + if (eb) + return eb; + +- eb = __alloc_extent_buffer(fs_info, start); +- if (!eb) +- return ERR_PTR(-ENOMEM); ++ if (!pa->eb) { ++ ret = btrfs_init_eb_prealloc(fs_info, pa, pa->supports_nowait); ++ if (ret) ++ return ERR_PTR(ret); ++ } ++ eb = pa->eb; ++ pa->eb = NULL; ++ init_extent_buffer(fs_info, eb, start); + + /* + * The reloc trees are just snapshots, so we need them to appear to be +@@ -3622,66 +3887,18 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, + + btrfs_set_buffer_lockdep_class(lockdep_owner, eb, level); + +- /* +- * Preallocate folio private for subpage case, so that we won't +- * allocate memory with i_private_lock nor page lock hold. +- * +- * The memory will be freed by attach_extent_buffer_page() or freed +- * manually if we exit earlier. +- */ +- if (btrfs_meta_is_subpage(fs_info)) { +- prealloc = btrfs_alloc_folio_state(fs_info, PAGE_SIZE, BTRFS_SUBPAGE_METADATA); +- if (IS_ERR(prealloc)) { +- ret = PTR_ERR(prealloc); +- goto out; +- } +- } +- +-reallocate: +- /* +- * Allocate all pages first. These will be attached to btree_inode->i_mapping +- * below (added to LRU, served by btree_migrate_folio), so request +- * __GFP_MOVABLE so the page allocator places them in MOVABLE pageblocks. +- */ +- ret = alloc_eb_folio_array(eb, GFP_NOFS | __GFP_NOFAIL | __GFP_MOVABLE); +- if (ret < 0) { +- btrfs_free_folio_state(prealloc); +- goto out; +- } +- + /* Attach all pages to the filemap. */ + for (int i = 0; i < num_extent_folios(eb); i++) { + struct folio *folio; + +- ret = attach_eb_folio_to_filemap(eb, i, prealloc, &existing_eb); ++ ret = attach_eb_folio_to_filemap(eb, i, pa, &existing_eb); + if (ret > 0) { + ASSERT(existing_eb); + goto out; + } +- +- /* +- * TODO: Special handling for a corner case where the order of +- * folios mismatch between the new eb and filemap. +- * +- * This happens when: +- * +- * - the new eb is using higher order folio +- * +- * - the filemap is still using 0-order folios for the range +- * This can happen at the previous eb allocation, and we don't +- * have higher order folio for the call. +- * +- * - the existing eb has already been freed +- * +- * In this case, we have to free the existing folios first, and +- * re-allocate using the same order. +- * Thankfully this is not going to happen yet, as we're still +- * using 0-order folios. +- */ +- if (unlikely(ret == -EAGAIN)) { +- DEBUG_WARN("folio order mismatch between new eb and filemap"); +- goto reallocate; +- } ++ /* -EAGAIN: folio order mismatch, unreachable with 0-order folios. */ ++ if (ret < 0) ++ goto out; + attached++; + + /* +@@ -3758,6 +3975,10 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, + out: + WARN_ON(!refcount_dec_and_test(&eb->refs)); + ++ /* Attach hands off pa->bfs; free it if we bailed first. */ ++ btrfs_free_folio_state(pa->bfs); ++ pa->bfs = NULL; ++ + /* + * Any attached folios need to be detached before we unlock them. This + * is because when we're inserting our new folios into the mapping, and +@@ -3842,12 +4063,31 @@ static int release_extent_buffer(struct extent_buffer *eb) + return 0; + } + +-void free_extent_buffer(struct extent_buffer *eb) ++static void clear_extent_buffer_reading(struct extent_buffer *eb) ++{ ++ clear_and_wake_up_bit(EXTENT_BUFFER_READING, &eb->bflags); ++} ++ ++static void free_extent_buffer_clear_reading(struct extent_buffer *eb, ++ bool clear_reading) + { + int refs; ++ + if (!eb) + return; + ++ /* ++ * We want to clear EXTENT_BUFFER_READING flag and decrease refs ++ * in the same critical section. ++ * This will make sure invalidate_and_check_btree_folios() won't ++ * see an eb with EXTENT_BUFFER_READING cleared but refs not yet ++ * decreased. ++ */ ++ if (clear_reading) { ++ spin_lock(&eb->refs_lock); ++ clear_extent_buffer_reading(eb); ++ } ++ + refs = refcount_read(&eb->refs); + while (1) { + if (test_bit(EXTENT_BUFFER_UNMAPPED, &eb->bflags)) { +@@ -3858,11 +4098,16 @@ void free_extent_buffer(struct extent_buffer *eb) + } + + /* Optimization to avoid locking eb->refs_lock. */ +- if (atomic_try_cmpxchg(&eb->refs.refs, &refs, refs - 1)) ++ if (atomic_try_cmpxchg(&eb->refs.refs, &refs, refs - 1)) { ++ if (clear_reading) ++ spin_unlock(&eb->refs_lock); + return; ++ } + } + +- spin_lock(&eb->refs_lock); ++ if (!clear_reading) ++ spin_lock(&eb->refs_lock); ++ + if (refcount_read(&eb->refs) == 2 && + test_bit(EXTENT_BUFFER_STALE, &eb->bflags) && + !extent_buffer_under_io(eb) && +@@ -3876,6 +4121,11 @@ void free_extent_buffer(struct extent_buffer *eb) + release_extent_buffer(eb); + } + ++void free_extent_buffer(struct extent_buffer *eb) ++{ ++ return free_extent_buffer_clear_reading(eb, false); ++} ++ + void free_extent_buffer_stale(struct extent_buffer *eb) + { + if (!eb) +@@ -3890,6 +4140,32 @@ void free_extent_buffer_stale(struct extent_buffer *eb) + release_extent_buffer(eb); + } + ++static void clear_extent_buffer_dirty(struct extent_buffer *eb) ++{ ++ struct btrfs_fs_info *fs_info = eb->fs_info; ++ ++ if (!test_and_clear_bit(EXTENT_BUFFER_DIRTY, &eb->bflags)) ++ return; ++ ++ buffer_tree_clear_mark(eb, PAGECACHE_TAG_DIRTY); ++ percpu_counter_add_batch(&fs_info->dirty_metadata_bytes, -(s64)eb->len, ++ fs_info->dirty_metadata_batch); ++ ++ for (int i = 0; i < num_extent_folios(eb); i++) { ++ struct folio *folio = eb->folios[i]; ++ bool last; ++ ++ if (!folio_test_dirty(folio)) ++ continue; ++ folio_lock(folio); ++ last = btrfs_meta_folio_clear_and_test_dirty(folio, eb); ++ if (last) ++ btrfs_clear_folio_dirty_tag(folio); ++ folio_unlock(folio); ++ } ++ WARN_ON(refcount_read(&eb->refs) == 0); ++} ++ + void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans, + struct extent_buffer *eb) + { +@@ -3914,26 +4190,42 @@ void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans, + return; + } + +- if (!test_and_clear_bit(EXTENT_BUFFER_DIRTY, &eb->bflags)) ++ clear_extent_buffer_dirty(eb); ++} ++ ++/* ++ * On a zoned filesystem a freed tree block is kept dirty and flagged as ++ * EXTENT_BUFFER_ZONED_ZEROOUT so a later writeback zeroes it out and advances ++ * the zone write pointer. Such buffers still dirty when the filesystem is torn ++ * down can no longer be written back and are stale; if left dirty they hang the ++ * final iput() of the btree inode. Drop their dirty state, and the deferred ++ * zero-out along with it. ++ */ ++void btrfs_zoned_release_dirty_metadata(struct btrfs_fs_info *fs_info) ++{ ++ struct eb_batch batch; ++ unsigned long index = 0; ++ ++ if (!btrfs_is_zoned(fs_info)) + return; + +- buffer_tree_clear_mark(eb, PAGECACHE_TAG_DIRTY); +- percpu_counter_add_batch(&fs_info->dirty_metadata_bytes, -(s64)eb->len, +- fs_info->dirty_metadata_batch); ++ btrfs_zoned_meta_io_lock(fs_info); ++ eb_batch_init(&batch); ++ while (buffer_tree_get_ebs_tag(fs_info, &index, ULONG_MAX, ++ PAGECACHE_TAG_DIRTY, &batch)) { ++ struct extent_buffer *eb; + +- for (int i = 0; i < num_extent_folios(eb); i++) { +- struct folio *folio = eb->folios[i]; +- bool last; +- +- if (!folio_test_dirty(folio)) +- continue; +- folio_lock(folio); +- last = btrfs_meta_folio_clear_and_test_dirty(folio, eb); +- if (last) +- btrfs_clear_folio_dirty_tag(folio); +- folio_unlock(folio); ++ while ((eb = eb_batch_next(&batch)) != NULL) { ++ btrfs_tree_lock(eb); ++ if (test_and_clear_bit(EXTENT_BUFFER_ZONED_ZEROOUT, ++ &eb->bflags)) ++ clear_extent_buffer_dirty(eb); ++ btrfs_tree_unlock(eb); ++ } ++ eb_batch_release(&batch); ++ cond_resched(); + } +- WARN_ON(refcount_read(&eb->refs) == 0); ++ btrfs_zoned_meta_io_unlock(fs_info); + } + + void set_extent_buffer_dirty(struct extent_buffer *eb) +@@ -4001,11 +4293,6 @@ void set_extent_buffer_uptodate(struct extent_buffer *eb) + btrfs_meta_folio_set_uptodate(eb->folios[i], eb); + } + +-static void clear_extent_buffer_reading(struct extent_buffer *eb) +-{ +- clear_and_wake_up_bit(EXTENT_BUFFER_READING, &eb->bflags); +-} +- + static void end_bbio_meta_read(struct btrfs_bio *bbio) + { + struct extent_buffer *eb = bbio->private; +@@ -4029,8 +4316,7 @@ static void end_bbio_meta_read(struct btrfs_bio *bbio) + else + clear_extent_buffer_uptodate(eb); + +- clear_extent_buffer_reading(eb); +- free_extent_buffer(eb); ++ free_extent_buffer_clear_reading(eb, true); + + bio_put(&bbio->bio); + } +@@ -4789,6 +5075,8 @@ void btrfs_readahead_tree_block(struct btrfs_fs_info *fs_info, + .level = level, + .transid = gen + }; ++ /* Readahead is best effort so prefer to fail rather than block in reclaim. */ ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + struct extent_buffer *eb; + int ret; + +@@ -4797,7 +5085,7 @@ void btrfs_readahead_tree_block(struct btrfs_fs_info *fs_info, + check.has_first_key = true; + } + +- eb = btrfs_find_create_tree_block(fs_info, bytenr, owner_root, level); ++ eb = btrfs_find_create_tree_block(fs_info, &pa, bytenr, owner_root, level); + if (IS_ERR(eb)) + return; + +diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h +--- a/fs/btrfs/extent_io.h ++++ b/fs/btrfs/extent_io.h +@@ -119,6 +119,25 @@ struct extent_buffer { + #endif + }; + ++/* ++ * Wrapper struct for managing preallocating an extent_buffer, its folios and a ++ * btrfs_folio_state if needed. ++ * ++ * Only used to mediate allocation, do not refer to the eb directly if not ++ * returned from a successful eb allocating API. ++ * ++ * The eb folios and bfs should generally not be fully attached, except briefly ++ * before they are NULLed in the struct after successful attachment. ++ */ ++struct btrfs_eb_prealloc { ++ struct extent_buffer *eb; ++ struct btrfs_folio_state *bfs; ++ /* eb alloc may use GFP_NOWAIT; caller can drop locks and retry. */ ++ bool supports_nowait; ++ /* GFP_NOWAIT eb alloc failed; preallocate again and retry. */ ++ bool needs_prealloc; ++}; ++ + struct btrfs_eb_write_context { + struct writeback_control *wbc; + struct extent_buffer *eb; +@@ -271,7 +290,11 @@ int set_folio_extent_mapped(struct folio *folio); + void clear_folio_extent_mapped(struct folio *folio); + + struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 start, u64 owner_root, int level); ++int btrfs_init_eb_prealloc(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, bool nowait); ++void btrfs_free_eb_prealloc(struct btrfs_eb_prealloc *pa); + struct extent_buffer *alloc_dummy_extent_buffer(struct btrfs_fs_info *fs_info, + u64 start); + struct extent_buffer *btrfs_clone_extent_buffer(const struct extent_buffer *src); +@@ -393,6 +416,7 @@ void extent_clear_unlock_delalloc(struct btrfs_inode *inode, u64 start, u64 end, + u32 bits_to_clear, unsigned long page_ops); + void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans, + struct extent_buffer *buf); ++void btrfs_zoned_release_dirty_metadata(struct btrfs_fs_info *fs_info); + + static inline void btrfs_clear_folio_dirty_tag(struct folio *folio) + { +diff --git a/fs/btrfs/fiemap.c b/fs/btrfs/fiemap.c +--- a/fs/btrfs/fiemap.c ++++ b/fs/btrfs/fiemap.c +@@ -641,7 +641,7 @@ static int extent_fiemap(struct btrfs_inode *inode, + u64 prev_extent_end; + u64 range_start; + u64 range_end; +- const u64 sectorsize = inode->root->fs_info->sectorsize; ++ const u32 sectorsize = inode->root->fs_info->sectorsize; + bool stopped = false; + int ret; + +@@ -660,7 +660,7 @@ static int extent_fiemap(struct btrfs_inode *inode, + range_end = round_up(start + len, sectorsize); + prev_extent_end = range_start; + +- btrfs_lock_extent(&inode->io_tree, range_start, range_end, &cached_state); ++ btrfs_lock_extent(&inode->io_tree, range_start, range_end - 1, &cached_state); + + ret = fiemap_find_last_extent_offset(inode, path, &last_extent_end); + if (ret < 0) +@@ -840,7 +840,7 @@ static int extent_fiemap(struct btrfs_inode *inode, + } + + out_unlock: +- btrfs_unlock_extent(&inode->io_tree, range_start, range_end, &cached_state); ++ btrfs_unlock_extent(&inode->io_tree, range_start, range_end - 1, &cached_state); + + if (ret == BTRFS_FIEMAP_FLUSH_CACHE) { + btrfs_release_path(path); +diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c +--- a/fs/btrfs/file.c ++++ b/fs/btrfs/file.c +@@ -875,70 +875,64 @@ static noinline int prepare_one_folio(struct inode *inode, struct folio **folio_ + + /* + * Locks the extent and properly waits for data=ordered extents to finish +- * before allowing the folios to be modified if need. ++ * before allowing the folios to be modified. + * + * Return: +- * 1 - the extent is locked +- * 0 - the extent is not locked, and everything is OK ++ * 0 - the extent is locked + * -EAGAIN - need to prepare the folios again + */ + static noinline int +-lock_and_cleanup_extent_if_need(struct btrfs_inode *inode, struct folio *folio, +- loff_t pos, size_t write_bytes, +- u64 *lockstart, u64 *lockend, bool nowait, +- struct extent_state **cached_state) ++lock_and_cleanup_extent(struct btrfs_inode *inode, struct folio *folio, ++ loff_t pos, size_t write_bytes, ++ u64 *lockstart, u64 *lockend, bool nowait, ++ struct extent_state **cached_state) + { + struct btrfs_fs_info *fs_info = inode->root->fs_info; ++ struct btrfs_ordered_extent *ordered; + u64 start_pos; + u64 last_pos; +- int ret = 0; + + start_pos = round_down(pos, fs_info->sectorsize); + last_pos = round_up(pos + write_bytes, fs_info->sectorsize) - 1; + +- if (start_pos < inode->vfs_inode.i_size) { +- struct btrfs_ordered_extent *ordered; +- +- if (nowait) { +- if (!btrfs_try_lock_extent(&inode->io_tree, start_pos, +- last_pos, cached_state)) { +- folio_unlock(folio); +- folio_put(folio); +- return -EAGAIN; +- } +- } else { +- btrfs_lock_extent(&inode->io_tree, start_pos, last_pos, +- cached_state); +- } +- +- ordered = btrfs_lookup_ordered_range(inode, start_pos, +- last_pos - start_pos + 1); +- if (ordered && +- ordered->file_offset + ordered->num_bytes > start_pos && +- ordered->file_offset <= last_pos) { +- btrfs_unlock_extent(&inode->io_tree, start_pos, last_pos, +- cached_state); ++ if (nowait) { ++ if (!btrfs_try_lock_extent(&inode->io_tree, start_pos, ++ last_pos, cached_state)) { + folio_unlock(folio); + folio_put(folio); +- btrfs_start_ordered_extent(ordered); +- btrfs_put_ordered_extent(ordered); + return -EAGAIN; + } +- if (ordered) +- btrfs_put_ordered_extent(ordered); +- +- *lockstart = start_pos; +- *lockend = last_pos; +- ret = 1; ++ } else { ++ btrfs_lock_extent(&inode->io_tree, start_pos, last_pos, ++ cached_state); + } + ++ ordered = btrfs_lookup_ordered_range(inode, start_pos, ++ last_pos - start_pos + 1); ++ if (ordered && ++ ordered->file_offset + ordered->num_bytes > start_pos && ++ ordered->file_offset <= last_pos) { ++ btrfs_unlock_extent(&inode->io_tree, start_pos, last_pos, ++ cached_state); ++ folio_unlock(folio); ++ folio_put(folio); ++ btrfs_start_ordered_extent(ordered); ++ btrfs_put_ordered_extent(ordered); ++ return -EAGAIN; ++ } ++ if (ordered) ++ btrfs_put_ordered_extent(ordered); ++ ++ *lockstart = start_pos; ++ *lockend = last_pos; ++ + /* + * We should be called after prepare_one_folio() which should have locked + * all pages in the range. + */ + WARN_ON(!folio_test_locked(folio)); + +- return ret; ++ return 0; + } + + /* +@@ -1195,7 +1189,6 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + const u64 reserved_start = round_down(start, fs_info->sectorsize); + u64 reserved_len; + struct folio *folio = NULL; +- int extents_locked; + u64 lockstart; + u64 lockend; + bool only_release_metadata = false; +@@ -1253,18 +1246,16 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + reserved_len = last_block - reserved_start; + } + +- extents_locked = lock_and_cleanup_extent_if_need(inode, folio, start, +- write_bytes, &lockstart, +- &lockend, nowait, +- &cached_state); +- if (extents_locked < 0) { +- if (!nowait && extents_locked == -EAGAIN) ++ ret = lock_and_cleanup_extent(inode, folio, start, write_bytes, ++ &lockstart, &lockend, nowait, &cached_state); ++ if (ret < 0) { ++ if (!nowait) + goto again; + + btrfs_delalloc_release_extents(inode, reserved_len); + release_space(inode, *data_reserved, reserved_start, reserved_len, + only_release_metadata); +- return extents_locked; ++ return ret; + } + + copied = copy_folio_from_iter_atomic(folio, offset_in_folio(folio, start), +@@ -1288,11 +1279,8 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + + /* No copied bytes, unlock, release reserved space and exit. */ + if (copied == 0) { +- if (extents_locked) +- btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, +- &cached_state); +- else +- btrfs_free_extent_state(cached_state); ++ btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, ++ &cached_state); + btrfs_delalloc_release_extents(inode, reserved_len); + release_space(inode, *data_reserved, reserved_start, reserved_len, + only_release_metadata); +@@ -1311,17 +1299,7 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + + ret = btrfs_dirty_folio(inode, folio, start, copied, &cached_state, + only_release_metadata); +- /* +- * If we have not locked the extent range, because the range's start +- * offset is >= i_size, we might still have a non-NULL cached extent +- * state, acquired while marking the extent range as delalloc through +- * btrfs_dirty_page(). Therefore free any possible cached extent state +- * to avoid a memory leak. +- */ +- if (extents_locked) +- btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, &cached_state); +- else +- btrfs_free_extent_state(cached_state); ++ btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, &cached_state); + + btrfs_delalloc_release_extents(inode, reserved_len); + if (ret) { +@@ -1595,8 +1573,6 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) + else + btrfs_inode_lock(inode, BTRFS_ILOCK_MMAP); + +- atomic_inc(&root->log_batch); +- + /* + * Before we acquired the inode's lock and the mmap lock, someone may + * have dirtied more pages in the target range. We need to make sure +@@ -1679,8 +1655,6 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) + if (ret) + goto out_release_extents; + +- atomic_inc(&root->log_batch); +- + if (skip_inode_logging(&ctx)) { + /* + * We've had everything committed since the last time we were +@@ -2683,8 +2657,8 @@ static int btrfs_punch_hole(struct file *file, loff_t offset, loff_t len) + + lockstart = round_up(offset, fs_info->sectorsize); + lockend = round_down(offset + len, fs_info->sectorsize) - 1; +- same_block = (BTRFS_BYTES_TO_BLKS(fs_info, offset)) +- == (BTRFS_BYTES_TO_BLKS(fs_info, offset + len - 1)); ++ same_block = (offset >> fs_info->sectorsize_bits) == ++ ((offset + len - 1) >> fs_info->sectorsize_bits); + /* + * Only do this if we are in the same block and we aren't doing the + * entire block. +@@ -2888,7 +2862,7 @@ enum { + static int btrfs_zero_range_check_range_boundary(struct btrfs_inode *inode, + u64 offset) + { +- const u64 sectorsize = inode->root->fs_info->sectorsize; ++ const u32 sectorsize = inode->root->fs_info->sectorsize; + struct extent_map *em; + int ret; + +@@ -2918,7 +2892,7 @@ static int btrfs_zero_range(struct inode *inode, + struct extent_changeset *data_reserved = NULL; + int ret; + u64 alloc_hint = 0; +- const u64 sectorsize = fs_info->sectorsize; ++ const u32 sectorsize = fs_info->sectorsize; + const u64 orig_start = offset; + const u64 orig_end = offset + len - 1; + u64 alloc_start = round_down(offset, sectorsize); +@@ -2967,8 +2941,8 @@ static int btrfs_zero_range(struct inode *inode, + } + btrfs_free_extent_map(em); + +- if (BTRFS_BYTES_TO_BLKS(fs_info, offset) == +- BTRFS_BYTES_TO_BLKS(fs_info, offset + len - 1)) { ++ if ((offset >> fs_info->sectorsize_bits) == ++ ((offset + len - 1) >> fs_info->sectorsize_bits)) { + em = btrfs_get_extent(BTRFS_I(inode), NULL, alloc_start, sectorsize); + if (IS_ERR(em)) { + ret = PTR_ERR(em); +diff --git a/fs/btrfs/fs.c b/fs/btrfs/fs.c +--- a/fs/btrfs/fs.c ++++ b/fs/btrfs/fs.c +@@ -127,20 +127,9 @@ void btrfs_csum_final(struct btrfs_csum_ctx *ctx, u8 *out) + } + + /* +- * We support the following block sizes for all systems: +- * +- * - 4K +- * This is the most common block size. For PAGE SIZE > 4K cases the subpage +- * mode is used. +- * +- * - PAGE_SIZE +- * The straightforward block size to support. +- * +- * And extra support for the following block sizes based on the kernel config: +- * +- * - MIN_BLOCKSIZE +- * This is either 4K (regular builds) or 2K (debug builds) +- * This allows testing subpage routines on x86_64. ++ * For regular builds, any block size <= page size is supported. ++ * For experimental builds, any block size between BTRFS_MIN_BLOCKSIZE ++ * and BTRFS_MAX_BLOCKSIZE (inclusive) is supported. + */ + bool __attribute_const__ btrfs_supported_blocksize(u32 blocksize) + { +@@ -148,7 +137,7 @@ bool __attribute_const__ btrfs_supported_blocksize(u32 blocksize) + ASSERT(is_power_of_2(blocksize) && blocksize >= BTRFS_MIN_BLOCKSIZE && + blocksize <= BTRFS_MAX_BLOCKSIZE); + +- if (blocksize == PAGE_SIZE || blocksize == SZ_4K || blocksize == BTRFS_MIN_BLOCKSIZE) ++ if (blocksize <= PAGE_SIZE) + return true; + #ifdef CONFIG_BTRFS_EXPERIMENTAL + /* +diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h +--- a/fs/btrfs/fs.h ++++ b/fs/btrfs/fs.h +@@ -289,7 +289,8 @@ enum { + BTRFS_MOUNT_IGNOREBADROOTS | \ + BTRFS_MOUNT_IGNOREDATACSUMS | \ + BTRFS_MOUNT_IGNOREMETACSUMS | \ +- BTRFS_MOUNT_IGNORESUPERFLAGS) ++ BTRFS_MOUNT_IGNORESUPERFLAGS | \ ++ BTRFS_MOUNT_USEBACKUPROOT) + + /* + * Compat flags that we support. If any incompat flags are set other than the +@@ -890,7 +891,6 @@ struct btrfs_fs_info { + u32 sectorsize_bits; + u32 block_min_order; + u32 block_max_order; +- u32 stripesize; + u32 writeback_bio_size; + u32 csum_size; + u32 csums_per_leaf; +@@ -1060,8 +1060,6 @@ static inline u64 btrfs_calc_metadata_size(const struct btrfs_fs_info *fs_info, + #define BTRFS_MAX_EXTENT_ITEM_SIZE(r) ((BTRFS_LEAF_DATA_SIZE(r->fs_info) >> 4) - \ + sizeof(struct btrfs_item)) + +-#define BTRFS_BYTES_TO_BLKS(fs_info, bytes) ((bytes) >> (fs_info)->sectorsize_bits) +- + static inline bool btrfs_is_zoned(const struct btrfs_fs_info *fs_info) + { + return IS_ENABLED(CONFIG_BLK_DEV_ZONED) && fs_info->zone_size > 0; +diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -250,8 +250,8 @@ static void print_data_reloc_error(const struct btrfs_inode *inode, u64 file_off + + ret = extent_from_logical(fs_info, logical, &path, &found_key, &flags); + if (ret < 0) { +- btrfs_err_rl(fs_info, "failed to lookup extent item for logical %llu: %d", +- logical, ret); ++ btrfs_err_rl(fs_info, "failed to lookup extent item for logical %llu: %pe", ++ logical, ERR_PTR(ret)); + return; + } + eb = path.nodes[0]; +@@ -869,7 +869,7 @@ static void compress_file_range(struct btrfs_work *work) + struct btrfs_inode *inode = async_chunk->inode; + struct btrfs_fs_info *fs_info = inode->root->fs_info; + struct compressed_bio *cb = NULL; +- u64 blocksize = fs_info->sectorsize; ++ const u32 blocksize = fs_info->sectorsize; + u64 start = async_chunk->start; + u64 end = async_chunk->end; + u64 actual_end; +@@ -1019,9 +1019,10 @@ static void submit_uncompressed_range(struct btrfs_inode *inode, + btrfs_folio_end_lock(inode->root->fs_info, locked_folio, + start, async_extent->ram_size); + btrfs_err_rl(inode->root->fs_info, +- "%s failed, root=%llu inode=%llu start=%llu len=%llu: %d", ++ "%s failed, root=%llu inode=%llu start=%llu len=%llu: %pe", + __func__, btrfs_root_id(inode->root), +- btrfs_ino(inode), start, async_extent->ram_size, ret); ++ btrfs_ino(inode), start, async_extent->ram_size, ++ ERR_PTR(ret)); + } + } + +@@ -1508,10 +1509,10 @@ static noinline int cow_file_range(struct btrfs_inode *inode, + end - start - cur_alloc_size + 1, NULL); + } + btrfs_err(fs_info, +-"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu cur_alloc_size=%u: %d", ++"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu cur_alloc_size=%u: %pe", + __func__, btrfs_root_id(inode->root), + btrfs_ino(inode), orig_start, end + 1 - orig_start, +- start, cur_alloc_size, ret); ++ start, cur_alloc_size, ERR_PTR(ret)); + return ret; + } + +@@ -1962,9 +1963,9 @@ static int nocow_one_range(struct btrfs_inode *inode, struct folio *locked_folio + PAGE_UNLOCK | PAGE_START_WRITEBACK | + PAGE_END_WRITEBACK); + btrfs_err(inode->root->fs_info, +- "%s failed, root=%lld inode=%llu start=%llu len=%llu: %d", ++ "%s failed, root=%lld inode=%llu start=%llu len=%llu: %pe", + __func__, btrfs_root_id(inode->root), btrfs_ino(inode), +- file_pos, len, ret); ++ file_pos, len, ERR_PTR(ret)); + return ret; + } + +@@ -2285,10 +2286,10 @@ static noinline int run_delalloc_nocow(struct btrfs_inode *inode, + } + btrfs_free_path(path); + btrfs_err(fs_info, +-"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu oe_cleanup=%llu oe_cleanup_len=%llu untouched_start=%llu untouched_len=%llu: %d", ++"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu oe_cleanup=%llu oe_cleanup_len=%llu untouched_start=%llu untouched_len=%llu: %pe", + __func__, btrfs_root_id(inode->root), btrfs_ino(inode), + start, end + 1 - start, cur_offset, oe_cleanup_start, oe_cleanup_len, +- untouched_start, untouched_len, ret); ++ untouched_start, untouched_len, ERR_PTR(ret)); + return ret; + } + +@@ -3050,7 +3051,7 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans, + u64 qgroup_reserved) + { + struct btrfs_root *root = inode->root; +- const u64 sectorsize = root->fs_info->sectorsize; ++ const u32 sectorsize = root->fs_info->sectorsize; + BTRFS_PATH_AUTO_FREE(path); + struct extent_buffer *leaf; + struct btrfs_key ins; +@@ -3907,7 +3908,7 @@ int btrfs_orphan_cleanup(struct btrfs_root *root) + + out: + if (ret) +- btrfs_err(fs_info, "could not do orphan cleanup %d", ret); ++ btrfs_err(fs_info, "could not do orphan cleanup %pe", ERR_PTR(ret)); + return ret; + } + +@@ -4210,8 +4211,8 @@ static int btrfs_read_locked_inode(struct btrfs_inode *inode, struct btrfs_path + ret = btrfs_load_inode_props(inode, path); + if (ret) + btrfs_err(fs_info, +- "error loading props for ino %llu (root %llu): %d", +- btrfs_ino(inode), btrfs_root_id(root), ret); ++ "error loading props for ino %llu (root %llu): %pe", ++ btrfs_ino(inode), btrfs_root_id(root), ERR_PTR(ret)); + } + + /* +@@ -6825,8 +6826,8 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans, + } + if (ret) { + btrfs_err(fs_info, +- "error inheriting props for ino %llu (root %llu): %d", +- btrfs_ino(BTRFS_I(inode)), btrfs_root_id(root), ret); ++ "error inheriting props for ino %llu (root %llu): %pe", ++ btrfs_ino(BTRFS_I(inode)), btrfs_root_id(root), ERR_PTR(ret)); + } + + /* +@@ -10205,6 +10206,8 @@ static void btrfs_free_swapfile_pins(struct inode *inode) + struct btrfs_fs_info *fs_info = BTRFS_I(inode)->root->fs_info; + struct btrfs_swapfile_pin *sp; + struct rb_node *node, *next; ++ u64 bg_bytes_released = 0; ++ u32 bg_nr_released = 0; + + spin_lock(&fs_info->swapfile_pins_lock); + node = rb_first(&fs_info->swapfile_pins); +@@ -10214,15 +10217,24 @@ static void btrfs_free_swapfile_pins(struct inode *inode) + if (sp->inode == inode) { + rb_erase(&sp->node, &fs_info->swapfile_pins); + if (sp->is_block_group) { +- btrfs_dec_block_group_swap_extents(sp->ptr, ++ struct btrfs_block_group *bg = sp->ptr; ++ ++ bg_bytes_released += bg->length; ++ bg_nr_released++; ++ btrfs_dec_block_group_swap_extents(bg, + sp->bg_extent_count); +- btrfs_put_block_group(sp->ptr); ++ btrfs_put_block_group(bg); + } + kfree(sp); + } + node = next; + } + spin_unlock(&fs_info->swapfile_pins_lock); ++ btrfs_info(fs_info, ++"swapfile deactivated on root %llu ino %llu, released %llu bytes from %u block group(s)", ++ btrfs_root_id(BTRFS_I(inode)->root), ++ btrfs_ino(BTRFS_I(inode)), bg_bytes_released, ++ bg_nr_released); + } + + struct btrfs_swap_info { +@@ -10300,8 +10312,10 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, + struct btrfs_backref_share_check_ctx *backref_ctx = NULL; + struct btrfs_path *path = NULL; + int ret = 0; ++ u32 pinned_bg_nr = 0; + u64 isize; + u64 prev_extent_end = 0; ++ u64 pinned_bg_size = 0; + + /* + * Acquire the inode's mmap lock to prevent races with memory mapped +@@ -10551,6 +10565,9 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, + ret = 0; + else + goto out; ++ } else { ++ pinned_bg_size += bg->length; ++ pinned_bg_nr++; + } + + if (bsi.block_len && +@@ -10598,6 +10615,14 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, + if (ret) + return ret; + ++ btrfs_info(fs_info, ++"swapfile activated on root %llu ino %llu, pinned down %llu bytes from %u block group(s)", ++ btrfs_root_id(BTRFS_I(inode)->root), ++ btrfs_ino(BTRFS_I(inode)), ++ pinned_bg_size, pinned_bg_nr); ++ btrfs_warn(fs_info, ++"block groups with swapfile extents will not be scrubbed or balanced"); ++ + if (device) + sis->bdev = device->bdev; + *span = bsi.highest_ppage - bsi.lowest_ppage + 1; +diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c +--- a/fs/btrfs/ioctl.c ++++ b/fs/btrfs/ioctl.c +@@ -400,9 +400,9 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + + /* + * 1 for inode item +- * 2 for properties ++ * 1 for property + */ +- trans = btrfs_start_transaction(root, 3); ++ trans = btrfs_start_transaction(root, 2); + if (IS_ERR(trans)) + return PTR_ERR(trans); + +@@ -1143,13 +1143,13 @@ static noinline int btrfs_ioctl_resize(struct file *file, + } + + static noinline int __btrfs_ioctl_snap_create(struct file *file, +- struct mnt_idmap *idmap, + const char *name, unsigned long fd, bool subvol, + bool readonly, + struct btrfs_qgroup_inherit *inherit) + { + int ret; + struct qstr qname = QSTR(name); ++ struct mnt_idmap *idmap = file_mnt_idmap(file); + + if (!S_ISDIR(file_inode(file)->i_mode)) + return -ENOTDIR; +@@ -1227,8 +1227,7 @@ static noinline int btrfs_ioctl_snap_create(struct file *file, + if (ret < 0) + return ret; + +- return __btrfs_ioctl_snap_create(file, file_mnt_idmap(file), +- vol_args->name, vol_args->fd, subvol, ++ return __btrfs_ioctl_snap_create(file, vol_args->name, vol_args->fd, subvol, + false, NULL); + } + +@@ -1271,8 +1270,7 @@ static noinline int btrfs_ioctl_snap_create_v2(struct file *file, + return ret; + } + +- return __btrfs_ioctl_snap_create(file, file_mnt_idmap(file), +- vol_args->name, vol_args->fd, subvol, ++ return __btrfs_ioctl_snap_create(file, vol_args->name, vol_args->fd, subvol, + readonly, inherit); + } + +@@ -1664,13 +1662,11 @@ static noinline int btrfs_ioctl_tree_search_v2(struct btrfs_root *root, + } + + /* +- * Search INODE_REFs to identify path name of 'dirid' directory +- * in a 'tree_id' tree. and sets path name to 'name'. ++ * Search for an INODE_REF in a 'root' tree which identifies the path name of ++ * 'dirid'. When found, it sets 'name' with the path name. + */ +-static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, +- u64 tree_id, u64 dirid, char *name) ++static noinline int btrfs_search_path_in_tree(struct btrfs_root *root, u64 dirid, char *name) + { +- struct btrfs_root *root; + struct btrfs_key key; + char *ptr; + int ret = -1; +@@ -1692,13 +1688,6 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + + ptr = &name[BTRFS_INO_LOOKUP_PATH_MAX - 1]; + +- root = btrfs_get_fs_root(info, tree_id, true); +- if (IS_ERR(root)) { +- ret = PTR_ERR(root); +- root = NULL; +- goto out; +- } +- + key.objectid = dirid; + key.type = BTRFS_INODE_REF_KEY; + key.offset = (u64)-1; +@@ -1706,11 +1695,9 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + while (1) { + ret = btrfs_search_backwards(root, &key, path); + if (ret < 0) +- goto out; +- else if (ret > 0) { +- ret = -ENOENT; +- goto out; +- } ++ return ret; ++ else if (ret > 0) ++ return -ENOENT; + + l = path->nodes[0]; + slot = path->slots[0]; +@@ -1719,10 +1706,8 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + len = btrfs_inode_ref_name_len(l, iref); + ptr -= len + 1; + total_len += len + 1; +- if (ptr < name) { +- ret = -ENAMETOOLONG; +- goto out; +- } ++ if (ptr < name) ++ return -ENAMETOOLONG; + + *(ptr + len) = '/'; + read_extent_buffer(l, ptr, (unsigned long)(iref + 1), len); +@@ -1737,10 +1722,8 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + } + memmove(name, ptr, total_len); + name[total_len] = '\0'; +- ret = 0; +-out: +- btrfs_put_root(root); +- return ret; ++ ++ return 0; + } + + static int btrfs_search_path_in_tree_user(struct mnt_idmap *idmap, +@@ -1884,6 +1867,7 @@ static int btrfs_search_path_in_tree_user(struct mnt_idmap *idmap, + static noinline int btrfs_ioctl_ino_lookup(struct btrfs_root *root, + void __user *argp) + { ++ bool new_root = false; + struct btrfs_ioctl_ino_lookup_args AUTO_KFREE(args); + int ret = 0; + +@@ -1897,6 +1881,8 @@ static noinline int btrfs_ioctl_ino_lookup(struct btrfs_root *root, + */ + if (args->treeid == 0) + args->treeid = btrfs_root_id(root); ++ else ++ new_root = true; + + if (args->objectid == BTRFS_FIRST_FREE_OBJECTID) { + args->name[0] = 0; +@@ -1908,9 +1894,14 @@ static noinline int btrfs_ioctl_ino_lookup(struct btrfs_root *root, + goto out; + } + +- ret = btrfs_search_path_in_tree(root->fs_info, +- args->treeid, args->objectid, +- args->name); ++ if (new_root) { ++ root = btrfs_get_fs_root(root->fs_info, args->treeid, true); ++ if (IS_ERR(root)) ++ return PTR_ERR(root); ++ } ++ ret = btrfs_search_path_in_tree(root, args->objectid, args->name); ++ if (new_root) ++ btrfs_put_root(root); + + out: + if (ret == 0 && copy_to_user(argp, args, sizeof(*args))) +@@ -2848,8 +2839,8 @@ static long btrfs_ioctl_default_subvol(struct file *file, void __user *argp) + else + ret = -ENOENT; + btrfs_err(fs_info, +- "could not find default diritem for dir %llu: %d", +- dir_id, ret); ++ "could not find default diritem for dir %llu: %pe", ++ dir_id, ERR_PTR(ret)); + goto out_free; + } + +@@ -3620,7 +3611,7 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + { + struct inode *inode = file_inode(file); + struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); +- struct btrfs_root *root = BTRFS_I(inode)->root; ++ struct btrfs_root *quota_root; + struct btrfs_ioctl_qgroup_assign_args AUTO_KFREE(sa); + struct btrfs_qgroup_list AUTO_KFREE(prealloc); + struct btrfs_trans_handle *trans; +@@ -3651,10 +3642,20 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + } + } + ++ mutex_lock(&fs_info->qgroup_ioctl_lock); ++ quota_root = btrfs_grab_root(fs_info->quota_root); ++ mutex_unlock(&fs_info->qgroup_ioctl_lock); ++ ++ if (!quota_root) { ++ ret = -ENOTCONN; ++ goto drop_write; ++ } ++ + /* 2 BTRFS_QGROUP_RELATION_KEY items. */ +- trans = btrfs_start_transaction(root, 2); ++ trans = btrfs_start_transaction(quota_root, 2); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); ++ btrfs_put_root(quota_root); + goto drop_write; + } + +@@ -3678,6 +3679,7 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + "qgroup status update failed after %s relation, marked as inconsistent", + sa->assign ? "adding" : "deleting"); + err = btrfs_end_transaction(trans); ++ btrfs_put_root(quota_root); + if (err && !ret) + ret = err; + +@@ -3689,7 +3691,8 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + { + struct inode *inode = file_inode(file); +- struct btrfs_root *root = BTRFS_I(inode)->root; ++ struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); ++ struct btrfs_root *quota_root; + struct btrfs_ioctl_qgroup_create_args AUTO_KFREE(sa); + struct btrfs_trans_handle *trans; + int ret; +@@ -3698,7 +3701,7 @@ static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + +- if (!btrfs_qgroup_enabled(root->fs_info)) ++ if (!btrfs_qgroup_enabled(fs_info)) + return -ENOTCONN; + + ret = mnt_want_write_file(file); +@@ -3721,13 +3724,23 @@ static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + goto drop_write; + } + ++ mutex_lock(&fs_info->qgroup_ioctl_lock); ++ quota_root = btrfs_grab_root(fs_info->quota_root); ++ mutex_unlock(&fs_info->qgroup_ioctl_lock); ++ ++ if (!quota_root) { ++ ret = -ENOTCONN; ++ goto drop_write; ++ } ++ + /* + * 1 BTRFS_QGROUP_INFO_KEY item. + * 1 BTRFS_QGROUP_LIMIT_KEY item. + */ +- trans = btrfs_start_transaction(root, 2); ++ trans = btrfs_start_transaction(quota_root, 2); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); ++ btrfs_put_root(quota_root); + goto drop_write; + } + +@@ -3738,6 +3751,7 @@ static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + } + + err = btrfs_end_transaction(trans); ++ btrfs_put_root(quota_root); + if (err && !ret) + ret = err; + +@@ -3750,6 +3764,8 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + { + struct inode *inode = file_inode(file); + struct btrfs_root *root = BTRFS_I(inode)->root; ++ struct btrfs_root *quota_root; ++ struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_ioctl_qgroup_limit_args AUTO_KFREE(sa); + struct btrfs_trans_handle *trans; + int ret; +@@ -3759,7 +3775,7 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + +- if (!btrfs_qgroup_enabled(root->fs_info)) ++ if (!btrfs_qgroup_enabled(fs_info)) + return -ENOTCONN; + + ret = mnt_want_write_file(file); +@@ -3772,10 +3788,20 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + goto drop_write; + } + ++ mutex_lock(&fs_info->qgroup_ioctl_lock); ++ quota_root = btrfs_grab_root(fs_info->quota_root); ++ mutex_unlock(&fs_info->qgroup_ioctl_lock); ++ ++ if (!quota_root) { ++ ret = -ENOTCONN; ++ goto drop_write; ++ } ++ + /* 1 BTRFS_QGROUP_LIMIT_KEY item. */ +- trans = btrfs_start_transaction(root, 1); ++ trans = btrfs_start_transaction(quota_root, 1); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); ++ btrfs_put_root(quota_root); + goto drop_write; + } + +@@ -3788,6 +3814,7 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + ret = btrfs_limit_qgroup(trans, qgroupid, &sa->lim); + + err = btrfs_end_transaction(trans); ++ btrfs_put_root(quota_root); + if (err && !ret) + ret = err; + +diff --git a/fs/btrfs/messages.c b/fs/btrfs/messages.c +--- a/fs/btrfs/messages.c ++++ b/fs/btrfs/messages.c +@@ -279,7 +279,6 @@ void __btrfs_panic(const struct btrfs_fs_info *fs_info, const char *function, + unsigned int line, int error, const char *fmt, ...) + { + char *s_id = ""; +- const char *errstr; + struct va_format vaf = { .fmt = fmt }; + va_list args; + +@@ -289,13 +288,12 @@ void __btrfs_panic(const struct btrfs_fs_info *fs_info, const char *function, + va_start(args, fmt); + vaf.va = &args; + +- errstr = btrfs_decode_error(error); + if (fs_info && (btrfs_test_opt(fs_info, PANIC_ON_FATAL_ERROR))) +- panic(KERN_CRIT "BTRFS panic (device %s) in %s:%d: %pV (errno=%d %s)\n", +- s_id, function, line, &vaf, error, errstr); ++ panic(KERN_CRIT "BTRFS panic (device %s) in %s:%d: %pV (errno=%d %pe)\n", ++ s_id, function, line, &vaf, error, ERR_PTR(error)); + +- btrfs_crit(fs_info, "panic in %s:%d: %pV (errno=%d %s)", +- function, line, &vaf, error, errstr); ++ btrfs_crit(fs_info, "panic in %s:%d: %pV (errno=%d %pe)", ++ function, line, &vaf, error, ERR_PTR(error)); + va_end(args); + /* Caller calls BUG() */ + } +diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c +--- a/fs/btrfs/qgroup.c ++++ b/fs/btrfs/qgroup.c +@@ -3915,8 +3915,8 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) + ret = PTR_ERR(trans); + trans = NULL; + btrfs_err(fs_info, +- "fail to start transaction for status update: %d", +- ret); ++ "fail to start transaction for status update: %pe", ++ ERR_PTR(ret)); + } + } else { + trans = NULL; +@@ -3931,7 +3931,7 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) + + if (ret2 < 0) { + ret = ret2; +- btrfs_err(fs_info, "fail to update qgroup status: %d", ret); ++ btrfs_err(fs_info, "fail to update qgroup status: %pe", ERR_PTR(ret)); + } + } + fs_info->qgroup_rescan_running = false; +@@ -3952,7 +3952,7 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) + btrfs_info(fs_info, "qgroup scan completed%s", + ret > 0 ? " (inconsistency flag cleared)" : ""); + } else { +- btrfs_err(fs_info, "qgroup scan failed with %d", ret); ++ btrfs_err(fs_info, "qgroup scan failed with %pe", ERR_PTR(ret)); + } + } + +@@ -4339,12 +4339,13 @@ static int qgroup_free_reserved_data(struct btrfs_inode *inode, + struct ulist_node *unode; + struct ulist_iterator uiter; + struct extent_changeset changeset; ++ const u32 sectorsize = root->fs_info->sectorsize; ++ const u64 aligned_start = round_down(start, sectorsize); ++ const u64 aligned_len = round_up(start + len, sectorsize) - aligned_start; + u64 freed = 0; + int ret; + + extent_changeset_init_bytes_only(&changeset); +- len = round_up(start + len, root->fs_info->sectorsize); +- start = round_down(start, root->fs_info->sectorsize); + + ULIST_ITER_INIT(&uiter); + while ((unode = ulist_next(&reserved->range_changed, &uiter))) { +@@ -4356,12 +4357,15 @@ static int qgroup_free_reserved_data(struct btrfs_inode *inode, + + extent_changeset_release(&changeset); + +- /* Only free range in range [start, start + len) */ +- if (range_start >= start + len || +- range_start + range_len <= start) ++ /* ++ * Only free the range within ++ * [aligned_start, aligned_start + aligned_len). ++ */ ++ if (range_start >= aligned_start + aligned_len || ++ range_start + range_len <= aligned_start) + continue; +- free_start = max(range_start, start); +- free_len = min(start + len, range_start + range_len) - ++ free_start = max(range_start, aligned_start); ++ free_len = min(aligned_start + aligned_len, range_start + range_len) - + free_start; + /* + * TODO: To also modify reserved->ranges_reserved to reflect +diff --git a/fs/btrfs/raid56.c b/fs/btrfs/raid56.c +--- a/fs/btrfs/raid56.c ++++ b/fs/btrfs/raid56.c +@@ -2997,13 +2997,11 @@ void raid56_parity_submit_scrub_rbio(struct btrfs_raid_bio *rbio) + * This is due to the fact rbio has its own page management for its cache. + */ + void raid56_parity_cache_data_folios(struct btrfs_raid_bio *rbio, +- struct folio **data_folios, u64 data_logical) ++ void *vaddr, u64 data_logical) + { + struct btrfs_fs_info *fs_info = rbio->bioc->fs_info; + const u64 offset_in_full_stripe = data_logical - + rbio->bioc->full_stripe_logical; +- unsigned int findex = 0; +- unsigned int foffset = 0; + int ret; + + /* +@@ -3026,18 +3024,10 @@ void raid56_parity_cache_data_folios(struct btrfs_raid_bio *rbio, + cur_off < offset_in_full_stripe + BTRFS_STRIPE_LEN; + cur_off += PAGE_SIZE) { + const unsigned int pindex = cur_off >> PAGE_SHIFT; +- void *kaddr; + +- kaddr = kmap_local_page(rbio->stripe_pages[pindex]); +- memcpy_from_folio(kaddr, data_folios[findex], foffset, PAGE_SIZE); +- kunmap_local(kaddr); +- +- foffset += PAGE_SIZE; +- ASSERT(foffset <= folio_size(data_folios[findex])); +- if (foffset == folio_size(data_folios[findex])) { +- findex++; +- foffset = 0; +- } ++ ASSERT(cur_off - offset_in_full_stripe + PAGE_SIZE <= BTRFS_STRIPE_LEN); ++ memcpy_to_page(rbio->stripe_pages[pindex], 0, ++ vaddr + cur_off - offset_in_full_stripe, PAGE_SIZE); + } + bitmap_set(rbio->stripe_uptodate_bitmap, + offset_in_full_stripe >> fs_info->sectorsize_bits, +diff --git a/fs/btrfs/raid56.h b/fs/btrfs/raid56.h +--- a/fs/btrfs/raid56.h ++++ b/fs/btrfs/raid56.h +@@ -283,7 +283,7 @@ struct btrfs_raid_bio *raid56_parity_alloc_scrub_rbio(struct bio *bio, + void raid56_parity_submit_scrub_rbio(struct btrfs_raid_bio *rbio); + + void raid56_parity_cache_data_folios(struct btrfs_raid_bio *rbio, +- struct folio **data_folios, u64 data_logical); ++ void *vaddr, u64 data_logical); + + int btrfs_alloc_stripe_hash_table(struct btrfs_fs_info *info); + void btrfs_free_stripe_hash_table(struct btrfs_fs_info *info); +diff --git a/fs/btrfs/reflink.c b/fs/btrfs/reflink.c +--- a/fs/btrfs/reflink.c ++++ b/fs/btrfs/reflink.c +@@ -20,30 +20,31 @@ + #define BTRFS_MAX_DEDUPE_LEN SZ_16M + + static int clone_finish_inode_update(struct btrfs_trans_handle *trans, +- struct inode *inode, ++ struct btrfs_inode *inode, + u64 endoff, + const u64 destoff, + const u64 olen, + bool no_time_update) + { ++ struct inode *vfs_inode = &inode->vfs_inode; + int ret; + +- inode_inc_iversion(inode); +- if (!no_time_update) { +- inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); +- } ++ inode_inc_iversion(vfs_inode); ++ if (!no_time_update) ++ inode_set_mtime_to_ts(vfs_inode, inode_set_ctime_current(vfs_inode)); ++ + /* + * We round up to the block size at eof when determining which + * extents to clone above, but shouldn't round up the file size. + */ + if (endoff > destoff + olen) + endoff = destoff + olen; +- if (endoff > inode->i_size) { +- i_size_write(inode, endoff); +- btrfs_inode_safe_disk_i_size_write(BTRFS_I(inode), 0); ++ if (endoff > vfs_inode->i_size) { ++ i_size_write(vfs_inode, endoff); ++ btrfs_inode_safe_disk_i_size_write(inode, 0); + } + +- ret = btrfs_update_inode(trans, BTRFS_I(inode)); ++ ret = btrfs_update_inode(trans, inode); + if (unlikely(ret)) { + btrfs_abort_transaction(trans, ret); + btrfs_end_transaction(trans); +@@ -392,11 +393,11 @@ static int clone_copy_inline_extent(struct btrfs_inode *inode, + * @destoff: Offset within @inode to start clone + * @no_time_update: Whether to update mtime/ctime on the target inode + */ +-static int btrfs_clone(struct inode *src, struct inode *inode, ++static int btrfs_clone(struct btrfs_inode *src, struct btrfs_inode *inode, + const u64 off, const u64 olen, const u64 olen_aligned, + const u64 destoff, bool no_time_update) + { +- struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); ++ struct btrfs_fs_info *fs_info = inode->root->fs_info; + BTRFS_PATH_AUTO_FREE(path); + struct extent_buffer *leaf; + struct btrfs_trans_handle *trans; +@@ -420,7 +421,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + + path->reada = READA_FORWARD; + /* Clone data */ +- key.objectid = btrfs_ino(BTRFS_I(src)); ++ key.objectid = btrfs_ino(src); + key.type = BTRFS_EXTENT_DATA_KEY; + key.offset = off; + +@@ -436,8 +437,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + u64 drop_start; + + /* Note the key will change type as we walk through the tree */ +- ret = btrfs_search_slot(NULL, BTRFS_I(src)->root, &key, path, +- 0, 0); ++ ret = btrfs_search_slot(NULL, src->root, &key, path, 0, 0); + if (ret < 0) + goto out; + /* +@@ -455,7 +455,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + nritems = btrfs_header_nritems(path->nodes[0]); + process_slot: + if (path->slots[0] >= nritems) { +- ret = btrfs_next_leaf(BTRFS_I(src)->root, path); ++ ret = btrfs_next_leaf(src->root, path); + if (ret < 0) + goto out; + if (ret > 0) +@@ -466,8 +466,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + slot = path->slots[0]; + + btrfs_item_key_to_cpu(leaf, &key, slot); +- if (key.type > BTRFS_EXTENT_DATA_KEY || +- key.objectid != btrfs_ino(BTRFS_I(src))) ++ if (key.type > BTRFS_EXTENT_DATA_KEY || key.objectid != btrfs_ino(src)) + break; + + ASSERT(key.type == BTRFS_EXTENT_DATA_KEY, "key.type=%u", key.type); +@@ -514,7 +513,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + btrfs_release_path(path); + + memcpy(&new_key, &key, sizeof(new_key)); +- new_key.objectid = btrfs_ino(BTRFS_I(inode)); ++ new_key.objectid = btrfs_ino(inode); + if (off <= key.offset) + new_key.offset = key.offset + destoff - off; + else +@@ -558,7 +557,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + clone_info.extent_buf = buf; + clone_info.is_new_extent = false; + clone_info.update_times = !no_time_update; +- ret = btrfs_replace_file_extents(BTRFS_I(inode), path, ++ ret = btrfs_replace_file_extents(inode, path, + drop_start, new_key.offset + datal - 1, + &clone_info, &trans); + if (ret) +@@ -582,7 +581,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + goto out; + } + +- ret = clone_copy_inline_extent(BTRFS_I(inode), path, &new_key, ++ ret = clone_copy_inline_extent(inode, path, &new_key, + drop_start, datal, size, + comp, buf, &trans); + if (ret) +@@ -605,9 +604,9 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + * the checksums problem on fsync. + */ + if (extent_gen == trans->transid && disko > 0) +- BTRFS_I(src)->last_reflink_trans = trans->transid; ++ src->last_reflink_trans = trans->transid; + +- BTRFS_I(inode)->last_reflink_trans = trans->transid; ++ inode->last_reflink_trans = trans->transid; + + last_dest_end = ALIGN(new_key.offset + datal, + fs_info->sectorsize); +@@ -653,10 +652,10 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + * set by previous calls to btrfs_replace_file_extents() that + * replaced file extent items. + */ +- if (last_dest_end >= i_size_read(inode)) +- btrfs_set_inode_full_sync(BTRFS_I(inode)); ++ if (last_dest_end >= i_size_read(&inode->vfs_inode)) ++ btrfs_set_inode_full_sync(inode); + +- ret = btrfs_replace_file_extents(BTRFS_I(inode), path, ++ ret = btrfs_replace_file_extents(inode, path, + last_dest_end, destoff + len - 1, NULL, &trans); + if (ret) + goto out; +@@ -666,7 +665,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + } + + out: +- clear_bit(BTRFS_INODE_NO_DELALLOC_FLUSH, &BTRFS_I(inode)->runtime_flags); ++ clear_bit(BTRFS_INODE_NO_DELALLOC_FLUSH, &inode->runtime_flags); + + return ret; + } +@@ -688,10 +687,10 @@ static void btrfs_double_mmap_unlock(struct btrfs_inode *inode1, struct btrfs_in + static int btrfs_extent_same_range(struct btrfs_inode *src, u64 loff, u64 len, + struct btrfs_inode *dst, u64 dst_loff) + { +- const u64 end = dst_loff + len - 1; + struct extent_state *cached_state = NULL; + struct btrfs_fs_info *fs_info = src->root->fs_info; +- const u64 bs = fs_info->sectorsize; ++ const u32 bs = fs_info->sectorsize; ++ const u64 end = round_up(dst_loff + len, bs) - 1; + int ret; + + /* +@@ -701,8 +700,7 @@ static int btrfs_extent_same_range(struct btrfs_inode *src, u64 loff, u64 len, + * mode. + */ + btrfs_lock_extent(&dst->io_tree, dst_loff, end, &cached_state); +- ret = btrfs_clone(&src->vfs_inode, &dst->vfs_inode, loff, len, +- ALIGN(len, bs), dst_loff, true); ++ ret = btrfs_clone(src, dst, loff, len, ALIGN(len, bs), dst_loff, true); + btrfs_unlock_extent(&dst->io_tree, dst_loff, end, &cached_state); + + btrfs_btree_balance_dirty(fs_info); +@@ -710,12 +708,12 @@ static int btrfs_extent_same_range(struct btrfs_inode *src, u64 loff, u64 len, + return ret; + } + +-static int btrfs_extent_same(struct inode *src, u64 loff, u64 olen, +- struct inode *dst, u64 dst_loff) ++static int btrfs_extent_same(struct btrfs_inode *src, u64 loff, u64 olen, ++ struct btrfs_inode *dst, u64 dst_loff) + { + int ret = 0; + u64 i, tail_len, chunk_count; +- struct btrfs_root *root_dst = BTRFS_I(dst)->root; ++ struct btrfs_root *root_dst = dst->root; + + spin_lock(&root_dst->root_item_lock); + if (root_dst->send_in_progress) { +@@ -733,8 +731,8 @@ static int btrfs_extent_same(struct inode *src, u64 loff, u64 olen, + chunk_count = div_u64(olen, BTRFS_MAX_DEDUPE_LEN); + + for (i = 0; i < chunk_count; i++) { +- ret = btrfs_extent_same_range(BTRFS_I(src), loff, BTRFS_MAX_DEDUPE_LEN, +- BTRFS_I(dst), dst_loff); ++ ret = btrfs_extent_same_range(src, loff, BTRFS_MAX_DEDUPE_LEN, ++ dst, dst_loff); + if (ret) + goto out; + +@@ -743,8 +741,7 @@ static int btrfs_extent_same(struct inode *src, u64 loff, u64 olen, + } + + if (tail_len > 0) +- ret = btrfs_extent_same_range(BTRFS_I(src), loff, tail_len, +- BTRFS_I(dst), dst_loff); ++ ret = btrfs_extent_same_range(src, loff, tail_len, dst, dst_loff); + out: + spin_lock(&root_dst->root_item_lock); + root_dst->dedupe_in_progress--; +@@ -757,12 +754,14 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + u64 off, u64 olen, u64 destoff) + { + struct extent_state *cached_state = NULL; +- struct inode *inode = file_inode(file); +- struct inode *src = file_inode(file_src); +- struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); ++ struct btrfs_inode *inode = BTRFS_I(file_inode(file)); ++ struct btrfs_inode *src = BTRFS_I(file_inode(file_src)); ++ struct btrfs_fs_info *fs_info = inode->root->fs_info; ++ const u64 src_isize = src->vfs_inode.i_size; ++ const u64 inode_isize = inode->vfs_inode.i_size; + int ret; + u64 len = olen; +- u64 bs = fs_info->sectorsize; ++ const u32 bs = fs_info->sectorsize; + u64 end; + + /* +@@ -771,13 +770,13 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * if the file size is not blocksize aligned. So we don't need to check + * for that case here. + */ +- if (off + len == src->i_size) +- len = ALIGN(src->i_size, bs) - off; ++ if (off + len == src_isize) ++ len = ALIGN(src_isize, bs) - off; + +- if (destoff > inode->i_size) { +- const u64 wb_start = ALIGN_DOWN(inode->i_size, bs); ++ if (destoff > inode_isize) { ++ const u64 wb_start = ALIGN_DOWN(inode_isize, bs); + +- ret = btrfs_cont_expand(BTRFS_I(inode), inode->i_size, destoff); ++ ret = btrfs_cont_expand(inode, inode_isize, destoff); + if (ret) + return ret; + /* +@@ -789,8 +788,7 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * we found the previous extent covering eof and before we + * attempted to increment its reference count). + */ +- ret = btrfs_wait_ordered_range(BTRFS_I(inode), wb_start, +- destoff - wb_start); ++ ret = btrfs_wait_ordered_range(inode, wb_start, destoff - wb_start); + if (ret) + return ret; + } +@@ -801,10 +799,10 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * because we have already locked the inode's i_mmap_lock in exclusive + * mode. + */ +- end = destoff + len - 1; +- btrfs_lock_extent(&BTRFS_I(inode)->io_tree, destoff, end, &cached_state); ++ end = round_up(destoff + len, bs) - 1; ++ btrfs_lock_extent(&inode->io_tree, destoff, end, &cached_state); + ret = btrfs_clone(src, inode, off, olen, len, destoff, false); +- btrfs_unlock_extent(&BTRFS_I(inode)->io_tree, destoff, end, &cached_state); ++ btrfs_unlock_extent(&inode->io_tree, destoff, end, &cached_state); + if (ret < 0) + return ret; + +@@ -818,7 +816,7 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * could come from some range other than the copied inline extent's + * destination range and we have no way to know that. + */ +- ret = btrfs_wait_ordered_range(BTRFS_I(inode), destoff, len); ++ ret = btrfs_wait_ordered_range(inode, destoff, len); + if (ret < 0) + return ret; + +@@ -826,7 +824,7 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * Invalidate page cache so that future reads will see the cloned data + * immediately and not the previous data. + */ +- ret = filemap_invalidate_inode(inode, false, destoff, end); ++ ret = filemap_invalidate_inode(&inode->vfs_inode, false, destoff, end); + if (ret < 0) + return ret; + +@@ -841,7 +839,7 @@ static int btrfs_remap_file_range_prep(struct file *file_in, loff_t pos_in, + { + struct btrfs_inode *inode_in = BTRFS_I(file_inode(file_in)); + struct btrfs_inode *inode_out = BTRFS_I(file_inode(file_out)); +- u64 bs = inode_out->root->fs_info->sectorsize; ++ const u32 bs = inode_out->root->fs_info->sectorsize; + u64 wb_len; + int ret; + +@@ -934,7 +932,7 @@ loff_t btrfs_remap_file_range(struct file *src_file, loff_t off, + bool same_inode = dst_inode == src_inode; + int ret; + +- if (btrfs_is_shutdown(inode_to_fs_info(file_inode(src_file)))) ++ if (btrfs_is_shutdown(src_inode->root->fs_info)) + return -EIO; + + if (remap_flags & ~(REMAP_FILE_DEDUP | REMAP_FILE_ADVISORY)) +@@ -953,8 +951,7 @@ loff_t btrfs_remap_file_range(struct file *src_file, loff_t off, + goto out_unlock; + + if (remap_flags & REMAP_FILE_DEDUP) +- ret = btrfs_extent_same(&src_inode->vfs_inode, off, len, +- &dst_inode->vfs_inode, destoff); ++ ret = btrfs_extent_same(src_inode, off, len, dst_inode, destoff); + else + ret = btrfs_clone_files(dst_file, src_file, off, len, destoff); + +diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c +--- a/fs/btrfs/relocation.c ++++ b/fs/btrfs/relocation.c +@@ -339,14 +339,15 @@ static struct btrfs_backref_node *walk_down_backref( + + static bool reloc_root_is_dead(const struct btrfs_root *root) + { +- /* +- * Pair with set_bit/clear_bit in clean_dirty_subvols and +- * btrfs_update_reloc_root. We need to see the updated bit before +- * trying to access reloc_root +- */ +- smp_rmb(); + if (test_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state)) + return true; ++ /* ++ * Pairs with set_bit/clear_bit in clear_reloc_root() and ++ * btrfs_update_reloc_root(). We need to see the updated bit before ++ * trying to access root->reloc_root in our callers. ++ */ ++ smp_rmb(); ++ + return false; + } + +@@ -1537,6 +1538,33 @@ static void clear_reloc_root(struct btrfs_root *root) + clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state); + } + ++/* Drop the reloc trees of a relocation that is being deferred and retried. */ ++static void abort_reloc_roots(struct reloc_control *rc, struct list_head *list) ++{ ++ struct btrfs_fs_info *fs_info = rc->extent_root->fs_info; ++ struct btrfs_root *reloc_root, *tmp; ++ ++ list_for_each_entry_safe(reloc_root, tmp, list, root_list) { ++ struct btrfs_root *root; ++ ++ root = btrfs_get_fs_root(fs_info, reloc_root->root_key.offset, false); ++ if (!IS_ERR(root)) { ++ if (root->reloc_root == reloc_root) { ++ clear_reloc_root(root); ++ btrfs_put_root(reloc_root); ++ } ++ btrfs_put_root(root); ++ } ++ ++ btrfs_set_root_refs(&reloc_root->root_item, 0); ++ memset(&reloc_root->root_item.drop_progress, 0, sizeof(struct btrfs_disk_key)); ++ btrfs_set_root_drop_level(&reloc_root->root_item, 0); ++ ++ list_del_init(&reloc_root->root_list); ++ list_add_tail(&reloc_root->reloc_dirty_list, &rc->dirty_subvol_roots); ++ } ++} ++ + static int clean_dirty_subvols(struct reloc_control *rc) + { + struct btrfs_root *root; +@@ -1876,8 +1904,7 @@ int prepare_to_merge(struct reloc_control *rc, int err) + return err; + } + +-static noinline_for_stack +-void merge_reloc_roots(struct reloc_control *rc) ++static noinline_for_stack int merge_reloc_roots(struct reloc_control *rc) + { + struct btrfs_fs_info *fs_info = rc->extent_root->fs_info; + struct btrfs_root *root; +@@ -1975,7 +2002,15 @@ void merge_reloc_roots(struct reloc_control *rc) + goto again; + } + out: +- if (ret) { ++ if (btrfs_is_zoned(fs_info) && ret == -EAGAIN) { ++ abort_reloc_roots(rc, &reloc_roots); ++ ++ /* New reloc root may be added. */ ++ mutex_lock(&fs_info->reloc_mutex); ++ list_splice_init(&rc->reloc_roots, &reloc_roots); ++ mutex_unlock(&fs_info->reloc_mutex); ++ abort_reloc_roots(rc, &reloc_roots); ++ } else if (ret) { + btrfs_handle_fs_error(fs_info, ret, NULL); + free_reloc_roots(&reloc_roots); + +@@ -2001,6 +2036,7 @@ void merge_reloc_roots(struct reloc_control *rc) + * + * The remaining nodes will be cleaned up by put_reloc_control(). + */ ++ return ret; + } + + static void free_block_list(struct rb_root *blocks) +@@ -3730,7 +3766,9 @@ static noinline_for_stack int relocate_block_group(struct reloc_control *rc) + */ + err = prepare_to_merge(rc, err); + +- merge_reloc_roots(rc); ++ ret = merge_reloc_roots(rc); ++ if (ret && !err) ++ err = ret; + + rc->merge_reloc_tree = false; + unset_reloc_control(rc); +@@ -4114,10 +4152,10 @@ static int copy_remapped_data(struct btrfs_fs_info *fs_info, u64 old_addr, + u64 new_addr, u64 length) + { + int ret; +- u64 copy_len = min_t(u64, length, SZ_1M); ++ const u64 copy_len = min_t(u64, length, SZ_1M); + struct page **pages; + struct reloc_io_private priv; +- unsigned int nr_pages = DIV_ROUND_UP(length, PAGE_SIZE); ++ const unsigned int nr_pages = DIV_ROUND_UP(copy_len, PAGE_SIZE); + + pages = kzalloc_objs(struct page *, nr_pages, GFP_NOFS); + if (!pages) +@@ -5720,7 +5758,9 @@ int btrfs_recover_relocation(struct btrfs_fs_info *fs_info) + goto out_drop_reloc_refs; + release_recovered_fs_roots(&recovered_roots, false); + +- merge_reloc_roots(rc); ++ ret = merge_reloc_roots(rc); ++ if (ret) ++ goto out_unset; + + unset_reloc_control(rc); + +diff --git a/fs/btrfs/root-tree.c b/fs/btrfs/root-tree.c +--- a/fs/btrfs/root-tree.c ++++ b/fs/btrfs/root-tree.c +@@ -265,15 +265,15 @@ int btrfs_find_orphan_roots(struct btrfs_fs_info *fs_info) + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + btrfs_err(fs_info, +- "failed to join transaction to delete orphan item: %d", +- ret); ++ "failed to join transaction to delete orphan item: %pe", ++ ERR_PTR(ret)); + return ret; + } + ret = btrfs_del_orphan_item(trans, tree_root, root_objectid); + btrfs_end_transaction(trans); + if (ret) { + btrfs_err(fs_info, +- "failed to delete root orphan item: %d", ret); ++ "failed to delete root orphan item: %pe", ERR_PTR(ret)); + return ret; + } + continue; +diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c +--- a/fs/btrfs/scrub.c ++++ b/fs/btrfs/scrub.c +@@ -57,12 +57,6 @@ struct scrub_ctx; + + #define SCRUB_TOTAL_STRIPES (SCRUB_GROUPS_PER_SCTX * SCRUB_STRIPES_PER_GROUP) + +-/* +- * The following value times PAGE_SIZE needs to be large enough to match the +- * largest node/leaf/sector size that shall be supported. +- */ +-#define SCRUB_MAX_SECTORS_PER_BLOCK (BTRFS_MAX_METADATA_BLOCKSIZE / SZ_4K) +- + /* Represent one sector and its needed info to verify the content. */ + struct scrub_sector_verification { + union { +@@ -129,19 +123,17 @@ enum { + scrub_bitmap_nr_last, + }; + +-#define SCRUB_STRIPE_MAX_FOLIOS (BTRFS_STRIPE_LEN / PAGE_SIZE) +- + /* + * Represent one contiguous range with a length of BTRFS_STRIPE_LEN. + */ + struct scrub_stripe { + struct scrub_ctx *sctx; + struct btrfs_block_group *bg; +- +- struct folio *folios[SCRUB_STRIPE_MAX_FOLIOS]; + struct scrub_sector_verification *sectors; +- + struct btrfs_device *dev; ++ ++ void *buffer; ++ + u64 logical; + u64 physical; + +@@ -227,6 +219,9 @@ struct scrub_ctx { + refcount_t refs; + }; + ++static_assert(BTRFS_STRIPE_LEN >= PAGE_SIZE); ++static_assert(IS_ALIGNED(BTRFS_STRIPE_LEN, PAGE_SIZE)); ++ + #define scrub_calc_start_bit(stripe, name, block_nr) \ + ({ \ + unsigned int __start_bit; \ +@@ -338,13 +333,10 @@ static void release_scrub_stripe(struct scrub_stripe *stripe) + if (!stripe) + return; + +- for (int i = 0; i < SCRUB_STRIPE_MAX_FOLIOS; i++) { +- if (stripe->folios[i]) +- folio_put(stripe->folios[i]); +- stripe->folios[i] = NULL; +- } ++ kvfree(stripe->buffer); + kfree(stripe->sectors); + kfree(stripe->csums); ++ stripe->buffer = NULL; + stripe->sectors = NULL; + stripe->csums = NULL; + stripe->sctx = NULL; +@@ -354,9 +346,6 @@ static void release_scrub_stripe(struct scrub_stripe *stripe) + static int init_scrub_stripe(struct btrfs_fs_info *fs_info, + struct scrub_stripe *stripe) + { +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; +- int ret; +- + memset(stripe, 0, sizeof(*stripe)); + + stripe->nr_sectors = BTRFS_STRIPE_LEN >> fs_info->sectorsize_bits; +@@ -367,11 +356,8 @@ static int init_scrub_stripe(struct btrfs_fs_info *fs_info, + atomic_set(&stripe->pending_io, 0); + spin_lock_init(&stripe->write_error_lock); + +- ASSERT(BTRFS_STRIPE_LEN >> min_folio_shift <= SCRUB_STRIPE_MAX_FOLIOS); +- ret = btrfs_alloc_folio_array(BTRFS_STRIPE_LEN >> min_folio_shift, +- fs_info->block_min_order, stripe->folios, +- GFP_NOFS); +- if (ret < 0) ++ stripe->buffer = kvmalloc(BTRFS_STRIPE_LEN, GFP_NOFS); ++ if (!stripe->buffer) + goto error; + + stripe->sectors = kzalloc_objs(struct scrub_sector_verification, +@@ -682,32 +668,18 @@ static int fill_writer_pointer_gap(struct scrub_ctx *sctx, u64 physical) + return ret; + } + +-static void *scrub_stripe_get_kaddr(struct scrub_stripe *stripe, int sector_nr) ++/* ++ * Unlike the existing csum which is based on paddr, this version is fully on ++ * vaddr, so no extra per-page iteration needed. ++ */ ++static void scrub_calc_vaddr_csum(struct btrfs_fs_info *fs_info, ++ void *vaddr, unsigned int len, u8 *dest) + { +- struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; +- u32 offset = (sector_nr << fs_info->sectorsize_bits); +- const struct folio *folio = stripe->folios[offset >> min_folio_shift]; ++ struct btrfs_csum_ctx csum; + +- /* stripe->folios[] is allocated by us and no highmem is allowed. */ +- ASSERT(folio); +- ASSERT(!folio_test_highmem(folio)); +- return folio_address(folio) + offset_in_folio(folio, offset); +-} +- +-static phys_addr_t scrub_stripe_get_paddr(struct scrub_stripe *stripe, int sector_nr) +-{ +- struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; +- u32 offset = (sector_nr << fs_info->sectorsize_bits); +- const struct folio *folio = stripe->folios[offset >> min_folio_shift]; +- +- /* stripe->folios[] is allocated by us and no highmem is allowed. */ +- ASSERT(folio); +- ASSERT(!folio_test_highmem(folio)); +- /* And the range must be contained inside the folio. */ +- ASSERT(offset_in_folio(folio, offset) + fs_info->sectorsize <= folio_size(folio)); +- return page_to_phys(folio_page(folio, 0)) + offset_in_folio(folio, offset); ++ btrfs_csum_init(&csum, fs_info->csum_type); ++ btrfs_csum_update(&csum, vaddr, len); ++ btrfs_csum_final(&csum, dest); + } + + static void scrub_verify_one_metadata(struct scrub_stripe *stripe, int sector_nr) +@@ -715,19 +687,10 @@ static void scrub_verify_one_metadata(struct scrub_stripe *stripe, int sector_nr + struct btrfs_fs_info *fs_info = stripe->bg->fs_info; + const u32 sectors_per_tree = fs_info->nodesize >> fs_info->sectorsize_bits; + const u64 logical = stripe->logical + (sector_nr << fs_info->sectorsize_bits); +- void *first_kaddr = scrub_stripe_get_kaddr(stripe, sector_nr); +- struct btrfs_header *header = first_kaddr; +- struct btrfs_csum_ctx csum; +- u8 on_disk_csum[BTRFS_CSUM_SIZE]; ++ void *first_vaddr = stripe->buffer + (sector_nr << fs_info->sectorsize_bits); ++ struct btrfs_header *header = first_vaddr; + u8 calculated_csum[BTRFS_CSUM_SIZE]; + +- /* +- * Here we don't have a good way to attach the pages (and subpages) +- * to a dummy extent buffer, thus we have to directly grab the members +- * from pages. +- */ +- memcpy(on_disk_csum, header->csum, fs_info->csum_size); +- + if (logical != btrfs_stack_header_bytenr(header)) { + scrub_bitmap_set_meta_error(stripe, sector_nr, sectors_per_tree); + scrub_bitmap_set_error(stripe, sector_nr, sectors_per_tree); +@@ -759,23 +722,15 @@ static void scrub_verify_one_metadata(struct scrub_stripe *stripe, int sector_nr + } + + /* Now check tree block csum. */ +- btrfs_csum_init(&csum, fs_info->csum_type); +- btrfs_csum_update(&csum, first_kaddr + BTRFS_CSUM_SIZE, +- fs_info->sectorsize - BTRFS_CSUM_SIZE); +- +- for (int i = sector_nr + 1; i < sector_nr + sectors_per_tree; i++) { +- btrfs_csum_update(&csum, scrub_stripe_get_kaddr(stripe, i), +- fs_info->sectorsize); +- } +- +- btrfs_csum_final(&csum, calculated_csum); +- if (memcmp(calculated_csum, on_disk_csum, fs_info->csum_size) != 0) { ++ scrub_calc_vaddr_csum(fs_info, first_vaddr + BTRFS_CSUM_SIZE, ++ fs_info->nodesize - BTRFS_CSUM_SIZE, calculated_csum); ++ if (memcmp(calculated_csum, header->csum, fs_info->csum_size) != 0) { + scrub_bitmap_set_meta_error(stripe, sector_nr, sectors_per_tree); + scrub_bitmap_set_error(stripe, sector_nr, sectors_per_tree); + btrfs_warn_rl(fs_info, + "scrub: tree block %llu mirror %u has bad csum, has " BTRFS_CSUM_FMT " want " BTRFS_CSUM_FMT, + logical, stripe->mirror_num, +- BTRFS_CSUM_FMT_VALUE(fs_info->csum_size, on_disk_csum), ++ BTRFS_CSUM_FMT_VALUE(fs_info->csum_size, header->csum), + BTRFS_CSUM_FMT_VALUE(fs_info->csum_size, calculated_csum)); + return; + } +@@ -801,9 +756,7 @@ static void scrub_verify_one_sector(struct scrub_stripe *stripe, int sector_nr) + struct btrfs_fs_info *fs_info = stripe->bg->fs_info; + struct scrub_sector_verification *sector = &stripe->sectors[sector_nr]; + const u32 sectors_per_tree = fs_info->nodesize >> fs_info->sectorsize_bits; +- phys_addr_t paddr = scrub_stripe_get_paddr(stripe, sector_nr); + u8 csum_buf[BTRFS_CSUM_SIZE]; +- int ret; + + ASSERT(sector_nr >= 0 && sector_nr < stripe->nr_sectors); + +@@ -846,8 +799,10 @@ static void scrub_verify_one_sector(struct scrub_stripe *stripe, int sector_nr) + return; + } + +- ret = btrfs_check_block_csum(fs_info, paddr, csum_buf, sector->csum); +- if (ret < 0) { ++ scrub_calc_vaddr_csum(fs_info, ++ stripe->buffer + (sector_nr << fs_info->sectorsize_bits), ++ fs_info->sectorsize, csum_buf); ++ if (memcmp(csum_buf, sector->csum, fs_info->csum_size)) { + scrub_bitmap_set_bit_csum_error(stripe, sector_nr); + scrub_bitmap_set_bit_error(stripe, sector_nr); + } else { +@@ -870,16 +825,51 @@ static void scrub_verify_one_stripe(struct scrub_stripe *stripe, unsigned long b + } + } + +-static int calc_sector_number(struct scrub_stripe *stripe, struct bio_vec *first_bvec) ++static unsigned int calc_sector_number(const struct btrfs_bio *bbio) + { +- int i; ++ const struct scrub_stripe *stripe = bbio->private; ++ const struct btrfs_fs_info *fs_info = stripe->bg->fs_info; + +- for (i = 0; i < stripe->nr_sectors; i++) { +- if (scrub_stripe_get_kaddr(stripe, i) == bvec_virt(first_bvec)) +- break; ++ /* Scrub bbios all have their @file_offset set to the logical bytenr. */ ++ ASSERT(bbio->file_offset >= stripe->logical && ++ bbio->file_offset < stripe->logical + (stripe->nr_sectors << ++ fs_info->sectorsize_bits), ++ "scrub bio logical=%llu stripe logical=%llu stripe len=%u", ++ bbio->file_offset, stripe->logical, ++ stripe->nr_sectors << fs_info->sectorsize_bits); ++ return (bbio->file_offset - stripe->logical) >> fs_info->sectorsize_bits; ++} ++ ++/* ++ * Common handling of read endio. ++ * ++ * The bbio will be released, so no more access to @bbio after this function. ++ */ ++static void scrub_read_endio_common(struct btrfs_bio *bbio) ++{ ++ struct scrub_stripe *stripe = bbio->private; ++ struct btrfs_fs_info *fs_info = stripe->bg->fs_info; ++ unsigned int sector_nr = calc_sector_number(bbio); ++ const u32 bio_size = bio_get_size(&bbio->bio); ++ const u32 sectors = bio_size >> fs_info->sectorsize_bits; ++ ++ ++ /* ++ * For vmallocated space, readers need to call invalidate_kernel_vmap_range() ++ * to manage the coherency between kernel mapping and devie space mapping. ++ */ ++ if (is_vmalloc_addr(stripe->buffer)) ++ invalidate_kernel_vmap_range( ++ stripe->buffer + (sector_nr << fs_info->sectorsize_bits), ++ bio_size); ++ ++ if (bbio->bio.bi_status) { ++ scrub_bitmap_set_io_error(stripe, sector_nr, sectors); ++ scrub_bitmap_set_error(stripe, sector_nr, sectors); ++ } else { ++ scrub_bitmap_clear_io_error(stripe, sector_nr, sectors); + } +- ASSERT(i < stripe->nr_sectors); +- return i; ++ bio_put(&bbio->bio); + } + + /* +@@ -891,22 +881,9 @@ static int calc_sector_number(struct scrub_stripe *stripe, struct bio_vec *first + static void scrub_repair_read_endio(struct btrfs_bio *bbio) + { + struct scrub_stripe *stripe = bbio->private; +- struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- int sector_nr = calc_sector_number(stripe, bio_first_bvec_all(&bbio->bio)); +- const u32 bio_size = bio_get_size(&bbio->bio); + +- ASSERT(sector_nr < stripe->nr_sectors); ++ scrub_read_endio_common(bbio); + +- if (bbio->bio.bi_status) { +- scrub_bitmap_set_io_error(stripe, sector_nr, +- bio_size >> fs_info->sectorsize_bits); +- scrub_bitmap_set_error(stripe, sector_nr, +- bio_size >> fs_info->sectorsize_bits); +- } else { +- scrub_bitmap_clear_io_error(stripe, sector_nr, +- bio_size >> fs_info->sectorsize_bits); +- } +- bio_put(&bbio->bio); + if (atomic_dec_and_test(&stripe->pending_io)) + wake_up(&stripe->io_wait); + } +@@ -921,30 +898,35 @@ static void scrub_bio_add_sector(struct btrfs_bio *bbio, struct scrub_stripe *st + int sector_nr) + { + struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info; +- void *kaddr = scrub_stripe_get_kaddr(stripe, sector_nr); ++ const u32 offset = sector_nr << fs_info->sectorsize_bits; + int ret; + +- ret = bio_add_page(&bbio->bio, virt_to_page(kaddr), fs_info->sectorsize, +- offset_in_page(kaddr)); +- /* +- * Caller should ensure the bbio has enough size. +- * And we cannot use __bio_add_page(), which doesn't do any merge. +- * +- * Meanwhile for scrub_submit_initial_read() we fully rely on the merge +- * to create the minimal amount of bio vectors, for fs block size < page +- * size cases. +- */ ++ ASSERT(offset + fs_info->sectorsize <= BTRFS_STRIPE_LEN); ++ ++ if (is_vmalloc_addr(stripe->buffer)) { ++ ret = bio_add_vmalloc(&bbio->bio, stripe->buffer + offset, fs_info->sectorsize); ++ ASSERT(ret == true); ++ return; ++ } ++ ret = bio_add_page(&bbio->bio, virt_to_page(stripe->buffer + offset), ++ fs_info->sectorsize, offset_in_page(stripe->buffer + offset)); + ASSERT(ret == fs_info->sectorsize); + } + + static struct btrfs_bio *alloc_scrub_bbio(struct btrfs_fs_info *fs_info, +- unsigned int nr_vecs, blk_opf_t opf, ++ blk_opf_t opf, + u64 logical, + btrfs_bio_end_io_t end_io, void *private) + { + struct btrfs_bio *bbio; + +- bbio = btrfs_bio_alloc(nr_vecs, opf, BTRFS_I(fs_info->btree_inode), ++ /* ++ * Stripe->buffer is allocated by kvmalloc(), which can be pages at ++ * different physical addresses, we have to ensure the bbio is large ++ * enough to contain the full stripe. ++ */ ++ bbio = btrfs_bio_alloc(BTRFS_STRIPE_LEN >> PAGE_SHIFT, opf, ++ BTRFS_I(fs_info->btree_inode), + logical, end_io, private); + bbio->is_scrub = true; + bbio->bio.bi_iter.bi_sector = logical >> SECTOR_SHIFT; +@@ -976,7 +958,7 @@ static void scrub_stripe_submit_repair_read(struct scrub_stripe *stripe, + } + + if (!bbio) +- bbio = alloc_scrub_bbio(fs_info, stripe->nr_sectors, REQ_OP_READ, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_READ, + stripe->logical + (i << fs_info->sectorsize_bits), + scrub_repair_read_endio, stripe); + +@@ -1245,20 +1227,9 @@ static void scrub_stripe_read_repair_worker(struct work_struct *work) + static void scrub_read_endio(struct btrfs_bio *bbio) + { + struct scrub_stripe *stripe = bbio->private; +- int sector_nr = calc_sector_number(stripe, bio_first_bvec_all(&bbio->bio)); +- int num_sectors; +- const u32 bio_size = bio_get_size(&bbio->bio); + +- ASSERT(sector_nr < stripe->nr_sectors); +- num_sectors = bio_size >> stripe->bg->fs_info->sectorsize_bits; ++ scrub_read_endio_common(bbio); + +- if (bbio->bio.bi_status) { +- scrub_bitmap_set_io_error(stripe, sector_nr, num_sectors); +- scrub_bitmap_set_error(stripe, sector_nr, num_sectors); +- } else { +- scrub_bitmap_clear_io_error(stripe, sector_nr, num_sectors); +- } +- bio_put(&bbio->bio); + if (atomic_dec_and_test(&stripe->pending_io)) { + wake_up(&stripe->io_wait); + INIT_WORK(&stripe->work, scrub_stripe_read_repair_worker); +@@ -1270,7 +1241,7 @@ static void scrub_write_endio(struct btrfs_bio *bbio) + { + struct scrub_stripe *stripe = bbio->private; + struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- int sector_nr = calc_sector_number(stripe, bio_first_bvec_all(&bbio->bio)); ++ unsigned int sector_nr = calc_sector_number(bbio); + const u32 bio_size = bio_get_size(&bbio->bio); + + if (bbio->bio.bi_status) { +@@ -1349,7 +1320,7 @@ static void scrub_write_sectors(struct scrub_ctx *sctx, struct scrub_stripe *str + bbio = NULL; + } + if (!bbio) +- bbio = alloc_scrub_bbio(fs_info, stripe->nr_sectors, REQ_OP_WRITE, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_WRITE, + stripe->logical + (sector_nr << fs_info->sectorsize_bits), + scrub_write_endio, stripe); + scrub_bio_add_sector(bbio, stripe, sector_nr); +@@ -1844,7 +1815,7 @@ static void scrub_submit_extent_sector_read(struct scrub_stripe *stripe) + continue; + } + +- bbio = alloc_scrub_bbio(fs_info, stripe->nr_sectors, REQ_OP_READ, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_READ, + logical, scrub_read_endio, stripe); + } + +@@ -1869,7 +1840,6 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx, + { + struct btrfs_fs_info *fs_info = sctx->fs_info; + struct btrfs_bio *bbio; +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; + unsigned int nr_sectors = stripe_length(stripe) >> fs_info->sectorsize_bits; + int mirror = stripe->mirror_num; + +@@ -1882,7 +1852,7 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx, + return; + } + +- bbio = alloc_scrub_bbio(fs_info, BTRFS_STRIPE_LEN >> min_folio_shift, REQ_OP_READ, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_READ, + stripe->logical, scrub_read_endio, stripe); + /* Read the whole range inside the chunk boundary. */ + for (unsigned int cur = 0; cur < nr_sectors; cur++) +@@ -2138,7 +2108,7 @@ static int scrub_raid56_cached_parity(struct scrub_ctx *sctx, + for (int i = 0; i < data_stripes; i++) { + struct scrub_stripe *stripe = &sctx->raid56_data_stripes[i]; + +- raid56_parity_cache_data_folios(rbio, stripe->folios, ++ raid56_parity_cache_data_folios(rbio, stripe->buffer, + full_stripe_start + (i << BTRFS_STRIPE_LEN_SHIFT)); + } + raid56_parity_submit_scrub_rbio(rbio); +@@ -3091,14 +3061,6 @@ int btrfs_scrub_dev(struct btrfs_fs_info *fs_info, u64 devid, u64 start, + /* At mount time we have ensured nodesize is in the range of [4K, 64K]. */ + ASSERT(fs_info->nodesize <= BTRFS_STRIPE_LEN); + +- /* +- * SCRUB_MAX_SECTORS_PER_BLOCK is calculated using the largest possible +- * value (max nodesize / min sectorsize), thus nodesize should always +- * be fine. +- */ +- ASSERT(fs_info->nodesize <= +- SCRUB_MAX_SECTORS_PER_BLOCK << fs_info->sectorsize_bits); +- + /* Allocate outside of device_list_mutex */ + sctx = scrub_setup_ctx(fs_info, is_dev_replace); + if (IS_ERR(sctx)) +diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c +--- a/fs/btrfs/send.c ++++ b/fs/btrfs/send.c +@@ -130,10 +130,10 @@ static_assert(offsetof(struct backref_cache_entry, entry) == 0); + #define SEND_MAX_DIR_CREATED_CACHE_SIZE 64 + + /* +- * Max number of entries in the cache that stores directories that were already +- * created. The cache uses raw struct btrfs_lru_cache_entry entries, so it uses +- * at most 4096 bytes - sizeof(struct btrfs_lru_cache_entry) is 48 bytes, but +- * the kmalloc-64 slab is used, so we get 4096 bytes (64 bytes * 64). ++ * Maximum number of entries in the cache that stores utimes values for directories. ++ * The cache uses raw struct btrfs_lru_cache_entry entries, so it uses at most ++ * 4096 bytes - sizeof(struct btrfs_lru_cache_entry) is 48 bytes, but the ++ * kmalloc-64 slab is used, so we get 4096 bytes (64 bytes * 64). + */ + #define SEND_MAX_DIR_UTIMES_CACHE_SIZE 64 + +@@ -625,9 +625,8 @@ static void fs_path_unreverse(struct fs_path *p) + static inline bool is_current_inode_path(const struct send_ctx *sctx, + const struct fs_path *path) + { +- const struct fs_path *cur = &sctx->cur_inode_path; +- +- return (strncmp(path->start, cur->start, fs_path_len(cur)) == 0); ++ /* Paths are always nul terminated. */ ++ return (strcmp(path->start, sctx->cur_inode_path.start) == 0); + } + + static struct btrfs_path *alloc_path_for_send(void) +@@ -6033,7 +6032,7 @@ static int send_write_or_clone(struct send_ctx *sctx, + int ret = 0; + u64 offset = key->offset; + u64 end; +- u64 bs = sctx->send_root->fs_info->sectorsize; ++ const u32 bs = sctx->send_root->fs_info->sectorsize; + struct btrfs_file_extent_item *ei; + u64 disk_byte; + u64 data_offset; +@@ -8251,7 +8250,7 @@ long btrfs_ioctl_send(struct btrfs_root *send_root, const struct btrfs_ioctl_sen + } + + if (sort_clone_roots) { +- for (i = 0; i < sctx->clone_roots_cnt; i++) { ++ for (i = 0; sctx && i < sctx->clone_roots_cnt; i++) { + btrfs_root_dec_send_in_progress( + sctx->clone_roots[i].root); + btrfs_put_root(sctx->clone_roots[i].root); +diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c +--- a/fs/btrfs/space-info.c ++++ b/fs/btrfs/space-info.c +@@ -2156,7 +2156,7 @@ static bool do_reclaim_sweep(struct btrfs_space_info *space_info, int raid) + will_reclaim = true; + reclaim = true; + } +- bg->reclaim_mark++; ++ bg->reclaim_mark = true; + spin_unlock(&bg->lock); + if (reclaim) + btrfs_mark_bg_to_reclaim(bg); +diff --git a/fs/btrfs/subpage.c b/fs/btrfs/subpage.c +--- a/fs/btrfs/subpage.c ++++ b/fs/btrfs/subpage.c +@@ -59,7 +59,7 @@ int btrfs_attach_folio_state(const struct btrfs_fs_info *fs_info, + if (type == BTRFS_SUBPAGE_DATA && !btrfs_is_subpage(fs_info, folio)) + return 0; + +- bfs = btrfs_alloc_folio_state(fs_info, folio_size(folio), type); ++ bfs = btrfs_alloc_folio_state(fs_info, folio_size(folio), type, GFP_NOFS); + if (IS_ERR(bfs)) + return PTR_ERR(bfs); + +@@ -86,7 +86,8 @@ void btrfs_detach_folio_state(const struct btrfs_fs_info *fs_info, struct folio + } + + struct btrfs_folio_state *btrfs_alloc_folio_state(const struct btrfs_fs_info *fs_info, +- size_t fsize, enum btrfs_folio_type type) ++ size_t fsize, enum btrfs_folio_type type, ++ gfp_t gfp) + { + struct btrfs_folio_state *ret; + unsigned int real_size; +@@ -96,7 +97,7 @@ struct btrfs_folio_state *btrfs_alloc_folio_state(const struct btrfs_fs_info *fs + real_size = struct_size(ret, bitmaps, + BITS_TO_LONGS(btrfs_bitmap_nr_max * + (fsize >> fs_info->sectorsize_bits))); +- ret = kzalloc(real_size, GFP_NOFS); ++ ret = kzalloc(real_size, gfp); + if (!ret) + return ERR_PTR(-ENOMEM); + +diff --git a/fs/btrfs/subpage.h b/fs/btrfs/subpage.h +--- a/fs/btrfs/subpage.h ++++ b/fs/btrfs/subpage.h +@@ -110,7 +110,8 @@ void btrfs_detach_folio_state(const struct btrfs_fs_info *fs_info, struct folio + + /* Allocate additional data where page represents more than one sector */ + struct btrfs_folio_state *btrfs_alloc_folio_state(const struct btrfs_fs_info *fs_info, +- size_t fsize, enum btrfs_folio_type type); ++ size_t fsize, enum btrfs_folio_type type, ++ gfp_t gfp); + static inline void btrfs_free_folio_state(struct btrfs_folio_state *bfs) + { + kfree(bfs); +diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c +--- a/fs/btrfs/super.c ++++ b/fs/btrfs/super.c +@@ -129,7 +129,6 @@ enum { + + /* Rescue options */ + Opt_rescue, +- Opt_usebackuproot, + + /* Debugging options */ + Opt_enospc_debug, +@@ -249,8 +248,6 @@ static const struct fs_parameter_spec btrfs_fs_parameters[] = { + + /* Rescue options. */ + fsparam_enum("rescue", Opt_rescue, btrfs_parameter_rescue), +- /* Deprecated, with alias rescue=usebackuproot */ +- __fsparam(NULL, "usebackuproot", Opt_usebackuproot, fs_param_deprecated, NULL), + /* For compatibility only, alias for "rescue=nologreplay". */ + fsparam_flag("norecovery", Opt_norecovery), + +@@ -514,19 +511,20 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + btrfs_clear_opt(ctx->mount_opt, NODISCARD); + break; + case Opt_space_cache: +- if (result.negated) { +- btrfs_set_opt(ctx->mount_opt, NOSPACECACHE); +- btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE); +- btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); +- } else { +- btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); +- btrfs_set_opt(ctx->mount_opt, SPACE_CACHE); +- } ++ if (!result.negated) ++ btrfs_warn(NULL, ++ "v1 space cache is deprecated, falling back to no space cache"); ++ btrfs_set_opt(ctx->mount_opt, NOSPACECACHE); ++ btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE); ++ btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); + break; + case Opt_space_cache_version: + switch (result.uint_32) { + case Opt_space_cache_v1: +- btrfs_set_opt(ctx->mount_opt, SPACE_CACHE); ++ btrfs_warn(NULL, ++ "v1 space cache is deprecated, falling back to no space cache"); ++ btrfs_set_opt(ctx->mount_opt, NOSPACECACHE); ++ btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE); + btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); + break; + case Opt_space_cache_v2: +@@ -560,14 +558,6 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + else + btrfs_set_opt(ctx->mount_opt, AUTO_DEFRAG); + break; +- case Opt_usebackuproot: +- btrfs_warn(NULL, +- "'usebackuproot' is deprecated, use 'rescue=usebackuproot' instead"); +- btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT); +- +- /* If we're loading the backup roots we can't trust the space cache. */ +- btrfs_set_opt(ctx->mount_opt, CLEAR_CACHE); +- break; + case Opt_skip_balance: + btrfs_set_opt(ctx->mount_opt, SKIP_BALANCE); + break; +@@ -620,6 +610,7 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + btrfs_set_opt(ctx->mount_opt, IGNORESUPERFLAGS); + btrfs_set_opt(ctx->mount_opt, IGNOREBADROOTS); + btrfs_set_opt(ctx->mount_opt, NOLOGREPLAY); ++ btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT); + break; + default: + btrfs_info(NULL, "unrecognized rescue option '%s'", +@@ -668,7 +659,6 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + */ + static void btrfs_clear_oneshot_options(struct btrfs_fs_info *fs_info) + { +- btrfs_clear_opt(fs_info->mount_opt, USEBACKUPROOT); + btrfs_clear_opt(fs_info->mount_opt, CLEAR_CACHE); + btrfs_clear_opt(fs_info->mount_opt, NOSPACECACHE); + } +@@ -692,7 +682,8 @@ bool btrfs_check_options(const struct btrfs_fs_info *info, + bool ret = true; + + if (!(flags & SB_RDONLY) && +- (check_ro_option(info, *mount_opt, BTRFS_MOUNT_NOLOGREPLAY, "nologreplay") || ++ (check_ro_option(info, *mount_opt, BTRFS_MOUNT_USEBACKUPROOT, "usebackuproot") || ++ check_ro_option(info, *mount_opt, BTRFS_MOUNT_NOLOGREPLAY, "nologreplay") || + check_ro_option(info, *mount_opt, BTRFS_MOUNT_IGNOREBADROOTS, "ignorebadroots") || + check_ro_option(info, *mount_opt, BTRFS_MOUNT_IGNOREDATACSUMS, "ignoredatacsums") || + check_ro_option(info, *mount_opt, BTRFS_MOUNT_IGNOREMETACSUMS, "ignoremetacsums") || +@@ -982,7 +973,7 @@ static int btrfs_fill_super(struct super_block *sb, + + ret = open_ctree(sb, fs_devices); + if (ret) { +- btrfs_err(fs_info, "open_ctree failed: %d", ret); ++ btrfs_err(fs_info, "open_ctree failed: %pe", ERR_PTR(ret)); + return ret; + } + +diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c +--- a/fs/btrfs/sysfs.c ++++ b/fs/btrfs/sysfs.c +@@ -1336,7 +1336,7 @@ char *btrfs_get_mod_read_policy(void) + return read_policy; + } + +-/* Set perms to 0, disable /sys/module/btrfs/parameter/read_policy interface. */ ++/* Set perms to 0, disable /sys/module/btrfs/parameters/read_policy interface. */ + module_param(read_policy, charp, 0); + MODULE_PARM_DESC(read_policy, + "Global read policy: pid (default), round-robin[:], devid[:]"); +diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c +--- a/fs/btrfs/transaction.c ++++ b/fs/btrfs/transaction.c +@@ -698,8 +698,6 @@ start_transaction(struct btrfs_root *root, unsigned int num_items, + goto alloc_fail; + } + +- xa_init(&h->writeback_inhibited_ebs); +- + /* + * If we are JOIN_NOLOCK we're already committing a transaction and + * waiting on this guy, so we don't need to do the sb_start_intwrite +@@ -1519,12 +1517,8 @@ static noinline int commit_fs_roots(struct btrfs_trans_handle *trans) + ASSERT(atomic_read(&root->log_writers) == 0, + "atomic_read(&root->log_writers)=%d", + atomic_read(&root->log_writers)); +- ASSERT(atomic_read(&root->log_commit[0]) == 0, +- "atomic_read(&root->log_commit[0])=%d", +- atomic_read(&root->log_commit[0])); +- ASSERT(atomic_read(&root->log_commit[1]) == 0, +- "atomic_read(&root->log_commit[1])=%d", +- atomic_read(&root->log_commit[1])); ++ ASSERT(!root->log_commit[0]); ++ ASSERT(!root->log_commit[1]); + + radix_tree_tag_clear(&fs_info->fs_roots_radix, + (unsigned long)btrfs_root_id(root), +@@ -1642,7 +1636,7 @@ static int qgroup_account_snapshot(struct btrfs_trans_handle *trans, + ret = btrfs_write_and_wait_transaction(trans); + if (unlikely(ret)) { + btrfs_err(fs_info, +-"error while writing out transaction during qgroup snapshot accounting: %d", ret); ++"error while writing out transaction during qgroup snapshot accounting: %pe", ERR_PTR(ret)); + return ret; + } + +@@ -2588,7 +2582,7 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans) + + ret = btrfs_write_and_wait_transaction(trans); + if (unlikely(ret)) { +- btrfs_err(fs_info, "error while writing out transaction: %d", ret); ++ btrfs_err(fs_info, "error while writing out transaction: %pe", ERR_PTR(ret)); + mutex_unlock(&fs_info->tree_log_mutex); + goto scrub_continue; + } +@@ -2749,8 +2743,8 @@ void __cold __btrfs_abort_transaction(struct btrfs_trans_handle *trans, + WRITE_ONCE(trans->transaction->aborted, error); + trace_btrfs_transaction_abort(trans); + if (first_hit) { +- btrfs_err(fs_info, "Transaction %llu aborted (error %d)", +- trans->transid, error); ++ btrfs_err(fs_info, "Transaction %llu aborted (%pe)", ++ trans->transid, ERR_PTR(error)); + if (error == -ENOSPC) + btrfs_dump_space_info_for_trans_abort(fs_info); + } +diff --git a/fs/btrfs/transaction.h b/fs/btrfs/transaction.h +--- a/fs/btrfs/transaction.h ++++ b/fs/btrfs/transaction.h +@@ -7,12 +7,12 @@ + #define BTRFS_TRANSACTION_H + + #include ++#include + #include + #include + #include + #include + #include +-#include + #include "btrfs_inode.h" + #include "delayed-ref.h" + +@@ -23,6 +23,7 @@ struct btrfs_fs_info; + struct btrfs_root_item; + struct btrfs_root; + struct btrfs_path; ++struct extent_buffer; + + /* + * Signal that a direct IO write is in progress, to avoid deadlock for sync +@@ -136,6 +137,18 @@ enum { + + #define TRANS_EXTWRITERS (__TRANS_START | __TRANS_ATTACH) + ++/* ++ * Number of extent buffers a transaction handle tracks for writeback ++ * inhibition. The CLOCK reference bits pack into a u32 so this must not exceed ++ * 32, and keeping it a power of two lets the compiler reduce the CLOCK hand ++ * modulo to a mask. ++ */ ++#define BTRFS_INHIBITED_EBS_SLOTS 8 ++ ++static_assert(BTRFS_INHIBITED_EBS_SLOTS <= 32); ++static_assert(BTRFS_INHIBITED_EBS_SLOTS != 0 && ++ (BTRFS_INHIBITED_EBS_SLOTS & (BTRFS_INHIBITED_EBS_SLOTS - 1)) == 0); ++ + struct btrfs_trans_handle { + u64 transid; + u64 bytes_reserved; +@@ -163,8 +176,14 @@ struct btrfs_trans_handle { + struct btrfs_fs_info *fs_info; + struct list_head new_bgs; + struct btrfs_block_rsv delayed_rsv; +- /* Extent buffers with writeback inhibited by this handle. */ +- struct xarray writeback_inhibited_ebs; ++ ++ /* Extent buffers this handle has inhibited writeback on. */ ++ struct extent_buffer *inhibited_ebs[BTRFS_INHIBITED_EBS_SLOTS]; ++ /* CLOCK reference bit per slot. */ ++ u32 inhibited_ebs_referenced; ++ u32 nr_inhibited_ebs; ++ /* CLOCK hand. */ ++ u32 inhibited_ebs_hand; + }; + + /* +diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c +--- a/fs/btrfs/tree-log.c ++++ b/fs/btrfs/tree-log.c +@@ -221,7 +221,7 @@ static int btrfs_log_inode(struct btrfs_trans_handle *trans, + static int link_to_fixup_dir(struct walk_control *wc, u64 objectid); + static noinline int replay_dir_deletes(struct walk_control *wc, + u64 dirid, bool del_all); +-static void wait_log_commit(struct btrfs_root *root, int transid); ++static bool wait_log_commit(struct btrfs_root *root, int transid); + + /* + * tree logging is a special write ahead log used to make sure that +@@ -305,24 +305,13 @@ static int start_log_trans(struct btrfs_trans_handle *trans, + + again: + if (root->log_root) { +- int index = (root->log_transid + 1) % 2; +- + if (btrfs_need_log_full_commit(trans)) { + ret = BTRFS_LOG_FORCE_COMMIT; + goto out; + } + +- if (zoned && atomic_read(&root->log_commit[index])) { +- wait_log_commit(root, root->log_transid - 1); ++ if (zoned && wait_log_commit(root, root->log_transid - 1)) + goto again; +- } +- +- if (!root->log_start_pid) { +- clear_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state); +- root->log_start_pid = current->pid; +- } else if (root->log_start_pid != current->pid) { +- set_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state); +- } + } else { + /* + * This means fs_info->log_root_tree was already created +@@ -340,8 +329,6 @@ static int start_log_trans(struct btrfs_trans_handle *trans, + goto out; + + set_bit(BTRFS_ROOT_HAS_LOG_TREE, &root->state); +- clear_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state); +- root->log_start_pid = current->pid; + } + + atomic_inc(&root->log_writers); +@@ -372,13 +359,9 @@ static int join_running_log_trans(struct btrfs_root *root) + mutex_lock(&root->log_mutex); + again: + if (root->log_root) { +- int index = (root->log_transid + 1) % 2; +- + ret = 0; +- if (zoned && atomic_read(&root->log_commit[index])) { +- wait_log_commit(root, root->log_transid - 1); ++ if (zoned && wait_log_commit(root, root->log_transid - 1)) + goto again; +- } + atomic_inc(&root->log_writers); + } + mutex_unlock(&root->log_mutex); +@@ -2986,6 +2969,7 @@ static noinline int walk_down_log_tree(struct btrfs_path *path, int *level, + { + struct btrfs_trans_handle *trans = wc->trans; + struct btrfs_fs_info *fs_info = wc->log->fs_info; ++ struct btrfs_eb_prealloc pa = { 0 }; + u64 bytenr; + u64 ptr_gen; + struct extent_buffer *next; +@@ -3010,7 +2994,7 @@ static noinline int walk_down_log_tree(struct btrfs_path *path, int *level, + check.has_first_key = true; + btrfs_node_key_to_cpu(cur, &check.first_key, path->slots[*level]); + +- next = btrfs_find_create_tree_block(fs_info, bytenr, ++ next = btrfs_find_create_tree_block(fs_info, &pa, bytenr, + btrfs_header_owner(cur), + *level - 1); + if (IS_ERR(next)) { +@@ -3181,10 +3165,14 @@ static int update_log_root(struct btrfs_trans_handle *trans, + return ret; + } + +-static void wait_log_commit(struct btrfs_root *root, int transid) ++/* Returns true if we had to wait, false otherwise. */ ++static bool wait_log_commit(struct btrfs_root *root, int transid) + { + DEFINE_WAIT(wait); +- int index = transid % 2; ++ const int index = (transid >= 0 ? transid % 2 : -transid % 2); ++ ++ if (!root->log_commit[index]) ++ return false; + + /* + * we only allow two pending log transactions at a time, +@@ -3195,15 +3183,17 @@ static void wait_log_commit(struct btrfs_root *root, int transid) + prepare_to_wait(&root->log_commit_wait[index], + &wait, TASK_UNINTERRUPTIBLE); + +- if (!(root->log_transid_committed < transid && +- atomic_read(&root->log_commit[index]))) +- break; +- + mutex_unlock(&root->log_mutex); + schedule(); + mutex_lock(&root->log_mutex); ++ ++ if (!(root->log_transid_committed < transid && ++ root->log_commit[index])) ++ break; + } + finish_wait(&root->log_commit_wait[index], &wait); ++ ++ return true; + } + + static void wait_for_writer(struct btrfs_root *root) +@@ -3307,15 +3297,15 @@ static inline void btrfs_remove_all_log_ctxs(struct btrfs_root *root, + int btrfs_sync_log(struct btrfs_trans_handle *trans, + struct btrfs_root *root, struct btrfs_log_ctx *ctx) + { +- int index1; +- int index2; + int mark; + int ret; + struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_root *log = root->log_root; + struct btrfs_root *log_root_tree = fs_info->log_root_tree; + struct btrfs_root_item new_root_item; +- int log_transid = 0; ++ int log_transid = ctx->log_transid; ++ int index1 = log_transid % 2; ++ int index2; + struct btrfs_log_ctx root_log_ctx; + struct blk_plug plug; + u64 log_root_start; +@@ -3323,41 +3313,25 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + + mutex_lock(&root->log_mutex); + trace_btrfs_sync_log_enter(trans, root, ctx); +- log_transid = ctx->log_transid; + if (root->log_transid_committed >= log_transid) { + trace_btrfs_sync_log_exit(trans, root, ctx, ctx->log_ret); + mutex_unlock(&root->log_mutex); + return ctx->log_ret; + } + +- index1 = log_transid % 2; +- if (atomic_read(&root->log_commit[index1])) { +- wait_log_commit(root, log_transid); ++ if (wait_log_commit(root, log_transid)) { + trace_btrfs_sync_log_exit(trans, root, ctx, ctx->log_ret); + mutex_unlock(&root->log_mutex); + return ctx->log_ret; + } + ASSERT(log_transid == root->log_transid, + "log_transid=%d root->log_transid=%d", log_transid, root->log_transid); +- atomic_set(&root->log_commit[index1], 1); ++ root->log_commit[index1] = true; + + /* wait for previous tree log sync to complete */ +- if (atomic_read(&root->log_commit[(index1 + 1) % 2])) +- wait_log_commit(root, log_transid - 1); ++ wait_log_commit(root, log_transid - 1); + +- while (1) { +- int batch = atomic_read(&root->log_batch); +- /* when we're on an ssd, just kick the log commit out */ +- if (!btrfs_test_opt(fs_info, SSD) && +- test_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state)) { +- mutex_unlock(&root->log_mutex); +- schedule_timeout_uninterruptible(1); +- mutex_lock(&root->log_mutex); +- } +- wait_for_writer(root); +- if (batch == atomic_read(&root->log_batch)) +- break; +- } ++ wait_for_writer(root); + + /* bail out if we need to do a full commit */ + if (btrfs_need_log_full_commit(trans)) { +@@ -3414,7 +3388,6 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + + btrfs_set_root_log_transid(root, root->log_transid + 1); + log->log_transid = root->log_transid; +- root->log_start_pid = 0; + /* + * IO has been started, blocks of the log tree have WRITTEN flag set + * in their headers. new modifications of the log will be written to +@@ -3473,7 +3446,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + goto out; + } + +- if (atomic_read(&log_root_tree->log_commit[index2])) { ++ if (log_root_tree->log_commit[index2]) { + blk_finish_plug(&plug); + ret = btrfs_wait_tree_log_extents(log, mark); + wait_log_commit(log_root_tree, +@@ -3487,12 +3460,9 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + ASSERT(root_log_ctx.log_transid == log_root_tree->log_transid, + "root_log_ctx.log_transid=%d log_root_tree->log_transid=%d", + root_log_ctx.log_transid, log_root_tree->log_transid); +- atomic_set(&log_root_tree->log_commit[index2], 1); ++ log_root_tree->log_commit[index2] = true; + +- if (atomic_read(&log_root_tree->log_commit[(index2 + 1) % 2])) { +- wait_log_commit(log_root_tree, +- root_log_ctx.log_transid - 1); +- } ++ wait_log_commit(log_root_tree, root_log_ctx.log_transid - 1); + + /* + * now that we've moved on to the tree of log tree roots, +@@ -3590,7 +3560,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + + /* + * We know there can only be one task here, since we have not yet set +- * root->log_commit[index1] to 0 and any task attempting to sync the ++ * root->log_commit[index1] to false and any task attempting to sync the + * log must wait for the previous log transaction to commit if it's + * still in progress or wait for the current log transaction commit if + * someone else already started it. We use <= and not < because the +@@ -3606,7 +3576,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + btrfs_remove_all_log_ctxs(log_root_tree, index2, ret); + + log_root_tree->log_transid_committed++; +- atomic_set(&log_root_tree->log_commit[index2], 0); ++ log_root_tree->log_commit[index2] = false; + mutex_unlock(&log_root_tree->log_mutex); + + /* +@@ -3619,7 +3589,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + mutex_lock(&root->log_mutex); + btrfs_remove_all_log_ctxs(root, index1, ret); + root->log_transid_committed++; +- atomic_set(&root->log_commit[index1], 0); ++ root->log_commit[index1] = false; + mutex_unlock(&root->log_mutex); + + /* +@@ -5622,6 +5592,15 @@ static int btrfs_log_holes(struct btrfs_trans_handle *trans, + if (!btrfs_fs_incompat(fs_info, NO_HOLES) || i_size == 0) + return 0; + ++ /* ++ * If there are no prealloc extents (which can be located past i_size), ++ * and disk space used is greater than or equals to i_size, then there ++ * are no holes. ++ */ ++ if (!(inode->flags & BTRFS_INODE_PREALLOC) && ++ i_size <= inode_get_bytes(&inode->vfs_inode)) ++ return 0; ++ + key.objectid = ino; + key.type = BTRFS_EXTENT_DATA_KEY; + key.offset = 0; +diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c +--- a/fs/btrfs/verity.c ++++ b/fs/btrfs/verity.c +@@ -638,7 +638,7 @@ static int btrfs_end_enable_verity(struct file *filp, const void *desc, + rollback_ret = rollback_verity(inode); + if (rollback_ret) + btrfs_err(inode->root->fs_info, +- "failed to rollback verity items: %d", rollback_ret); ++ "failed to rollback verity items: %pe", ERR_PTR(rollback_ret)); + return ret; + } + +@@ -720,14 +720,18 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, + goto out; + + folio_lock(folio); +- /* If it's not uptodate after we have the lock, we got a read error. */ +- if (!folio_test_uptodate(folio)) { ++ /* Folio was truncated from mapping. */ ++ if (!folio->mapping) { + folio_unlock(folio); + folio_put(folio); +- return ERR_PTR(-EIO); ++ goto again; + } +- folio_unlock(folio); +- goto out; ++ /* Another reader may have filled the folio while we waited. */ ++ if (folio_test_uptodate(folio)) { ++ folio_unlock(folio); ++ goto out; ++ } ++ goto read_folio; + } + + folio = filemap_alloc_folio(mapping_gfp_constraint(inode->i_mapping, ~__GFP_FS), +@@ -744,6 +748,7 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, + return ERR_PTR(ret); + } + ++read_folio: + /* + * Merkle item keys are indexed from byte 0 in the merkle tree. + * They have the form: +@@ -753,6 +758,7 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, + ret = read_key_bytes(BTRFS_I(inode), BTRFS_VERITY_MERKLE_ITEM_KEY, off, + folio_address(folio), PAGE_SIZE, folio); + if (ret < 0) { ++ folio_unlock(folio); + folio_put(folio); + return ERR_PTR(ret); + } +diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c +--- a/fs/btrfs/volumes.c ++++ b/fs/btrfs/volumes.c +@@ -740,41 +740,6 @@ const u8 *btrfs_sb_fsid_ptr(const struct btrfs_super_block *sb) + return has_metadata_uuid ? sb->metadata_uuid : sb->fsid; + } + +-static bool is_same_device(struct btrfs_device *device, const char *new_path) +-{ +- struct path old = { .mnt = NULL, .dentry = NULL }; +- struct path new = { .mnt = NULL, .dentry = NULL }; +- char AUTO_KFREE(old_path); +- bool is_same = false; +- int ret; +- +- if (!device->name) +- goto out; +- +- old_path = kzalloc(PATH_MAX, GFP_NOFS); +- if (!old_path) +- goto out; +- +- rcu_read_lock(); +- ret = strscpy(old_path, rcu_dereference(device->name), PATH_MAX); +- rcu_read_unlock(); +- if (ret < 0) +- goto out; +- +- ret = kern_path(old_path, LOOKUP_FOLLOW, &old); +- if (ret) +- goto out; +- ret = kern_path(new_path, LOOKUP_FOLLOW, &new); +- if (ret) +- goto out; +- if (path_equal(&old, &new)) +- is_same = true; +-out: +- path_put(&old); +- path_put(&new); +- return is_same; +-} +- + /* + * Add new device to list of registered devices + * +@@ -895,7 +860,7 @@ static noinline struct btrfs_device *device_list_add(const char *path, + MAJOR(path_devt), MINOR(path_devt), + current->comm, task_pid_nr(current)); + +- } else if (!device->name || !is_same_device(device, path)) { ++ } else if (!device->name || device->devt != path_devt) { + const char *old_name; + + /* +@@ -4588,7 +4553,7 @@ static int __btrfs_balance(struct btrfs_fs_info *fs_info) + if (ret == -ENOSPC) { + enospc_errors++; + } else if (ret == -ETXTBSY) { +- btrfs_info(fs_info, ++ btrfs_warn(fs_info, + "skipping relocation of block group %llu due to active swapfile", + found_key.offset); + ret = 0; +@@ -6049,6 +6014,19 @@ struct btrfs_chunk_map *btrfs_alloc_chunk_map(int num_stripes, gfp_t gfp) + return map; + } + ++static void set_real_chunk_type(struct btrfs_chunk_map *map) ++{ ++ map->type = map->on_disk_type; ++ if (likely((map->on_disk_type & BTRFS_BLOCK_GROUP_RAID56_MASK) == 0 || ++ nr_data_stripes(map) > 1)) ++ return; ++ if (map->on_disk_type & BTRFS_BLOCK_GROUP_RAID5) ++ map->type |= BTRFS_BLOCK_GROUP_RAID1; ++ else ++ map->type |= BTRFS_BLOCK_GROUP_RAID1C3; ++ map->type &= ~BTRFS_BLOCK_GROUP_RAID56_MASK; ++} ++ + static struct btrfs_block_group *create_chunk(struct btrfs_trans_handle *trans, + struct alloc_chunk_ctl *ctl, + struct btrfs_device_info *devices_info) +@@ -6067,11 +6045,10 @@ static struct btrfs_block_group *create_chunk(struct btrfs_trans_handle *trans, + map->start = start; + map->chunk_len = ctl->chunk_size; + map->stripe_size = ctl->stripe_size; +- map->type = type; +- map->io_align = BTRFS_STRIPE_LEN; +- map->io_width = BTRFS_STRIPE_LEN; ++ map->on_disk_type = type; + map->sub_stripes = ctl->sub_stripes; + map->num_stripes = ctl->num_stripes; ++ set_real_chunk_type(map); + + for (int i = 0; i < ctl->ndevs; i++) { + for (int j = 0; j < ctl->dev_stripes; j++) { +@@ -6250,7 +6227,7 @@ int btrfs_chunk_alloc_add_chunk_item(struct btrfs_trans_handle *trans, + btrfs_set_stack_chunk_length(chunk, bg->length); + btrfs_set_stack_chunk_owner(chunk, BTRFS_EXTENT_TREE_OBJECTID); + btrfs_set_stack_chunk_stripe_len(chunk, BTRFS_STRIPE_LEN); +- btrfs_set_stack_chunk_type(chunk, map->type); ++ btrfs_set_stack_chunk_type(chunk, map->on_disk_type); + btrfs_set_stack_chunk_num_stripes(chunk, map->num_stripes); + btrfs_set_stack_chunk_io_align(chunk, BTRFS_STRIPE_LEN); + btrfs_set_stack_chunk_io_width(chunk, BTRFS_STRIPE_LEN); +@@ -7632,9 +7609,7 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf, + map->start = logical; + map->chunk_len = length; + map->num_stripes = num_stripes; +- map->io_width = btrfs_chunk_io_width(leaf, chunk); +- map->io_align = btrfs_chunk_io_align(leaf, chunk); +- map->type = type; ++ map->on_disk_type = type; + /* + * We can't use the sub_stripes value, as for profiles other than + * RAID10, they may have 0 as sub_stripes for filesystems created by +@@ -7645,6 +7620,7 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf, + */ + map->sub_stripes = btrfs_raid_array[index].sub_stripes; + map->verified_stripes = 0; ++ set_real_chunk_type(map); + + if (num_stripes > 0) + map->stripe_size = btrfs_calc_stripe_length(map); +diff --git a/fs/btrfs/volumes.h b/fs/btrfs/volumes.h +--- a/fs/btrfs/volumes.h ++++ b/fs/btrfs/volumes.h +@@ -632,9 +632,15 @@ struct btrfs_chunk_map { + u64 start; + u64 chunk_len; + u64 stripe_size; ++ /* ++ * The real type that is utilized during logical address mapping. ++ * ++ * For most profiles it matches @on_disk_type, but for single-data-RAID56, ++ * the real type will be set to RAID1/RAID1C3, to avoid unsupported ++ * operations from raid56 lib. ++ */ + u64 type; +- int io_align; +- int io_width; ++ u64 on_disk_type; + int num_stripes; + int sub_stripes; + struct btrfs_io_stripe stripes[]; +diff --git a/include/trace/events/btrfs.h b/include/trace/events/btrfs.h +--- a/include/trace/events/btrfs.h ++++ b/include/trace/events/btrfs.h +@@ -1613,9 +1613,9 @@ TRACE_EVENT(btrfs_sync_log_enter, + __entry->log_transid_committed = + data_race(root->log_transid_committed); + __entry->log_committing = +- atomic_read(&root->log_commit[ctx->log_transid % 2]); ++ data_race(root->log_commit[ctx->log_transid % 2]); + __entry->log_committing_prev = +- atomic_read(&root->log_commit[(ctx->log_transid + 1) % 2]); ++ data_race(root->log_commit[(ctx->log_transid + 1) % 2]); + __entry->log_writers = atomic_read(&root->log_writers); + ), + diff --git a/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes.patch b/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes.patch new file mode 100644 index 0000000..c5a4318 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes.patch @@ -0,0 +1,386 @@ +diff --git a/fs/btrfs/dev-replace.c b/fs/btrfs/dev-replace.c +--- a/fs/btrfs/dev-replace.c ++++ b/fs/btrfs/dev-replace.c +@@ -626,7 +626,7 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info, + + ret = mark_block_group_to_copy(fs_info, src_device); + if (ret) +- return ret; ++ goto leave; + + down_write(&dev_replace->rwsem); + dev_replace->replace_task = current; +diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -3436,6 +3436,9 @@ int btrfs_finish_one_ordered(struct btrfs_ordered_extent *ordered_extent) + */ + btrfs_remove_ordered_extent(ordered_extent); + ++ /* Cleanup any remaining biocs attached to the OE. */ ++ btrfs_cleanup_ordered_bioc_list(ordered_extent); ++ + /* once for us */ + btrfs_put_ordered_extent(ordered_extent); + /* once for the tree */ +diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c +--- a/fs/btrfs/ioctl.c ++++ b/fs/btrfs/ioctl.c +@@ -384,6 +384,7 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + inode_flags &= ~BTRFS_INODE_COMPRESS; + inode_flags |= BTRFS_INODE_NOCOMPRESS; + } else if (fsflags & FS_COMPR_FL) { ++ enum btrfs_compression_type comp_type; + + if (IS_SWAPFILE(&inode->vfs_inode)) + return -ETXTBSY; +@@ -391,9 +392,23 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + inode_flags |= BTRFS_INODE_COMPRESS; + inode_flags &= ~BTRFS_INODE_NOCOMPRESS; + +- comp = btrfs_compress_type2str(fs_info->compress_type); +- if (!comp || comp[0] == 0) +- comp = btrfs_compress_type2str(BTRFS_COMPRESS_ZLIB); ++ /* ++ * Keep the algorithm recorded in the compression property, ++ * otherwise changing an unrelated attribute would reset it to ++ * the mount default, since FS_IOC_SETFLAGS callers write back ++ * the whole flag set they got from FS_IOC_GETFLAGS and that ++ * includes FS_COMPR_FL for any inode carrying the property. ++ * ++ * Inodes with the compress flag set but no property keep using ++ * the mount default, so they behave as before. ++ */ ++ if (inode->prop_compress) ++ comp_type = inode->prop_compress; ++ else if (fs_info->compress_type) ++ comp_type = fs_info->compress_type; ++ else ++ comp_type = BTRFS_COMPRESS_ZLIB; ++ comp = btrfs_compress_type2str(comp_type); + } else { + inode_flags &= ~(BTRFS_INODE_COMPRESS | BTRFS_INODE_NOCOMPRESS); + } +diff --git a/fs/btrfs/raid-stripe-tree.c b/fs/btrfs/raid-stripe-tree.c +--- a/fs/btrfs/raid-stripe-tree.c ++++ b/fs/btrfs/raid-stripe-tree.c +@@ -310,8 +310,10 @@ static int update_raid_extent_item(struct btrfs_trans_handle *trans, + + ret = btrfs_search_slot(trans, trans->fs_info->stripe_root, key, path, + 0, 1); +- if (ret) +- return (ret == 1 ? ret : -EINVAL); ++ if (ret > 0) ++ ret = -ENOENT; ++ if (ret < 0) ++ return ret; + + leaf = path->nodes[0]; + slot = path->slots[0]; +@@ -337,7 +339,6 @@ int btrfs_insert_one_raid_extent(struct btrfs_trans_handle *trans, + stripe_extent = kzalloc(item_size, GFP_NOFS); + if (unlikely(!stripe_extent)) { + btrfs_abort_transaction(trans, -ENOMEM); +- btrfs_end_transaction(trans); + return -ENOMEM; + } + +@@ -374,7 +375,7 @@ int btrfs_insert_raid_extent(struct btrfs_trans_handle *trans, + struct btrfs_ordered_extent *ordered_extent) + { + struct btrfs_io_context *bioc; +- int ret; ++ int ret = 0; + + if (!btrfs_fs_incompat(trans->fs_info, RAID_STRIPE_TREE)) + return 0; +@@ -382,17 +383,23 @@ int btrfs_insert_raid_extent(struct btrfs_trans_handle *trans, + list_for_each_entry(bioc, &ordered_extent->bioc_list, rst_ordered_entry) { + ret = btrfs_insert_one_raid_extent(trans, bioc); + if (ret) +- return ret; ++ break; + } + +- while (!list_empty(&ordered_extent->bioc_list)) { +- bioc = list_first_entry(&ordered_extent->bioc_list, ++ btrfs_cleanup_ordered_bioc_list(ordered_extent); ++ return ret; ++} ++ ++void btrfs_cleanup_ordered_bioc_list(struct btrfs_ordered_extent *ordered) ++{ ++ while (!list_empty(&ordered->bioc_list)) { ++ struct btrfs_io_context *bioc; ++ ++ bioc = list_first_entry(&ordered->bioc_list, + typeof(*bioc), rst_ordered_entry); + list_del(&bioc->rst_ordered_entry); + btrfs_put_bioc(bioc); + } +- +- return 0; + } + + int btrfs_get_raid_extent_offset(struct btrfs_fs_info *fs_info, +diff --git a/fs/btrfs/raid-stripe-tree.h b/fs/btrfs/raid-stripe-tree.h +--- a/fs/btrfs/raid-stripe-tree.h ++++ b/fs/btrfs/raid-stripe-tree.h +@@ -28,6 +28,7 @@ int btrfs_get_raid_extent_offset(struct btrfs_fs_info *fs_info, + u32 stripe_index, struct btrfs_io_stripe *stripe); + int btrfs_insert_raid_extent(struct btrfs_trans_handle *trans, + struct btrfs_ordered_extent *ordered_extent); ++void btrfs_cleanup_ordered_bioc_list(struct btrfs_ordered_extent *ordered); + + #ifdef CONFIG_BTRFS_FS_RUN_SANITY_TESTS + int btrfs_insert_one_raid_extent(struct btrfs_trans_handle *trans, +diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c +--- a/fs/btrfs/scrub.c ++++ b/fs/btrfs/scrub.c +@@ -1023,6 +1023,10 @@ static void scrub_stripe_report_errors(struct scrub_ctx *sctx, + + skip: + for_each_set_bit(sector_nr, &extent_bitmap, stripe->nr_sectors) { ++ const u64 sector_logical = stripe->logical + ++ ((u64)sector_nr << fs_info->sectorsize_bits); ++ const u64 sector_physical = physical + ++ ((u64)sector_nr << fs_info->sectorsize_bits); + bool repaired = false; + + if (scrub_bitmap_test_bit_is_metadata(stripe, sector_nr)) { +@@ -1051,12 +1055,12 @@ static void scrub_stripe_report_errors(struct scrub_ctx *sctx, + if (dev) { + btrfs_err_rl(fs_info, + "scrub: fixed up error at logical %llu on dev %s physical %llu", +- stripe->logical, btrfs_dev_name(dev), +- physical); ++ sector_logical, btrfs_dev_name(dev), ++ sector_physical); + } else { + btrfs_err_rl(fs_info, + "scrub: fixed up error at logical %llu on mirror %u", +- stripe->logical, stripe->mirror_num); ++ sector_logical, stripe->mirror_num); + } + continue; + } +@@ -1065,30 +1069,30 @@ static void scrub_stripe_report_errors(struct scrub_ctx *sctx, + if (dev) { + btrfs_err_rl(fs_info, + "scrub: unable to fixup (regular) error at logical %llu on dev %s physical %llu", +- stripe->logical, btrfs_dev_name(dev), +- physical); ++ sector_logical, btrfs_dev_name(dev), ++ sector_physical); + } else { + btrfs_err_rl(fs_info, + "scrub: unable to fixup (regular) error at logical %llu on mirror %u", +- stripe->logical, stripe->mirror_num); ++ sector_logical, stripe->mirror_num); + } + + if (scrub_bitmap_test_bit_io_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("i/o error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + if (scrub_bitmap_test_bit_csum_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("checksum error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + if (scrub_bitmap_test_bit_meta_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("header error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + if (scrub_bitmap_test_bit_meta_gen_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("generation error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + } + + /* Update the device stats. */ +diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c +--- a/fs/btrfs/send.c ++++ b/fs/btrfs/send.c +@@ -2065,7 +2065,7 @@ static int will_overwrite_ref(struct send_ctx *sctx, u64 dir, u64 dir_gen, + + ret = is_inode_existent(sctx, dir, dir_gen, NULL, &parent_root_dir_gen); + if (ret <= 0) +- return 0; ++ return ret; + + /* + * If we have a parent root we need to verify that the parent dir was +@@ -6417,6 +6417,13 @@ static int process_extent(struct send_ctx *sctx, + + if (S_ISLNK(sctx->cur_inode_mode)) + return 0; ++ if (unlikely(!S_ISREG(sctx->cur_inode_mode))) { ++ btrfs_crit(sctx->send_root->fs_info, ++ "send: extent for non-regular inode %llu root %llu mode 0%llo", ++ key->objectid, btrfs_root_id(sctx->send_root), ++ sctx->cur_inode_mode & S_IFMT); ++ return -EUCLEAN; ++ } + + if (sctx->parent_root && !sctx->cur_inode_new) { + ret = is_extent_unchanged(sctx, path, key); +diff --git a/fs/btrfs/tests/extent-io-tests.c b/fs/btrfs/tests/extent-io-tests.c +--- a/fs/btrfs/tests/extent-io-tests.c ++++ b/fs/btrfs/tests/extent-io-tests.c +@@ -133,14 +133,14 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) + if (IS_ERR(root)) { + test_std_err(TEST_ALLOC_ROOT); + ret = PTR_ERR(root); +- goto out; ++ goto out_root_info; + } + + inode = btrfs_new_test_inode(); + if (!inode) { + test_std_err(TEST_ALLOC_INODE); + ret = -ENOMEM; +- goto out; ++ goto out_root_info; + } + tmp = &BTRFS_I(inode)->io_tree; + BTRFS_I(inode)->root = root; +@@ -333,6 +333,7 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) + process_page_range(inode, 0, total_dirty - 1, + PROCESS_UNLOCK | PROCESS_RELEASE); + iput(inode); ++out_root_info: + btrfs_free_dummy_root(root); + btrfs_free_dummy_fs_info(fs_info); + return ret; +diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c +--- a/fs/btrfs/transaction.c ++++ b/fs/btrfs/transaction.c +@@ -458,8 +458,19 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans, + * through btrfs_record_root_in_trans without having to take the + * lock. smp_wmb() makes sure that all the writes above are + * done before we pop in the zero below ++ * ++ * If @force is true, it means the call is from ++ * qgroup_account_snapshot(), which only requires radix tree ++ * tracking. ++ * We should not force reloc root creation here, as the root ++ * may have already been modified, and in that case ++ * root->commit_root has already been dropped. ++ * ++ * Using that commit root will cause the reloc root to refer ++ * to a deleted extent, causing extent tree corruption. + */ +- ret = btrfs_init_reloc_root(trans, root); ++ if (!force) ++ ret = btrfs_init_reloc_root(trans, root); + smp_mb__before_atomic(); + clear_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state); + } +@@ -2583,6 +2594,12 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans) + ret = btrfs_write_and_wait_transaction(trans); + if (unlikely(ret)) { + btrfs_err(fs_info, "error while writing out transaction: %pe", ERR_PTR(ret)); ++ /* ++ * Abort before releasing tree_log_mutex, so a log sync waiting ++ * on it sees the fs error and skips writing super_for_commit ++ * for this failed transaction. See btrfs_sync_log(). ++ */ ++ btrfs_abort_transaction(trans, ret); + mutex_unlock(&fs_info->tree_log_mutex); + goto scrub_continue; + } +diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c +--- a/fs/btrfs/volumes.c ++++ b/fs/btrfs/volumes.c +@@ -3035,7 +3035,11 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path + error_sysfs: + btrfs_sysfs_remove_device(device); + mutex_lock(&fs_info->fs_devices->device_list_mutex); ++ if (seeding_dev) ++ btrfs_assign_next_active_device(device, seed_devices->latest_dev); + mutex_lock(&fs_info->chunk_mutex); ++ if (!list_empty(&device->post_commit_list)) ++ list_del_init(&device->post_commit_list); + list_del_rcu(&device->dev_list); + list_del(&device->dev_alloc_list); + fs_info->fs_devices->num_devices--; +diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c +--- a/fs/btrfs/zoned.c ++++ b/fs/btrfs/zoned.c +@@ -2626,16 +2626,13 @@ static int do_zone_finish(struct btrfs_block_group *block_group, bool fully_writ + down_read(&dev_replace->rwsem); + map = block_group->physical_map; + for (i = 0; i < map->num_stripes; i++) { +- + ret = call_zone_finish(block_group, &map->stripes[i]); +- if (ret) { +- up_read(&dev_replace->rwsem); +- return ret; +- } ++ if (ret) ++ break; + } + up_read(&dev_replace->rwsem); + +- if (!fully_written) ++ if (!ret && !fully_written) + btrfs_dec_block_group_ro(block_group); + + spin_lock(&fs_info->zone_active_bgs_lock); +@@ -2648,7 +2645,7 @@ static int do_zone_finish(struct btrfs_block_group *block_group, bool fully_writ + + clear_and_wake_up_bit(BTRFS_FS_NEED_ZONE_FINISH, &fs_info->flags); + +- return 0; ++ return ret; + } + + int btrfs_zone_finish(struct btrfs_block_group *block_group) +@@ -2713,6 +2710,7 @@ int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 leng + { + struct btrfs_block_group *block_group; + u64 min_alloc_bytes; ++ int ret = 0; + + if (!btrfs_is_zoned(fs_info)) + return 0; +@@ -2732,11 +2730,11 @@ int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 leng + block_group->start + block_group->zone_capacity) + goto out; + +- do_zone_finish(block_group, true); ++ ret = do_zone_finish(block_group, true); + + out: + btrfs_put_block_group(block_group); +- return 0; ++ return ret; + } + + static void btrfs_zone_finish_endio_workfn(struct work_struct *work) +diff --git a/fs/btrfs/zstd.c b/fs/btrfs/zstd.c +--- a/fs/btrfs/zstd.c ++++ b/fs/btrfs/zstd.c +@@ -307,8 +307,17 @@ struct list_head *zstd_get_workspace(struct btrfs_fs_info *fs_info, int level) + DEFINE_WAIT(wait); + + prepare_to_wait(&zwsm->wait, &wait, TASK_UNINTERRUPTIBLE); +- schedule(); ++ /* ++ * Re-check after being queued: zstd_put_workspace() only wakes ++ * a queue that already has a sleeper, so a workspace returned ++ * since the failed allocation woke nobody. ++ */ ++ ws = zstd_find_workspace(fs_info, level); ++ if (!ws) ++ schedule(); + finish_wait(&zwsm->wait, &wait); ++ if (ws) ++ return ws; + + goto again; + } diff --git a/pkgbuilds/linux-omarchy-bore/0302-btrfs-zstd-decompress-direct-to-page.patch b/pkgbuilds/linux-omarchy-bore/0302-btrfs-zstd-decompress-direct-to-page.patch new file mode 100644 index 0000000..d949f95 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0302-btrfs-zstd-decompress-direct-to-page.patch @@ -0,0 +1,109 @@ +diff --git c/fs/btrfs/zstd.c i/fs/btrfs/zstd.c +--- c/fs/btrfs/zstd.c ++++ i/fs/btrfs/zstd.c +@@ -589,10 +589,48 @@ int zstd_compress_bio(struct list_head *ws, struct compressed_bio *cb) + return ret; + } + ++/* ++ * Map the destination for the next chunk of output. ++ * ++ * @decompressed is the offset of the next output byte inside the fully ++ * decompressed extent. If that offset has reached the current destination ++ * segment, its page-bounded bio_vec is kmapped so that zstd can write into the ++ * page cache directly, and the number of bytes writable there is returned. ++ * Otherwise @kaddr_ret is set to NULL and the number of bytes to skip before ++ * that segment is returned. This covers both the initial prefix and gaps in ++ * the destination bio. ++ */ ++static u32 zstd_map_dest(struct compressed_bio *cb, u32 decompressed, ++ void **kaddr_ret) ++{ ++ struct bio *orig_bio = &cb->orig_bbio->bio; ++ struct bio_vec bvec; ++ u32 bvec_offset; ++ u32 off; ++ ++ bvec = bio_iter_iovec(orig_bio, orig_bio->bi_iter); ++ /* ++ * cb->start may underflow, but subtracting that value can still give us ++ * the correct offset inside the full decompressed extent. ++ */ ++ bvec_offset = page_offset(bvec.bv_page) + bvec.bv_offset - cb->start; ++ ++ if (decompressed < bvec_offset) { ++ *kaddr_ret = NULL; ++ return bvec_offset - decompressed; ++ } ++ ++ off = decompressed - bvec_offset; ++ ASSERT(off < bvec.bv_len); ++ *kaddr_ret = bvec_kmap_local(&bvec) + off; ++ return bvec.bv_len - off; ++} ++ + int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + { + struct btrfs_fs_info *fs_info = cb_to_fs_info(cb); + struct workspace *workspace = list_entry(ws, struct workspace, list); ++ struct bio *orig_bio = &cb->orig_bbio->bio; + struct folio_iter fi; + size_t srclen = bio_get_size(&cb->bbio.bio); + zstd_dstream *stream; +@@ -600,7 +638,6 @@ int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + const unsigned int min_folio_size = btrfs_min_folio_size(fs_info); + unsigned long folio_in_index = 0; + unsigned long total_folios_in = DIV_ROUND_UP(srclen, min_folio_size); +- unsigned long buf_start; + unsigned long total_out = 0; + + bio_first_folio(&fi, &cb->bbio.bio, 0); +@@ -624,15 +661,26 @@ int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + workspace->in_buf.pos = 0; + workspace->in_buf.size = min_t(size_t, srclen, min_folio_size); + +- workspace->out_buf.dst = workspace->buf; +- workspace->out_buf.pos = 0; +- workspace->out_buf.size = fs_info->sectorsize; +- +- while (1) { ++ while (orig_bio->bi_iter.bi_size) { + size_t ret2; ++ void *kaddr; ++ u32 dstlen; ++ ++ dstlen = zstd_map_dest(cb, total_out, &kaddr); ++ if (kaddr) { ++ workspace->out_buf.dst = kaddr; ++ workspace->out_buf.size = dstlen; ++ } else { ++ workspace->out_buf.dst = workspace->buf; ++ workspace->out_buf.size = min_t(u32, dstlen, ++ fs_info->sectorsize); ++ } ++ workspace->out_buf.pos = 0; + + ret2 = zstd_decompress_stream(stream, &workspace->out_buf, + &workspace->in_buf); ++ if (kaddr) ++ kunmap_local(kaddr); + if (unlikely(zstd_is_error(ret2))) { + struct btrfs_inode *inode = cb->bbio.inode; + +@@ -643,14 +691,9 @@ int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + ret = -EIO; + goto done; + } +- buf_start = total_out; + total_out += workspace->out_buf.pos; +- workspace->out_buf.pos = 0; +- +- ret = btrfs_decompress_buf2page(workspace->out_buf.dst, +- total_out - buf_start, cb, buf_start); +- if (ret == 0) +- break; ++ if (kaddr) ++ bio_advance(orig_bio, workspace->out_buf.pos); + + if (workspace->in_buf.pos >= srclen) + break; diff --git a/pkgbuilds/linux-omarchy-bore/0310-fuse-eof-zeroing.patch b/pkgbuilds/linux-omarchy-bore/0310-fuse-eof-zeroing.patch new file mode 100644 index 0000000..b34a909 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0310-fuse-eof-zeroing.patch @@ -0,0 +1,42 @@ +diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c +--- a/fs/fuse/dir.c ++++ b/fs/fuse/dir.c +@@ -2280,6 +2280,9 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + */ + if ((is_truncate || !is_wb) && + S_ISREG(inode->i_mode) && oldsize != outarg.attr.size) { ++ if (outarg.attr.size > oldsize) ++ truncate_pagecache_range(inode, oldsize, ++ outarg.attr.size - 1); + truncate_pagecache(inode, outarg.attr.size); + invalidate_inode_pages2(mapping); + } +diff --git a/fs/fuse/file.c b/fs/fuse/file.c +--- a/fs/fuse/file.c ++++ b/fs/fuse/file.c +@@ -1360,9 +1360,13 @@ static ssize_t fuse_perform_write(struct kiocb *iocb, struct iov_iter *ii) + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_inode *fi = get_fuse_inode(inode); + loff_t pos = iocb->ki_pos; ++ loff_t old_size = i_size_read(inode); + int err = 0; + ssize_t res = 0; + ++ if (pos > old_size) ++ truncate_pagecache_range(inode, old_size, pos - 1); ++ + if (inode->i_size < pos + iov_iter_count(ii)) + set_bit(FUSE_I_SIZE_UNSTABLE, &fi->state); + +@@ -2908,6 +2912,11 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset, + + /* we could have extended the file */ + if (!(mode & FALLOC_FL_KEEP_SIZE)) { ++ loff_t oldsize = i_size_read(inode); ++ ++ if (offset + length > oldsize) ++ truncate_pagecache_range(inode, oldsize, ++ offset + length - 1); + if (fuse_write_update_attr(inode, offset + length, length)) + file_update_time(file); + } diff --git a/pkgbuilds/linux-omarchy-bore/0311-fuse-perf.patch b/pkgbuilds/linux-omarchy-bore/0311-fuse-perf.patch new file mode 100644 index 0000000..5162077 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0311-fuse-perf.patch @@ -0,0 +1,104 @@ +diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c +--- a/fs/fuse/dev.c ++++ b/fs/fuse/dev.c +@@ -216,10 +216,13 @@ EXPORT_SYMBOL_GPL(fuse_req_hash); + /* + * A new request is available, wake fiq->waitq + */ +-static void fuse_dev_wake_and_unlock(struct fuse_iqueue *fiq) ++static void fuse_dev_wake_and_unlock(struct fuse_iqueue *fiq, bool sync) + __releases(fiq->lock) + { +- wake_up(&fiq->waitq); ++ if (sync) ++ wake_up_sync(&fiq->waitq); ++ else ++ wake_up(&fiq->waitq); + kill_fasync(&fiq->fasync, SIGIO, POLL_IN); + spin_unlock(&fiq->lock); + } +@@ -236,7 +239,7 @@ void fuse_dev_queue_forget(struct fuse_iqueue *fiq, + if (fiq->connected) { + fiq->forget_list_tail->next = forget; + fiq->forget_list_tail = forget; +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, false); + } else { + kfree(forget); + spin_unlock(&fiq->lock); +@@ -258,7 +261,7 @@ void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req) + list_del_init(&req->intr_entry); + spin_unlock(&fiq->lock); + } else { +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, false); + } + } else { + spin_unlock(&fiq->lock); +@@ -288,11 +291,13 @@ EXPORT_SYMBOL_GPL(fuse_request_assign_unique); + + static void fuse_dev_queue_req(struct fuse_iqueue *fiq, struct fuse_req *req) + { ++ bool sync = test_and_clear_bit(FR_SYNC_WAKEUP, &req->flags); ++ + spin_lock(&fiq->lock); + if (fiq->connected) { + fuse_request_assign_unique_locked(fiq, req); + list_add_tail(&req->list, &fiq->pending); +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, sync); + } else { + spin_unlock(&fiq->lock); + req->out.h.error = -ENOTCONN; +@@ -755,6 +760,11 @@ static void __fuse_request_send(struct fuse_req *req) + /* acquire extra reference, since request is still needed after + fuse_request_end() */ + __fuse_get_request(req); ++ /* ++ * This is a synchronous request: the caller will block waiting for ++ * the answer. Hint the scheduler via wake_up_sync(). ++ */ ++ set_bit(FR_SYNC_WAKEUP, &req->flags); + fuse_send_one(fiq, req); + + request_wait_answer(req); +@@ -1809,7 +1819,7 @@ void fuse_chan_resend(struct fuse_chan *fch) + } + /* iq and pq requests are both oldest to newest */ + list_splice(&to_queue, &fiq->pending); +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, false); + } + + /* Look up request on processing list by unique ID */ +diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h +--- a/fs/fuse/fuse_dev_i.h ++++ b/fs/fuse/fuse_dev_i.h +@@ -38,6 +38,8 @@ struct fuse_iqueue; + * @FR_PRIVATE: request is on private list + * @FR_ASYNC: request is asynchronous + * @FR_URING: request is handled through fuse-io-uring ++ * @FR_SYNC_WAKEUP: use synchronous wakeup when queueing this request to ++ * give the scheduler a hint about the waker task + */ + enum fuse_req_flag { + FR_ISREPLY, +@@ -53,6 +55,7 @@ enum fuse_req_flag { + FR_PRIVATE, + FR_ASYNC, + FR_URING, ++ FR_SYNC_WAKEUP, + }; + + /** +diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c +--- a/fs/fuse/inode.c ++++ b/fs/fuse/inode.c +@@ -1417,6 +1417,7 @@ static void process_init_reply(struct fuse_args *args, int error) + + fm->sb->s_bdi->ra_pages = + min(fm->sb->s_bdi->ra_pages, ra_pages); ++ fm->sb->s_bdi->io_pages = fc->max_pages; + fc->minor = arg->minor; + fc->max_write = arg->minor < 5 ? 4096 : arg->max_write; + fc->max_write = max_t(unsigned, 4096, fc->max_write); diff --git a/pkgbuilds/linux-omarchy-bore/0312-fuse-writethrough-uptodate.patch b/pkgbuilds/linux-omarchy-bore/0312-fuse-writethrough-uptodate.patch new file mode 100644 index 0000000..8aaf4c6 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0312-fuse-writethrough-uptodate.patch @@ -0,0 +1,38 @@ +diff --git a/fs/fuse/file.c b/fs/fuse/file.c +--- a/fs/fuse/file.c ++++ b/fs/fuse/file.c +@@ -1219,8 +1219,7 @@ static ssize_t fuse_send_write_pages(struct fuse_io_args *ia, + struct file *file = iocb->ki_filp; + struct fuse_file *ff = file->private_data; + struct fuse_mount *fm = ff->fm; +- unsigned int offset, i; +- bool short_write; ++ unsigned int i; + int err; + + for (i = 0; i < ap->num_folios; i++) +@@ -1235,24 +1234,9 @@ static ssize_t fuse_send_write_pages(struct fuse_io_args *ia, + if (!err && ia->write.out.size > count) + err = -EIO; + +- short_write = ia->write.out.size < count; +- offset = ap->descs[0].offset; +- count = ia->write.out.size; + for (i = 0; i < ap->num_folios; i++) { + struct folio *folio = ap->folios[i]; + +- if (err) { +- folio_clear_uptodate(folio); +- } else { +- if (count >= folio_size(folio) - offset) +- count -= folio_size(folio) - offset; +- else { +- if (short_write) +- folio_clear_uptodate(folio); +- count = 0; +- } +- offset = 0; +- } + if (ia->write.folio_locked && (i == ap->num_folios - 1)) + folio_unlock(folio); + folio_put(folio); diff --git a/pkgbuilds/linux-omarchy-bore/0313-fuse-background-wakeup.patch b/pkgbuilds/linux-omarchy-bore/0313-fuse-background-wakeup.patch new file mode 100644 index 0000000..77e750a --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0313-fuse-background-wakeup.patch @@ -0,0 +1,13 @@ +diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c +--- a/fs/fuse/dev.c ++++ b/fs/fuse/dev.c +@@ -409,7 +409,8 @@ void fuse_chan_max_background_set(struct fuse_chan *fch, unsigned int val) + fch->max_background = val; + fch->blocked = fch->num_background >= fch->max_background; + if (!fch->blocked) +- wake_up(&fch->blocked_waitq); ++ wake_up_nr(&fch->blocked_waitq, ++ fch->max_background - fch->num_background); + spin_unlock(&fch->bg_lock); + } + diff --git a/pkgbuilds/linux-omarchy-bore/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch b/pkgbuilds/linux-omarchy-bore/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch new file mode 100644 index 0000000..9a8f97f --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch @@ -0,0 +1,123 @@ +diff --git a/drivers/gpu/drm/drm_displayid_internal.h b/drivers/gpu/drm/drm_displayid_internal.h +index 4590d6a3d821..dbdc2053332d 100644 +--- a/drivers/gpu/drm/drm_displayid_internal.h ++++ b/drivers/gpu/drm/drm_displayid_internal.h +@@ -67,6 +67,7 @@ struct drm_edid; + #define DATA_BLOCK_2_TILED_DISPLAY_TOPOLOGY 0x28 + #define DATA_BLOCK_2_CONTAINER_ID 0x29 + #define DATA_BLOCK_2_TYPE_10_FORMULA_TIMING 0x2a ++#define DATA_BLOCK_2_ADAPTIVE_SYNC 0x2b + #define DATA_BLOCK_2_VENDOR_SPECIFIC 0x7e + #define DATA_BLOCK_2_CTA_DISPLAY_ID 0x81 + +diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c +index df3c25bac761..354d4ff1aff7 100644 +--- a/drivers/gpu/drm/drm_edid.c ++++ b/drivers/gpu/drm/drm_edid.c +@@ -6598,6 +6598,49 @@ static void drm_get_monitor_range(struct drm_connector *connector, + info->monitor_range.min_vfreq, info->monitor_range.max_vfreq); + } + ++#define DISPLAYID_ADAPTIVE_SYNC_DESC_SIZE 6 ++ ++static bool drm_update_displayid_adaptive_sync_range(struct drm_connector *connector, ++ const struct displayid_block *block) ++{ ++ struct drm_monitor_range_info *range = &connector->display_info.monitor_range; ++ const u8 *data = (const u8 *)(block + 1); ++ u16 best_min = 0, best_max = 0; ++ unsigned int best_span = 0; ++ int i; ++ ++ if (block->rev != 0 || block->num_bytes < DISPLAYID_ADAPTIVE_SYNC_DESC_SIZE || ++ block->num_bytes % DISPLAYID_ADAPTIVE_SYNC_DESC_SIZE) ++ return false; ++ ++ for (i = 0; i < block->num_bytes; i += DISPLAYID_ADAPTIVE_SYNC_DESC_SIZE) { ++ const u8 *desc = &data[i]; ++ u16 min_vfreq = desc[2]; ++ u16 max_vfreq = ((((u16)desc[4]) & 0x3) << 8) | desc[3]; ++ unsigned int span; ++ ++ max_vfreq += 1; ++ if (!min_vfreq || max_vfreq <= min_vfreq) ++ continue; ++ ++ span = max_vfreq - min_vfreq; ++ if (span <= best_span) ++ continue; ++ ++ best_min = min_vfreq; ++ best_max = max_vfreq; ++ best_span = span; ++ } ++ ++ if (best_span) { ++ range->min_vfreq = best_min; ++ range->max_vfreq = best_max; ++ return true; ++ } ++ ++ return false; ++} ++ + static void drm_parse_vesa_mso_data(struct drm_connector *connector, + const struct displayid_block *block) + { +@@ -6720,26 +6763,43 @@ static void update_displayid_info(struct drm_connector *connector, + { + struct drm_display_info *info = &connector->display_info; + const struct displayid_block *block; ++ bool adaptive_sync_range = false; ++ bool displayid_base_logged = false; + struct displayid_iter iter; + + displayid_iter_edid_begin(drm_edid, &iter); + displayid_iter_for_each(block, &iter) { +- drm_dbg_kms(connector->dev, +- "[CONNECTOR:%d:%s] DisplayID extension version 0x%02x, primary use 0x%02x\n", +- connector->base.id, connector->name, +- displayid_version(&iter), +- displayid_primary_use(&iter)); +- if (displayid_version(&iter) == DISPLAY_ID_STRUCTURE_VER_20 && +- (displayid_primary_use(&iter) == PRIMARY_USE_HEAD_MOUNTED_VR || +- displayid_primary_use(&iter) == PRIMARY_USE_HEAD_MOUNTED_AR)) +- info->non_desktop = true; +- + /* +- * We're only interested in the base section here, no need to +- * iterate further. ++ * Primary use is a DisplayID base section property, but later ++ * blocks may still carry useful metadata like adaptive sync ranges. + */ +- break; ++ if (!displayid_base_logged) { ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] DisplayID extension version 0x%02x, primary use 0x%02x\n", ++ connector->base.id, connector->name, ++ displayid_version(&iter), ++ displayid_primary_use(&iter)); ++ if (displayid_version(&iter) == DISPLAY_ID_STRUCTURE_VER_20 && ++ (displayid_primary_use(&iter) == PRIMARY_USE_HEAD_MOUNTED_VR || ++ displayid_primary_use(&iter) == PRIMARY_USE_HEAD_MOUNTED_AR)) ++ info->non_desktop = true; ++ ++ displayid_base_logged = true; ++ } ++ ++ if (!info->monitor_range.min_vfreq && !info->monitor_range.max_vfreq && ++ block->tag == DATA_BLOCK_2_ADAPTIVE_SYNC) ++ adaptive_sync_range = ++ drm_update_displayid_adaptive_sync_range(connector, block); + } ++ ++ if (adaptive_sync_range) ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] DisplayID adaptive sync refresh rate range is %d Hz - %d Hz\n", ++ connector->base.id, connector->name, ++ info->monitor_range.min_vfreq, ++ info->monitor_range.max_vfreq); ++ + displayid_iter_end(&iter); + } + diff --git a/pkgbuilds/linux-omarchy-bore/0360-gpu-mem-cgroup.patch b/pkgbuilds/linux-omarchy-bore/0360-gpu-mem-cgroup.patch new file mode 100644 index 0000000..278fc0c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0360-gpu-mem-cgroup.patch @@ -0,0 +1,601 @@ +diff --git a/drivers/gpu/drm/nouveau/nouveau_ttm.c b/drivers/gpu/drm/nouveau/nouveau_ttm.c +--- a/drivers/gpu/drm/nouveau/nouveau_ttm.c ++++ b/drivers/gpu/drm/nouveau/nouveau_ttm.c +@@ -188,6 +188,11 @@ nouveau_ttm_init_vram(struct nouveau_drm *drm) + + man->func = &nouveau_vram_manager; + ++ man->cg = drmm_cgroup_register_region(drm->dev, "vram", ++ drm->gem.vram_available); ++ if (IS_ERR(man->cg)) ++ return PTR_ERR(man->cg); ++ + ttm_resource_manager_init(man, &drm->ttm.bdev, + drm->gem.vram_available >> PAGE_SHIFT); + ttm_set_driver_manager(&drm->ttm.bdev, TTM_PL_VRAM, man); +diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c +--- a/drivers/gpu/drm/ttm/ttm_bo.c ++++ b/drivers/gpu/drm/ttm/ttm_bo.c +@@ -488,6 +488,118 @@ int ttm_bo_evict_first(struct ttm_device *bdev, struct ttm_resource_manager *man + return ret; + } + ++struct ttm_bo_alloc_state { ++ /** @charge_pool: The memory pool the resource is charged to */ ++ struct dmem_cgroup_pool_state *charge_pool; ++ /** @limit_pool: Which pool limit we should test against */ ++ struct dmem_cgroup_pool_state *limit_pool; ++ /** @in_evict: Whether we are currently evicting buffers */ ++ bool in_evict; ++ /** @may_try_low: If only unprotected BOs, i.e. BOs whose cgroup ++ * is exceeding its dmem low/min protection, should be considered for eviction ++ */ ++ bool may_try_low; ++}; ++ ++/** ++ * ttm_bo_alloc_at_place - Attempt allocating a BO's backing store in a place ++ * ++ * @bo: The buffer to allocate the backing store of ++ * @place: The place to attempt allocation in ++ * @ctx: ttm_operation_ctx associated with this allocation ++ * @force_space: If we should evict buffers to force space ++ * @res: On allocation success, the resulting struct ttm_resource. ++ * @alloc_state: Object holding allocation state such as charged cgroups. ++ * ++ * Returns: ++ * -EBUSY: No space available, but allocation should be retried with ttm_bo_evict_alloc. ++ * -ENOSPC: No space available, allocation should not be retried. ++ * -ERESTARTSYS: An interruptible sleep was interrupted by a signal. ++ * ++ */ ++static int ttm_bo_alloc_at_place(struct ttm_buffer_object *bo, ++ const struct ttm_place *place, ++ bool force_space, ++ struct ttm_resource **res, ++ struct ttm_bo_alloc_state *alloc_state) ++{ ++ bool may_evict; ++ int ret; ++ ++ may_evict = !alloc_state->in_evict && force_space && ++ place->mem_type != TTM_PL_SYSTEM; ++ if (!alloc_state->charge_pool) { ++ ret = ttm_resource_try_charge(bo, place, &alloc_state->charge_pool, ++ force_space ? &alloc_state->limit_pool ++ : NULL); ++ if (ret) { ++ /* ++ * -EAGAIN means the charge failed, which we treat ++ * like an allocation failure. Therefore, return an ++ * error code indicating the allocation failed - ++ * either -EBUSY if the allocation should be ++ * retried with eviction, or -ENOSPC if there should ++ * be no second attempt. ++ */ ++ if (!alloc_state->in_evict) ++ alloc_state->may_try_low = may_evict; ++ if (ret == -EAGAIN) ++ ret = may_evict ? -EBUSY : -ENOSPC; ++ return ret; ++ } ++ } ++ ++ /* ++ * cgroup protection plays a special role in eviction. ++ * Conceptually, protection of memory via the dmem cgroup controller ++ * entitles the protected cgroup to use a certain amount of memory. ++ * There are two types of protection - the 'low' limit is a ++ * "best-effort" protection, whereas the 'min' limit provides a hard ++ * guarantee that memory within the cgroup's allowance will not be ++ * evicted under any circumstance. ++ * ++ * To faithfully model this concept in TTM, we also need to take cgroup ++ * protection into account when allocating. When allocation in one ++ * place fails, TTM will default to trying other places first before ++ * evicting. ++ * If the allocation is covered by dmem cgroup protection, however, ++ * this prevents the allocation from using the memory it is "entitled" ++ * to. To make sure unprotected allocations cannot push new protected ++ * allocations out of places they are "entitled" to use, we should ++ * evict buffers not covered by any cgroup protection, if this ++ * allocation is covered by cgroup protection. ++ * ++ * Buffers covered by 'min' protection are a special case - the 'min' ++ * limit is a stronger guarantee than 'low', and thus buffers protected ++ * by 'low' but not 'min' should also be considered for eviction. ++ * Buffers protected by 'min' will never be considered for eviction ++ * anyway, so the regular eviction path should be triggered here. ++ * Buffers protected by 'low' but not 'min' will take a special ++ * eviction path that only evicts buffers covered by neither 'low' or ++ * 'min' protections. ++ */ ++ if (!alloc_state->in_evict) { ++ may_evict |= dmem_cgroup_below_min(NULL, alloc_state->charge_pool); ++ alloc_state->may_try_low = may_evict; ++ ++ may_evict |= dmem_cgroup_below_low(NULL, alloc_state->charge_pool); ++ } ++ ++ ret = ttm_resource_alloc(bo, place, res, alloc_state->charge_pool); ++ if (ret) { ++ if (ret == -ENOSPC && may_evict) ++ ret = -EBUSY; ++ return ret; ++ } ++ ++ /* ++ * Ownership of charge_pool has been transferred to the TTM resource, ++ * don't make the caller think we still hold a reference to it. ++ */ ++ alloc_state->charge_pool = NULL; ++ return 0; ++} ++ + /** + * struct ttm_bo_evict_walk - Parameters for the evict walk. + */ +@@ -503,22 +615,61 @@ struct ttm_bo_evict_walk { + /** @evicted: Number of successful evictions. */ + unsigned long evicted; + +- /** @limit_pool: Which pool limit we should test against */ +- struct dmem_cgroup_pool_state *limit_pool; + /** @try_low: Whether we should attempt to evict BO's with low watermark threshold */ + bool try_low; + /** @hit_low: If we cannot evict a bo when @try_low is false (first pass) */ + bool hit_low; ++ ++ /** @alloc_state: State associated with the allocation attempt. */ ++ struct ttm_bo_alloc_state *alloc_state; + }; + + static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object *bo) + { + struct ttm_bo_evict_walk *evict_walk = + container_of(walk, typeof(*evict_walk), walk); ++ struct dmem_cgroup_pool_state *limit_pool, *ancestor = NULL; ++ bool evict_valuable; + s64 lret; + +- if (!dmem_cgroup_state_evict_valuable(evict_walk->limit_pool, bo->resource->css, +- evict_walk->try_low, &evict_walk->hit_low)) ++ /* ++ * If may_try_low is not set, then we're trying to evict unprotected ++ * buffers in favor of a protected allocation for charge_pool. Explicitly skip ++ * buffers belonging to the same cgroup here - that cgroup is definitely protected, ++ * even though dmem_cgroup_state_evict_valuable would allow the eviction because a ++ * cgroup is always allowed to evict from itself even if it is protected. ++ */ ++ if (!evict_walk->alloc_state->may_try_low && ++ bo->resource->css == evict_walk->alloc_state->charge_pool) ++ return 0; ++ ++ limit_pool = evict_walk->alloc_state->limit_pool; ++ /* ++ * If there is no explicit limit pool, find the root of the shared subtree between ++ * evictor and evictee. This is important so that recursive protection rules can ++ * apply properly: Recursive protection distributes cgroup protection afforded ++ * to a parent cgroup but not used explicitly by a child cgroup between all child ++ * cgroups (see docs of effective_protection in mm/page_counter.c). However, when ++ * direct siblings compete for memory, siblings that were explicitly protected ++ * should get prioritized over siblings that weren't. This only happens correctly ++ * when the root of the shared subtree is passed to ++ * dmem_cgroup_state_evict_valuable. Otherwise, the effective-protection ++ * calculation cannot distinguish direct siblings from unrelated subtrees and the ++ * calculated protection ends up wrong. ++ */ ++ if (!limit_pool) { ++ ancestor = dmem_cgroup_get_common_ancestor(bo->resource->css, ++ evict_walk->alloc_state->charge_pool); ++ limit_pool = ancestor; ++ } ++ ++ evict_valuable = dmem_cgroup_state_evict_valuable(limit_pool, bo->resource->css, ++ evict_walk->try_low, ++ &evict_walk->hit_low); ++ if (ancestor) ++ dmem_cgroup_pool_state_put(ancestor); ++ ++ if (!evict_valuable) + return 0; + + if (bo->pin_count || !bo->bdev->funcs->eviction_valuable(bo, evict_walk->place)) +@@ -537,8 +688,10 @@ static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object * + + evict_walk->evicted++; + if (evict_walk->res) +- lret = ttm_resource_alloc(evict_walk->evictor, evict_walk->place, +- evict_walk->res, NULL); ++ lret = ttm_bo_alloc_at_place(evict_walk->evictor, ++ evict_walk->place, false, ++ evict_walk->res, ++ evict_walk->alloc_state); + if (lret == 0) + return 1; + out: +@@ -560,7 +713,7 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, + struct ttm_operation_ctx *ctx, + struct ww_acquire_ctx *ticket, + struct ttm_resource **res, +- struct dmem_cgroup_pool_state *limit_pool) ++ struct ttm_bo_alloc_state *state) + { + struct ttm_bo_evict_walk evict_walk = { + .walk = { +@@ -573,15 +726,21 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, + .place = place, + .evictor = evictor, + .res = res, +- .limit_pool = limit_pool, ++ .alloc_state = state, + }; + s64 lret; + ++ state->in_evict = true; ++ + evict_walk.walk.arg.trylock_only = true; + lret = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, 1); + +- /* One more attempt if we hit low limit? */ +- if (!lret && evict_walk.hit_low) { ++ /* If we failed to find enough BOs to evict, but we skipped over ++ * some BOs because they were covered by dmem low protection, retry ++ * evicting these protected BOs too, except if we're told not to ++ * consider protected BOs at all. ++ */ ++ if (!lret && evict_walk.hit_low && state->may_try_low) { + evict_walk.try_low = true; + lret = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, 1); + } +@@ -602,11 +761,13 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, + } while (!lret && evict_walk.evicted); + + /* We hit the low limit? Try once more */ +- if (!lret && evict_walk.hit_low && !evict_walk.try_low) { ++ if (!lret && evict_walk.hit_low && !evict_walk.try_low && ++ state->may_try_low) { + evict_walk.try_low = true; + goto retry; + } + out: ++ state->in_evict = false; + if (lret < 0) + return lret; + if (lret == 0) +@@ -724,9 +885,8 @@ static int ttm_bo_alloc_resource(struct ttm_buffer_object *bo, + + for (i = 0; i < placement->num_placement; ++i) { + const struct ttm_place *place = &placement->placement[i]; +- struct dmem_cgroup_pool_state *limit_pool = NULL; ++ struct ttm_bo_alloc_state alloc_state = {}; + struct ttm_resource_manager *man; +- bool may_evict; + + man = ttm_manager_type(bdev, place->mem_type); + if (!man || !ttm_resource_manager_used(man)) +@@ -736,25 +896,30 @@ static int ttm_bo_alloc_resource(struct ttm_buffer_object *bo, + TTM_PL_FLAG_FALLBACK)) + continue; + +- may_evict = (force_space && place->mem_type != TTM_PL_SYSTEM); +- ret = ttm_resource_alloc(bo, place, res, force_space ? &limit_pool : NULL); +- if (ret) { +- if (ret != -ENOSPC) { +- dmem_cgroup_pool_state_put(limit_pool); +- return ret; +- } +- if (!may_evict) { +- dmem_cgroup_pool_state_put(limit_pool); +- continue; +- } ++ ret = ttm_bo_alloc_at_place(bo, place, force_space, res, ++ &alloc_state); + ++ if (ret == -ENOSPC) { ++ dmem_cgroup_uncharge(alloc_state.charge_pool, bo->base.size); ++ dmem_cgroup_pool_state_put(alloc_state.limit_pool); ++ continue; ++ } else if (ret == -EBUSY) { + ret = ttm_bo_evict_alloc(bdev, man, place, bo, ctx, +- ticket, res, limit_pool); +- dmem_cgroup_pool_state_put(limit_pool); +- if (ret == -EBUSY) +- continue; +- if (ret) ++ ticket, res, &alloc_state); ++ ++ dmem_cgroup_pool_state_put(alloc_state.limit_pool); ++ ++ if (ret) { ++ dmem_cgroup_uncharge(alloc_state.charge_pool, ++ bo->base.size); ++ if (ret == -EBUSY) ++ continue; + return ret; ++ } ++ } else if (ret) { ++ dmem_cgroup_uncharge(alloc_state.charge_pool, bo->base.size); ++ dmem_cgroup_pool_state_put(alloc_state.limit_pool); ++ return ret; + } + + ret = ttm_bo_add_pipelined_eviction_fences(bo, man, ctx->no_wait_gpu); +diff --git a/drivers/gpu/drm/ttm/ttm_resource.c b/drivers/gpu/drm/ttm/ttm_resource.c +--- a/drivers/gpu/drm/ttm/ttm_resource.c ++++ b/drivers/gpu/drm/ttm/ttm_resource.c +@@ -386,33 +386,52 @@ void ttm_resource_fini(struct ttm_resource_manager *man, + } + EXPORT_SYMBOL(ttm_resource_fini); + ++/** ++ * ttm_resource_try_charge - charge a resource manager's cgroup pool ++ * @bo: buffer for which an allocation should be charged ++ * @place: where the allocation is attempted to be placed ++ * @ret_pool: on charge success, the pool that was charged ++ * @ret_limit_pool: on charge failure, the pool responsible for the failure ++ * ++ * Should be used to charge cgroups before attempting resource allocation. ++ * When charging succeeds, the value of ret_pool should be passed to ++ * ttm_resource_alloc. ++ * ++ * Returns: 0 on charge success, negative errno on failure. ++ */ ++int ttm_resource_try_charge(struct ttm_buffer_object *bo, ++ const struct ttm_place *place, ++ struct dmem_cgroup_pool_state **ret_pool, ++ struct dmem_cgroup_pool_state **ret_limit_pool) ++{ ++ struct ttm_resource_manager *man = ++ ttm_manager_type(bo->bdev, place->mem_type); ++ ++ if (!man->cg) { ++ *ret_pool = NULL; ++ if (ret_limit_pool) ++ *ret_limit_pool = NULL; ++ return 0; ++ } ++ ++ return dmem_cgroup_try_charge(man->cg, bo->base.size, ret_pool, ++ ret_limit_pool); ++} ++ + int ttm_resource_alloc(struct ttm_buffer_object *bo, + const struct ttm_place *place, + struct ttm_resource **res_ptr, +- struct dmem_cgroup_pool_state **ret_limit_pool) ++ struct dmem_cgroup_pool_state *charge_pool) + { + struct ttm_resource_manager *man = + ttm_manager_type(bo->bdev, place->mem_type); +- struct dmem_cgroup_pool_state *pool = NULL; + int ret; + +- if (man->cg) { +- ret = dmem_cgroup_try_charge(man->cg, bo->base.size, &pool, ret_limit_pool); +- if (ret) { +- if (ret == -EAGAIN) +- ret = -ENOSPC; +- return ret; +- } +- } +- + ret = man->func->alloc(man, bo, place, res_ptr); +- if (ret) { +- if (pool) +- dmem_cgroup_uncharge(pool, bo->base.size); ++ if (ret) + return ret; +- } + +- (*res_ptr)->css = pool; ++ (*res_ptr)->css = charge_pool; + + spin_lock(&bo->bdev->lru_lock); + ttm_resource_add_bulk_move(*res_ptr, bo); +diff --git a/include/drm/ttm/ttm_resource.h b/include/drm/ttm/ttm_resource.h +--- a/include/drm/ttm/ttm_resource.h ++++ b/include/drm/ttm/ttm_resource.h +@@ -458,10 +458,14 @@ void ttm_resource_init(struct ttm_buffer_object *bo, + void ttm_resource_fini(struct ttm_resource_manager *man, + struct ttm_resource *res); + ++int ttm_resource_try_charge(struct ttm_buffer_object *bo, ++ const struct ttm_place *place, ++ struct dmem_cgroup_pool_state **ret_pool, ++ struct dmem_cgroup_pool_state **ret_limit_pool); + int ttm_resource_alloc(struct ttm_buffer_object *bo, + const struct ttm_place *place, + struct ttm_resource **res, +- struct dmem_cgroup_pool_state **ret_limit_pool); ++ struct dmem_cgroup_pool_state *charge_pool); + void ttm_resource_free(struct ttm_buffer_object *bo, struct ttm_resource **res); + bool ttm_resource_intersects(struct ttm_device *bdev, + struct ttm_resource *res, +diff --git a/include/linux/cgroup.h b/include/linux/cgroup.h +--- a/include/linux/cgroup.h ++++ b/include/linux/cgroup.h +@@ -623,6 +623,27 @@ static inline struct cgroup *cgroup_ancestor(struct cgroup *cgrp, + return cgrp->ancestors[ancestor_level]; + } + ++/** ++ * cgroup_common_ancestor - find common ancestor of two cgroups ++ * @a: first cgroup to find common ancestor of ++ * @b: second cgroup to find common ancestor of ++ * ++ * Find the first cgroup that is an ancestor of both @a and @b, if it exists ++ * and return a pointer to it. If such a cgroup doesn't exist, return NULL. ++ * ++ * This function is safe to call as long as both @a and @b are accessible. ++ */ ++static inline struct cgroup *cgroup_common_ancestor(struct cgroup *a, ++ struct cgroup *b) ++{ ++ int level; ++ ++ for (level = min(a->level, b->level); level >= 0; level--) ++ if (a->ancestors[level] == b->ancestors[level]) ++ return a->ancestors[level]; ++ return NULL; ++} ++ + /** + * task_under_cgroup_hierarchy - test task's membership of cgroup ancestry + * @task: the task to be tested +diff --git a/include/linux/cgroup_dmem.h b/include/linux/cgroup_dmem.h +--- a/include/linux/cgroup_dmem.h ++++ b/include/linux/cgroup_dmem.h +@@ -24,6 +24,12 @@ void dmem_cgroup_uncharge(struct dmem_cgroup_pool_state *pool, u64 size); + bool dmem_cgroup_state_evict_valuable(struct dmem_cgroup_pool_state *limit_pool, + struct dmem_cgroup_pool_state *test_pool, + bool ignore_low, bool *ret_hit_low); ++bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test); ++bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test); ++struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgroup_pool_state *a, ++ struct dmem_cgroup_pool_state *b); + + void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool); + #else +@@ -59,6 +65,25 @@ bool dmem_cgroup_state_evict_valuable(struct dmem_cgroup_pool_state *limit_pool, + return true; + } + ++static inline bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test) ++{ ++ return false; ++} ++ ++static inline bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test) ++{ ++ return false; ++} ++ ++static inline ++struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgroup_pool_state *a, ++ struct dmem_cgroup_pool_state *b) ++{ ++ return NULL; ++} ++ + static inline void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool) + { } + +diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c +--- a/kernel/cgroup/dmem.c ++++ b/kernel/cgroup/dmem.c +@@ -695,6 +695,110 @@ int dmem_cgroup_try_charge(struct dmem_cgroup_region *region, u64 size, + } + EXPORT_SYMBOL_GPL(dmem_cgroup_try_charge); + ++/** ++ * dmem_cgroup_below_min() - Tests whether current usage is within min limit. ++ * ++ * @root: Root of the subtree to calculate protection for, or NULL to calculate global protection. ++ * @test: The pool to test the usage/min limit of. ++ * ++ * Return: true if usage is below min and the cgroup is protected, false otherwise. ++ */ ++bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test) ++{ ++ if (root == test || !pool_parent(test)) ++ return false; ++ ++ if (!root) { ++ for (root = test; pool_parent(root); root = pool_parent(root)) ++ {} ++ } ++ ++ /* ++ * In mem_cgroup_below_min(), the memcg pendant, this call is missing. ++ * mem_cgroup_below_min() gets called during traversal of the cgroup tree, where ++ * protection is already calculated as part of the traversal. dmem cgroup eviction ++ * does not traverse the cgroup tree, so we need to recalculate effective protection ++ * here. ++ */ ++ dmem_cgroup_calculate_protection(root, test); ++ return page_counter_read(&test->cnt) <= READ_ONCE(test->cnt.emin); ++} ++EXPORT_SYMBOL_GPL(dmem_cgroup_below_min); ++ ++/** ++ * dmem_cgroup_below_low() - Tests whether current usage is within low limit. ++ * ++ * @root: Root of the subtree to calculate protection for, or NULL to calculate global protection. ++ * @test: The pool to test the usage/low limit of. ++ * ++ * Return: true if usage is below low and the cgroup is protected, false otherwise. ++ */ ++bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test) ++{ ++ if (root == test || !pool_parent(test)) ++ return false; ++ ++ if (!root) { ++ for (root = test; pool_parent(root); root = pool_parent(root)) ++ {} ++ } ++ ++ /* ++ * In mem_cgroup_below_low(), the memcg pendant, this call is missing. ++ * mem_cgroup_below_low() gets called during traversal of the cgroup tree, where ++ * protection is already calculated as part of the traversal. dmem cgroup eviction ++ * does not traverse the cgroup tree, so we need to recalculate effective protection ++ * here. ++ */ ++ dmem_cgroup_calculate_protection(root, test); ++ return page_counter_read(&test->cnt) <= READ_ONCE(test->cnt.elow); ++} ++EXPORT_SYMBOL_GPL(dmem_cgroup_below_low); ++ ++/** ++ * dmem_cgroup_get_common_ancestor(): Find the first common ancestor of two pools. ++ * @a: First pool to find the common ancestor of. ++ * @b: First pool to find the common ancestor of. ++ * ++ * Return: The first pool that is a parent of both @a and @b, or NULL if either @a or @b are NULL, ++ * or if such a pool does not exist. A reference to the returned pool is grabbed and must be ++ * released by the caller when it is done using the pool. ++ */ ++struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgroup_pool_state *a, ++ struct dmem_cgroup_pool_state *b) ++{ ++ struct cgroup *ancestor_cgroup; ++ struct cgroup_subsys_state *ancestor_css; ++ struct dmemcg_state *ancestor_dmemcs = NULL; ++ struct dmem_cgroup_pool_state *pool = NULL; ++ ++ if (!a || !b) ++ return NULL; ++ ++ ancestor_cgroup = cgroup_common_ancestor(a->cs->css.cgroup, b->cs->css.cgroup); ++ if (!ancestor_cgroup) ++ return NULL; ++ ++ rcu_read_lock(); ++ ancestor_css = cgroup_e_css(ancestor_cgroup, &dmem_cgrp_subsys); ++ if (css_tryget(ancestor_css)) ++ ancestor_dmemcs = css_to_dmemcs(ancestor_css); ++ rcu_read_unlock(); ++ ++ if (ancestor_dmemcs) { ++ pool = get_cg_pool_unlocked(css_to_dmemcs(ancestor_css), ++ a->region); ++ if (WARN_ON(IS_ERR(pool))) { ++ pool = NULL; ++ css_put(ancestor_css); ++ } ++ } ++ return pool; ++} ++EXPORT_SYMBOL_GPL(dmem_cgroup_get_common_ancestor); ++ + static int dmem_cgroup_region_capacity_show(struct seq_file *sf, void *v) + { + struct dmem_cgroup_region *region; diff --git a/pkgbuilds/linux-omarchy-bore/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch b/pkgbuilds/linux-omarchy-bore/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch new file mode 100644 index 0000000..32f3010 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch @@ -0,0 +1,13 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_alpm.c b/drivers/gpu/drm/i915/display/intel_alpm.c +index c6963ea420cc..c38d613f15d6 100644 +--- a/drivers/gpu/drm/i915/display/intel_alpm.c ++++ b/drivers/gpu/drm/i915/display/intel_alpm.c +@@ -399,7 +399,7 @@ static void lnl_alpm_configure(struct intel_dp *intel_dp, + ALPM_CTL_AUX_LESS_SLEEP_HOLD_TIME_50_SYMBOLS | + ALPM_CTL_AUX_LESS_WAKE_TIME(crtc_state->alpm_state.aux_less_wake_lines); + +- if (intel_dp->as_sdp_supported) { ++ if (intel_dp->as_sdp_supported && crtc_state->has_panel_replay) { + u32 pr_alpm_ctl = get_pr_alpm_as_sdp_transmission_time(crtc_state); + + if (crtc_state->link_off_after_as_sdp_when_pr_active) diff --git a/pkgbuilds/linux-omarchy-bore/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch b/pkgbuilds/linux-omarchy-bore/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch new file mode 100644 index 0000000..28a6330 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch @@ -0,0 +1,196 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h +index c21e0c0ef0b1..e62788328bfd 100644 +--- a/drivers/gpu/drm/i915/display/intel_display_types.h ++++ b/drivers/gpu/drm/i915/display/intel_display_types.h +@@ -1780,6 +1780,7 @@ struct intel_psr { + u32 dc3co_exitline; + u32 dc3co_exit_delay; + struct delayed_work dc3co_work; ++ struct delayed_work panel_replay_reenable_work; + u8 entry_setup_frames; + + u8 io_wake_lines; +diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c +index beaa1d62613d..644ab9b1bee9 100644 +--- a/drivers/gpu/drm/i915/display/intel_psr.c ++++ b/drivers/gpu/drm/i915/display/intel_psr.c +@@ -2475,6 +2475,7 @@ void intel_psr_disable(struct intel_dp *intel_dp, + mutex_unlock(&intel_dp->psr.lock); + cancel_work_sync(&intel_dp->psr.work); + cancel_delayed_work_sync(&intel_dp->psr.dc3co_work); ++ cancel_delayed_work_sync(&intel_dp->psr.panel_replay_reenable_work); + } + + /** +@@ -2506,6 +2507,7 @@ void intel_psr_pause(struct intel_dp *intel_dp) + + cancel_work_sync(&psr->work); + cancel_delayed_work_sync(&psr->dc3co_work); ++ cancel_delayed_work_sync(&psr->panel_replay_reenable_work); + } + + /** +@@ -3539,6 +3541,17 @@ static void intel_psr_handle_irq(struct intel_dp *intel_dp) + drm_dp_dpcd_writeb(&intel_dp->aux, DP_SET_POWER, DP_SET_POWER_D0); + } + ++#define PANEL_REPLAY_REENABLE_DELAY_MS 50 ++ ++static bool panel_replay_alpm_cursor_lag_workaround_enabled(struct intel_dp *intel_dp) ++{ ++ return intel_dp_is_edp(intel_dp) && ++ intel_dp->psr.panel_replay_enabled && ++ intel_dp->psr.sel_update_enabled && ++ intel_has_dpcd_quirk(intel_dp, ++ QUIRK_PANEL_REPLAY_ALPM_CURSOR_LAG); ++} ++ + static void intel_psr_work(struct work_struct *work) + { + struct intel_dp *intel_dp = +@@ -3557,6 +3570,11 @@ static void intel_psr_work(struct work_struct *work) + if (intel_dp->psr.pause_counter) + goto unlock; + ++ /* The dedicated delayed work owns re-entry while the workaround is armed. */ ++ if (panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp) && ++ delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work)) ++ goto unlock; ++ + /* + * We have to make sure PSR is ready for re-enable + * otherwise it keeps disabled until next full enable/disable cycle. +@@ -3566,6 +3584,10 @@ static void intel_psr_work(struct work_struct *work) + if (!__psr_wait_for_idle_locked(intel_dp)) + goto unlock; + ++ if (panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp) && ++ delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work)) ++ goto unlock; ++ + /* + * The delayed work can race with an invalidate hence we need to + * recheck. Since psr_flush first clears this and then reschedules we +@@ -3579,6 +3601,33 @@ static void intel_psr_work(struct work_struct *work) + mutex_unlock(&intel_dp->psr.lock); + } + ++static void panel_replay_reenable_work(struct work_struct *work) ++{ ++ struct intel_dp *intel_dp = ++ container_of(work, typeof(*intel_dp), ++ psr.panel_replay_reenable_work.work); ++ ++ mutex_lock(&intel_dp->psr.lock); ++ ++ if (!intel_dp->psr.enabled || ++ !panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp) || ++ intel_dp->psr.pause_counter || ++ READ_ONCE(intel_dp->psr.irq_aux_error)) ++ goto unlock; ++ ++ if (!__psr_wait_for_idle_locked(intel_dp)) ++ goto unlock; ++ ++ /* Recheck for activity or a newer deadline after the unlocked wait. */ ++ if (delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work) || ++ intel_dp->psr.busy_frontbuffer_bits || intel_dp->psr.active) ++ goto unlock; ++ ++ intel_psr_activate(intel_dp); ++unlock: ++ mutex_unlock(&intel_dp->psr.lock); ++} ++ + static void intel_psr_configure_full_frame_update(struct intel_dp *intel_dp) + { + struct intel_display *display = to_intel_display(intel_dp); +@@ -3650,8 +3699,11 @@ void intel_psr_invalidate(struct intel_display *display, + INTEL_FRONTBUFFER_ALL_MASK(intel_dp->psr.pipe); + intel_dp->psr.busy_frontbuffer_bits |= pipe_frontbuffer_bits; + +- if (pipe_frontbuffer_bits) ++ if (pipe_frontbuffer_bits) { ++ if (panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp)) ++ cancel_delayed_work(&intel_dp->psr.panel_replay_reenable_work); + _psr_invalidate_handle(intel_dp); ++ } + + mutex_unlock(&intel_dp->psr.lock); + } +@@ -3767,6 +3819,15 @@ void intel_psr_flush(struct intel_display *display, + if (intel_dp->psr.pause_counter) + goto unlock; + ++ if (pipe_frontbuffer_bits && ++ panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp)) { ++ intel_psr_exit(intel_dp); ++ mod_delayed_work(display->wq.unordered, ++ &intel_dp->psr.panel_replay_reenable_work, ++ msecs_to_jiffies(PANEL_REPLAY_REENABLE_DELAY_MS)); ++ goto unlock; ++ } ++ + if (origin == ORIGIN_FLIP || + (origin == ORIGIN_CURSOR_UPDATE && + !intel_dp->psr.psr2_sel_fetch_enabled)) { +@@ -3830,6 +3891,8 @@ void intel_psr_init(struct intel_dp *intel_dp) + + INIT_WORK(&intel_dp->psr.work, intel_psr_work); + INIT_DELAYED_WORK(&intel_dp->psr.dc3co_work, tgl_dc3co_disable_work); ++ INIT_DELAYED_WORK(&intel_dp->psr.panel_replay_reenable_work, ++ panel_replay_reenable_work); + mutex_init(&intel_dp->psr.lock); + } + +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +index 33245f44c0d5..dc6576016400 100644 +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -86,12 +86,13 @@ static void quirk_edp_limit_rate_hbr2(struct intel_display *display) + drm_info(display->drm, "Applying eDP Limit rate to HBR2 quirk\n"); + } + +-static void quirk_disable_edp_panel_replay(struct intel_dp *intel_dp) ++static void quirk_panel_replay_alpm_cursor_lag(struct intel_dp *intel_dp) + { + struct intel_display *display = to_intel_display(intel_dp); + +- intel_set_dpcd_quirk(intel_dp, QUIRK_DISABLE_EDP_PANEL_REPLAY); +- drm_info(display->drm, "Applying disable Panel Replay quirk\n"); ++ intel_set_dpcd_quirk(intel_dp, QUIRK_PANEL_REPLAY_ALPM_CURSOR_LAG); ++ drm_info(display->drm, ++ "Applying Panel Replay ALPM cursor lag workaround\n"); + } + + static void quirk_disable_psr2(struct intel_display *display) +@@ -276,7 +277,7 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .subsystem_vendor = 0x1028, + .subsystem_device = 0x0db9, + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), +- .hook = quirk_disable_edp_panel_replay, ++ .hook = quirk_panel_replay_alpm_cursor_lag, + }, + /* Dell XPS 16 DA16260 */ + { +@@ -284,7 +285,7 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .subsystem_vendor = 0x1028, + .subsystem_device = 0x0dba, + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), +- .hook = quirk_disable_edp_panel_replay, ++ .hook = quirk_panel_replay_alpm_cursor_lag, + }, + }; + +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.h b/drivers/gpu/drm/i915/display/intel_quirks.h +index 970a4fe52faf..50ef333b87a9 100644 +--- a/drivers/gpu/drm/i915/display/intel_quirks.h ++++ b/drivers/gpu/drm/i915/display/intel_quirks.h +@@ -23,6 +23,7 @@ enum intel_quirk_id { + QUIRK_EDP_LIMIT_RATE_HBR2, + QUIRK_DISABLE_EDP_PANEL_REPLAY, + QUIRK_DISABLE_PSR2, ++ QUIRK_PANEL_REPLAY_ALPM_CURSOR_LAG, + }; + + void intel_init_quirks(struct intel_display *display); diff --git a/pkgbuilds/linux-omarchy-bore/0402-psr2-early-transport-panels.patch b/pkgbuilds/linux-omarchy-bore/0402-psr2-early-transport-panels.patch new file mode 100644 index 0000000..3e88cea --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0402-psr2-early-transport-panels.patch @@ -0,0 +1,20 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c +index beaa1d626..5fdf4594c 100644 +--- a/drivers/gpu/drm/i915/display/intel_psr.c ++++ b/drivers/gpu/drm/i915/display/intel_psr.c +@@ -714,8 +714,14 @@ static void _psr_init_dpcd(struct intel_dp *intel_dp, struct intel_connector *co + * To support PSR version 02h and PSR version 03h without + * Y-coordinate requirement panels we would need to enable + * GTC first. ++ * ++ * Early Transport (version 04h) implies Y-coordinate ++ * support. Accept it without explicit Y-coordinate ++ * requirement bit. + */ +- connector->dp.psr_caps.su_support = y_req && ++ connector->dp.psr_caps.su_support = ++ (y_req || connector->dp.psr_caps.dpcd[0] == ++ DP_PSR2_WITH_Y_COORD_ET_SUPPORTED) && + intel_alpm_aux_wake_supported(intel_dp); + drm_dbg_kms(display->drm, "PSR2 %ssupported\n", + connector->dp.psr_caps.su_support ? "" : "not "); diff --git a/pkgbuilds/linux-omarchy-bore/0411-drm-xe-display-no-stolen-framebuffers.patch b/pkgbuilds/linux-omarchy-bore/0411-drm-xe-display-no-stolen-framebuffers.patch new file mode 100644 index 0000000..c1f3ad5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0411-drm-xe-display-no-stolen-framebuffers.patch @@ -0,0 +1,43 @@ +diff --git a/drivers/gpu/drm/xe/display/xe_display_bo.c b/drivers/gpu/drm/xe/display/xe_display_bo.c +--- a/drivers/gpu/drm/xe/display/xe_display_bo.c ++++ b/drivers/gpu/drm/xe/display/xe_display_bo.c +@@ -147,33 +147,12 @@ static struct drm_gem_object *xe_display_bo_fbdev_create(struct drm_device *drm, + struct xe_device *xe = to_xe_device(drm); + struct xe_bo *obj; + +- obj = ERR_PTR(-ENODEV); +- +- if (xe_display_bo_fbdev_prefer_stolen(xe, size)) { +- obj = xe_bo_create_pin_map_novm(xe, xe_device_get_root_tile(xe), +- size, +- ttm_bo_type_kernel, +- XE_BO_FLAG_FORCE_WC | +- XE_BO_FLAG_STOLEN | +- XE_BO_FLAG_GGTT, +- false); +- if (!IS_ERR(obj)) +- drm_info(&xe->drm, "Allocated fbdev into stolen\n"); +- else +- drm_info(&xe->drm, "Allocated fbdev into stolen failed: %li\n", PTR_ERR(obj)); +- } else { +- drm_info(&xe->drm, "Allocating fbdev: Stolen memory not preferred.\n"); +- } +- +- if (IS_ERR(obj)) { +- obj = xe_bo_create_pin_map_novm(xe, xe_device_get_root_tile(xe), size, +- ttm_bo_type_kernel, +- XE_BO_FLAG_FORCE_WC | +- XE_BO_FLAG_VRAM_IF_DGFX(xe_device_get_root_tile(xe)) | +- XE_BO_FLAG_GGTT, +- false); +- } +- ++ obj = xe_bo_create_pin_map_novm(xe, xe_device_get_root_tile(xe), size, ++ ttm_bo_type_kernel, ++ XE_BO_FLAG_FORCE_WC | ++ XE_BO_FLAG_VRAM_IF_DGFX(xe_device_get_root_tile(xe)) | ++ XE_BO_FLAG_GGTT, ++ false); + if (IS_ERR(obj)) { + drm_err(&xe->drm, "failed to allocate framebuffer (%pe)\n", obj); + return ERR_PTR(-ENOMEM); diff --git a/pkgbuilds/linux-omarchy-bore/0420-safe-window.patch b/pkgbuilds/linux-omarchy-bore/0420-safe-window.patch new file mode 100644 index 0000000..21017c7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0420-safe-window.patch @@ -0,0 +1,23 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c +--- a/drivers/gpu/drm/i915/display/intel_display.c ++++ b/drivers/gpu/drm/i915/display/intel_display.c +@@ -2452,6 +2452,19 @@ static int intel_crtc_set_context_latency(struct intel_crtc_state *crtc_state) + set_context_latency = max(set_context_latency, + intel_psr_min_set_context_latency(crtc_state)); + ++ /* ++ * From PTL onwards, the set context latency can be in the vactive ++ * region, letting the safe window start some lines before the vblank ++ * start. With modes that have a smaller vblank region, the computed ++ * guardband is clamped to the vblank length, making the undelayed and ++ * delayed vblank coincide. If the SCL is also 0, the 'safe window' ++ * becomes effectively 0, and the DSB configured to wait for it gets ++ * stalled, since the hardware never signals the safe window. Keep the ++ * set context latency at a minimum of 1 to avoid this. ++ */ ++ if (DISPLAY_VER(display) >= 30) ++ set_context_latency = max(1, set_context_latency); ++ + return set_context_latency; + } + diff --git a/pkgbuilds/linux-omarchy-bore/0430-fbc.patch b/pkgbuilds/linux-omarchy-bore/0430-fbc.patch new file mode 100644 index 0000000..4548866 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0430-fbc.patch @@ -0,0 +1,112 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_fbc.c b/drivers/gpu/drm/i915/display/intel_fbc.c +--- a/drivers/gpu/drm/i915/display/intel_fbc.c ++++ b/drivers/gpu/drm/i915/display/intel_fbc.c +@@ -59,6 +59,7 @@ + #include "intel_fbc_regs.h" + #include "intel_frontbuffer.h" + #include "intel_parent.h" ++#include "skl_universal_plane.h" + + #define for_each_fbc_id(__display, __fbc_id) \ + for ((__fbc_id) = INTEL_FBC_A; (__fbc_id) < I915_MAX_FBCS; (__fbc_id)++) \ +@@ -1305,6 +1306,9 @@ static bool intel_fbc_surface_size_ok(const struct intel_plane_state *plane_stat + struct intel_display *display = to_intel_display(plane_state); + unsigned int effective_w, effective_h, max_w, max_h; + ++ if (DISPLAY_VER(display) >= 20) ++ return true; ++ + intel_fbc_max_surface_size(display, &max_w, &max_h); + + effective_w = plane_state->view.color_plane[0].x + +@@ -1315,10 +1319,18 @@ static bool intel_fbc_surface_size_ok(const struct intel_plane_state *plane_stat + return effective_w <= max_w && effective_h <= max_h; + } + +-static void intel_fbc_max_plane_size(struct intel_display *display, ++static void intel_fbc_max_plane_size(const struct intel_plane_state *plane_state, + unsigned int *w, unsigned int *h) + { +- if (DISPLAY_VER(display) >= 10) { ++ struct intel_display *display = to_intel_display(plane_state); ++ struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); ++ const struct drm_framebuffer *fb = plane_state->hw.fb; ++ unsigned int rotation = plane_state->hw.rotation; ++ ++ if (DISPLAY_VER(display) >= 20) { ++ *w = intel_plane_max_width(plane, fb, 0, rotation); ++ *h = 4096; ++ } else if (DISPLAY_VER(display) >= 10) { + *w = 5120; + *h = 4096; + } else if (DISPLAY_VER(display) >= 8 || display->platform.haswell) { +@@ -1335,10 +1347,9 @@ static void intel_fbc_max_plane_size(struct intel_display *display, + + static bool intel_fbc_plane_size_valid(const struct intel_plane_state *plane_state) + { +- struct intel_display *display = to_intel_display(plane_state); + unsigned int w, h, max_w, max_h; + +- intel_fbc_max_plane_size(display, &max_w, &max_h); ++ intel_fbc_max_plane_size(plane_state, &max_w, &max_h); + + w = drm_rect_width(&plane_state->uapi.src) >> 16; + h = drm_rect_height(&plane_state->uapi.src) >> 16; +diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c +--- a/drivers/gpu/drm/i915/display/skl_universal_plane.c ++++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c +@@ -1937,10 +1937,10 @@ static int intel_plane_min_height(struct intel_plane *plane, + return 1; + } + +-static int intel_plane_max_width(struct intel_plane *plane, +- const struct drm_framebuffer *fb, +- int color_plane, +- unsigned int rotation) ++int intel_plane_max_width(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation) + { + if (plane->max_width) + return plane->max_width(fb, color_plane, rotation); +@@ -1948,10 +1948,10 @@ static int intel_plane_max_width(struct intel_plane *plane, + return INT_MAX; + } + +-static int intel_plane_max_height(struct intel_plane *plane, +- const struct drm_framebuffer *fb, +- int color_plane, +- unsigned int rotation) ++int intel_plane_max_height(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation) + { + if (plane->max_height) + return plane->max_height(fb, color_plane, rotation); +diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.h b/drivers/gpu/drm/i915/display/skl_universal_plane.h +--- a/drivers/gpu/drm/i915/display/skl_universal_plane.h ++++ b/drivers/gpu/drm/i915/display/skl_universal_plane.h +@@ -8,6 +8,7 @@ + + #include + ++struct drm_framebuffer; + struct intel_crtc; + struct intel_display; + struct intel_initial_plane_config; +@@ -42,5 +43,13 @@ bool icl_is_hdr_plane(struct intel_display *display, enum plane_id plane_id); + + u32 skl_plane_aux_dist(const struct intel_plane_state *plane_state, + int color_plane); ++int intel_plane_max_width(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation); ++int intel_plane_max_height(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation); + + #endif diff --git a/pkgbuilds/linux-omarchy-bore/0440-xe3-peak-bandwidth.patch b/pkgbuilds/linux-omarchy-bore/0440-xe3-peak-bandwidth.patch new file mode 100644 index 0000000..a13b165 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0440-xe3-peak-bandwidth.patch @@ -0,0 +1,67 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c +--- a/drivers/gpu/drm/i915/display/intel_bw.c ++++ b/drivers/gpu/drm/i915/display/intel_bw.c +@@ -52,6 +52,8 @@ + + #define DEPROGBWPCLIMIT 60 + ++#define PEAK_BW_THRESHOLD 20000 ++ + struct intel_psf_gv_point { + u8 clk; /* clock in multiples of 16.6666 MHz */ + }; +@@ -587,6 +589,33 @@ + return num_channels * (channel_width / 8) * dclk; + } + ++static void xe3_add_peakbw_threshold(struct intel_display *display) ++{ ++ u8 qgv_points = display->bw.max[0].num_qgv_points; ++ ++ if (!HAS_PEAK_BW_THRESHOLD(display)) ++ return; ++ ++ if (qgv_points >= I915_NUM_QGV_POINTS) { ++ drm_dbg_kms(display->drm, "QGV points maxed out; skipping peak bandwidth threshold.\n"); ++ return; ++ } ++ ++ if (qgv_points <= 1) ++ return; ++ ++ for (int i = 0; i < ARRAY_SIZE(display->bw.max); i++) { ++ struct intel_bw_info *bi = &display->bw.max[i]; ++ ++ bi->num_qgv_points++; ++ bi->peakbw[qgv_points] = PEAK_BW_THRESHOLD; ++ bi->deratedbw[qgv_points] = PEAK_BW_THRESHOLD; ++ } ++ ++ drm_dbg_kms(display->drm, "An extra QGV point %d added for Peak bw threshod of %d\n", ++ qgv_points, PEAK_BW_THRESHOLD); ++} ++ + static int tgl_get_bw_info(struct intel_display *display, + const struct dram_info *dram_info, + const struct intel_soc_bw_params *soc_bw_params, +@@ -684,6 +713,9 @@ + } + } + ++ /* For xe3 cases add an extra qgv point for Peak bw threshold */ ++ xe3_add_peakbw_threshold(display); ++ + /* + * In case if SAGV is disabled in BIOS, we always get 1 + * SAGV point, but we can't send PCode commands to restrict it +diff --git a/drivers/gpu/drm/i915/display/intel_display_device.h b/drivers/gpu/drm/i915/display/intel_display_device.h +--- a/drivers/gpu/drm/i915/display/intel_display_device.h ++++ b/drivers/gpu/drm/i915/display/intel_display_device.h +@@ -191,6 +191,7 @@ + #define HAS_MBUS_JOINING(__display) ((__display)->platform.alderlake_p || DISPLAY_VER(__display) >= 14) + #define HAS_MSO(__display) (DISPLAY_VER(__display) >= 12) + #define HAS_OVERLAY(__display) (DISPLAY_INFO(__display)->has_overlay) ++#define HAS_PEAK_BW_THRESHOLD(__display) (DISPLAY_VER(__display) >= 30) + #define HAS_PIPEDMC(__display) (DISPLAY_VER(__display) >= 12) + #define HAS_PIXEL_NORMALIZER(__display) (DISPLAY_VER(__display) >= 35) + #define HAS_PSR(__display) (DISPLAY_INFO(__display)->has_psr) diff --git a/pkgbuilds/linux-omarchy-bore/0450-amd-hdmi-vrr-allm.patch b/pkgbuilds/linux-omarchy-bore/0450-amd-hdmi-vrr-allm.patch new file mode 100644 index 0000000..ebe9a36 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0450-amd-hdmi-vrr-allm.patch @@ -0,0 +1,456 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -104,6 +104,7 @@ + #include "ivsrcid/dcn/irqsrcs_dcn_1_0.h" + + #include "modules/inc/mod_freesync.h" ++#include "modules/inc/mod_info_packet.h" + #include "modules/inc/mod_power.h" + #include "modules/power/power_helpers.h" + +@@ -10068,6 +10069,9 @@ static void update_freesync_state_on_stream( + &vrr_infopacket, + pack_sdp_v1_3); + ++ if (new_stream->sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) ++ mod_build_infopacket_vtem(new_stream, &vrr_params, 0, &vrr_infopacket); ++ + new_crtc_state->freesync_vrr_info_changed |= + (memcmp(&new_crtc_state->vrr_infopacket, + &vrr_infopacket, +@@ -10079,6 +10083,36 @@ static void update_freesync_state_on_stream( + new_stream->vrr_infopacket = vrr_infopacket; + new_stream->allow_freesync = mod_freesync_get_freesync_enabled(&vrr_params); + ++ /* ++ * HDMI ALLM: when Gaming-VRR is active (VRR_EN=1) and the sink ++ * advertises ALLM in the SCDS, the Source shall transmit the HF-VSIF ++ * with ALLM_Mode=1 (HDMI 2.1 Section 7.6.6). ++ */ ++ if (new_stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || ++ new_stream->signal == SIGNAL_TYPE_HDMI_FRL) { ++ struct dc_info_packet hf_vsif = {0}; ++ bool sink_allm = aconn && aconn->base.display_info.hdmi.allm; ++ bool allm = sink_allm && ++ (vrr_params.state == VRR_STATE_ACTIVE_VARIABLE || ++ vrr_params.state == VRR_STATE_ACTIVE_FIXED); ++ bool allm_changed; ++ ++ if (allm) ++ mod_build_hf_vsif_infopacket(new_stream, &hf_vsif, allm, allm); ++ ++ allm_changed = memcmp(&new_stream->hfvsif_infopacket, &hf_vsif, ++ sizeof(hf_vsif)) != 0; ++ new_crtc_state->freesync_vrr_info_changed |= allm_changed; ++ new_stream->hfvsif_infopacket = hf_vsif; ++ ++ if (allm_changed) ++ drm_dbg_driver(adev_to_drm(adev), ++ "ALLM: flip on crtc=%u: sink_allm=%d vrr_state=%d -> ALLM_Mode=%d\n", ++ new_crtc_state->base.crtc->base.id, ++ sink_allm, ++ vrr_params.state, allm); ++ } ++ + if (new_crtc_state->freesync_vrr_info_changed) + drm_dbg_kms(adev_to_drm(adev), "VRR packet update: crtc=%u enabled=%d state=%d", + new_crtc_state->base.crtc->base.id, +@@ -10645,9 +10679,12 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, + } + + if (acrtc_state->stream) { +- if (acrtc_state->freesync_vrr_info_changed) ++ if (acrtc_state->freesync_vrr_info_changed) { + bundle->stream_update.vrr_infopacket = + &acrtc_state->stream->vrr_infopacket; ++ bundle->stream_update.hfvsif_infopacket = ++ &acrtc_state->stream->hfvsif_infopacket; ++ } + } + } + +@@ -12049,6 +12086,14 @@ static void get_freesync_config_for_crtc( + } + out: + new_crtc_state->freesync_config = config; ++ ++ drm_dbg_driver(new_con_state->base.connector->dev, ++ "VRR: cfg vrr_enabled=%d vrr_supported=%d fs_capable=%d vrefresh=%d min=%d max=%d state=%d\n", ++ new_crtc_state->base.vrr_enabled, ++ new_crtc_state->vrr_supported, ++ new_con_state->freesync_capable, vrefresh, ++ aconnector->min_vfreq, aconnector->max_vfreq, ++ config.state); + } + + static void reset_freesync_config_for_crtc( +@@ -14011,6 +14056,15 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + if (!adev->dm.freesync_module || !dc_supports_vrr(sink->ctx->dce_version)) + goto update; + ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: enter signal=%d hdmi_vrr=%d mrange[%d-%d] hdmi.vrr_cap[sup=%d min=%d max=%d]\n", ++ sink->sink_signal, connector->display_info.hdmi.vrr_cap.supported, ++ connector->display_info.monitor_range.min_vfreq, ++ connector->display_info.monitor_range.max_vfreq, ++ connector->display_info.hdmi.vrr_cap.supported, ++ connector->display_info.hdmi.vrr_cap.vrr_min, ++ connector->display_info.hdmi.vrr_cap.vrr_max); ++ + edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw() + + /* Some eDP panels only have the refresh rate range info in DisplayID */ +@@ -14036,7 +14090,9 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_EDP; + } + +- } else if (drm_edid && sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) { ++ } else if (drm_edid && ++ (sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A || ++ sink->sink_signal == SIGNAL_TYPE_HDMI_FRL)) { + i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); + if (i >= 0) { + amdgpu_dm_connector->vsdb_info = vsdb_info; +@@ -14052,6 +14108,59 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz; + } + } ++ ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: amd_vsdb i=%d fs_sup=%d min=%d max=%d fs_capable=%d\n", ++ i, vsdb_info.freesync_supported, ++ vsdb_info.min_refresh_rate_hz, ++ vsdb_info.max_refresh_rate_hz, freesync_capable); ++ ++ /* ++ * If AMD VSDB didn't provide a valid FreeSync range, fall back to ++ * the HDMI 2.1 VRR capability parsed from the HF-VSDB. ++ */ ++ if (!freesync_capable && connector->display_info.hdmi.vrr_cap.supported) { ++ struct drm_hdmi_vrr_cap *vrr_cap = ++ &connector->display_info.hdmi.vrr_cap; ++ ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: HF-VSDB fallback: hdmi_vrr=1 vrr_cap[sup=%d min=%d max=%d] mrange_max=%d\n", ++ vrr_cap->supported, vrr_cap->vrr_min, vrr_cap->vrr_max, ++ connector->display_info.monitor_range.max_vfreq); ++ ++ if (vrr_cap->supported && vrr_cap->vrr_min > 0) { ++ amdgpu_dm_connector->min_vfreq = vrr_cap->vrr_min; ++ amdgpu_dm_connector->max_vfreq = vrr_cap->vrr_max ? ++ vrr_cap->vrr_max : ++ connector->display_info.monitor_range.max_vfreq; ++ ++ /* ++ * VRRMAX = 0 in the HF-VSDB means "up to the Base ++ * Refresh Rate". If the EDID also did not provide a ++ * monitor range max, fall back to the Base Refresh ++ * Rate (the highest refresh rate of the preferred ++ * timing) so a valid VRR range is still reported to ++ * userspace. ++ */ ++ if (!amdgpu_dm_connector->max_vfreq) { ++ struct drm_display_mode *brr_mode = ++ get_highest_refresh_rate_mode(amdgpu_dm_connector, true); ++ ++ if (brr_mode) ++ amdgpu_dm_connector->max_vfreq = ++ drm_mode_vrefresh(brr_mode); ++ } ++ ++ if (amdgpu_dm_connector->max_vfreq - ++ amdgpu_dm_connector->min_vfreq > 10) ++ freesync_capable = true; ++ ++ connector->display_info.monitor_range.min_vfreq = ++ amdgpu_dm_connector->min_vfreq; ++ connector->display_info.monitor_range.max_vfreq = ++ amdgpu_dm_connector->max_vfreq; ++ } ++ } + } + + if (amdgpu_dm_connector->dc_link) +@@ -14093,6 +14202,11 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + if (dm_con_state) + dm_con_state->freesync_capable = freesync_capable; + ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: caps result: freesync_capable=%d min_vfreq=%d max_vfreq=%d\n", ++ freesync_capable, amdgpu_dm_connector->min_vfreq, ++ amdgpu_dm_connector->max_vfreq); ++ + if (connector->state && amdgpu_dm_connector->dc_link && !freesync_capable && + amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported) { + amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported = false; +diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h +--- a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h ++++ b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h +@@ -67,6 +67,10 @@ struct AS_Df_params { + struct frame_duration_op decrease; + }; + ++void mod_build_infopacket_vtem(const struct dc_stream_state *stream, ++ const struct mod_vrr_params *vrr, int fva_factor, ++ struct dc_info_packet *infopacket); ++ + void mod_build_adaptive_sync_infopacket(const struct dc_stream_state *stream, + enum adaptive_sync_type asType, const struct AS_Df_params *param, + struct dc_info_packet *info_packet); +diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +--- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c ++++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +@@ -291,6 +291,21 @@ void set_vsc_packet_colorimetry_data( + info_packet->sb[18] = 0; + } + ++static void set_field_with_mask(unsigned char *dest, unsigned int mask, unsigned int value) ++{ ++ unsigned int shift = 0; ++ ++ if (!mask || !dest) ++ return; ++ ++ while (!((mask >> shift) & 1)) ++ shift++; ++ ++ *dest = *dest & ~mask; ++ value = value & (mask >> shift); ++ *dest = *dest | (value << shift); ++} ++ + void mod_build_vsc_infopacket(const struct dc_stream_state *stream, + struct dc_info_packet *info_packet, + enum dc_color_space cs, +@@ -644,6 +659,100 @@ void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, + info_packet->valid = true; + } + ++static void build_vtem_infopacket_data(const struct dc_stream_state *stream, ++ const struct mod_vrr_params *vrr, int fva_factor, ++ struct dc_info_packet *infopacket) ++{ ++ unsigned int field_rate_in_hz; ++ ++ /* FVA Factor setting */ ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__FVA_FACTOR_M1, ++ (fva_factor > 0) ? (fva_factor - 1) : 0); ++ /* VRR Parameters */ ++ if (vrr->state == VRR_STATE_ACTIVE_VARIABLE || ++ vrr->state == VRR_STATE_ACTIVE_FIXED) { ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__VRR_EN, 1); ++ } else { ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__VRR_EN, 0); ++ } ++ ++ if (vrr->state == VRR_STATE_ACTIVE_FIXED) ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__M_CONST, vrr->m_const); ++ ++ if (!stream->timing.vic) { ++ set_field_with_mask(&infopacket->sb[VTEM_MD1], MASK_VTEM_MD1__BASE_VFRONT, ++ stream->timing.v_front_porch); ++ ++ ++ /* TODO: In dal2, we check mode flags for a reduced blanking timing. ++ * Need a way to relay that information to this function. ++ * if("ReducedBlanking") ++ * { ++ * set_field_with_mask(&infopacket->sb[VRR_VTEM_MD2], MASK__VRR_VTEM_MD2__RB, 1; ++ * } ++ */ ++ ++ field_rate_in_hz = stream->timing.pix_clk_100hz * 100; ++ field_rate_in_hz /= stream->timing.h_total; ++ field_rate_in_hz = (field_rate_in_hz + stream->timing.v_total / 2) ++ / stream->timing.v_total; ++ ++ set_field_with_mask(&infopacket->sb[VTEM_MD2], MASK_VTEM_MD2__BASE_REFRESH_RATE_98, ++ field_rate_in_hz >> 8); ++ set_field_with_mask(&infopacket->sb[VTEM_MD3], MASK_VTEM_MD3__BASE_REFRESH_RATE_07, ++ field_rate_in_hz); ++ ++ } ++ ++ /* ++ * When no VTEM feature is enabled (neither VRR nor FVA), signal a ++ * zero-length data set (MLDS) by clearing Data_Set_Length. HDMI 2.1 ++ * 10.10.2.4 requires the Source to either stop transmitting the VTEM ++ * or set Data_Set_Length = 0 when no feature is enabled; keeping the ++ * VTEM with Data_Set_Length = 0 preserves the every-MTW cadence while ++ * staying compliant (e.g. HDMI GCTS HF1-58 step 6.2). ++ */ ++ if (vrr->state != VRR_STATE_ACTIVE_VARIABLE && ++ vrr->state != VRR_STATE_ACTIVE_FIXED && fva_factor == 0) ++ set_field_with_mask(&infopacket->sb[VTEM_PB6], ++ MASK_VTEM_PB6__DATA_SET_LENGTH_LSB, 0); ++ ++ infopacket->valid = true; ++} ++ ++static void build_infopacket_header_vtem(enum signal_type signal, ++ struct dc_info_packet *infopacket) ++{ ++ /* HEADER */ ++ ++ /* HB0, HB1, HB2 indicates PacketType VTEMPacket */ ++ infopacket->hb0 = 0x7F; ++ infopacket->hb1 = 0xC0; ++ infopacket->hb2 = 0x00; /* sequence_index */ ++ ++ set_field_with_mask(&infopacket->sb[VTEM_PB0], MASK_VTEM_PB0__VFR, 1); ++ set_field_with_mask(&infopacket->sb[VTEM_PB2], MASK_VTEM_PB2__ORGANIZATION_ID, 1); ++ set_field_with_mask(&infopacket->sb[VTEM_PB3], MASK_VTEM_PB3__DATA_SET_TAG_MSB, 0); ++ set_field_with_mask(&infopacket->sb[VTEM_PB4], MASK_VTEM_PB4__DATA_SET_TAG_LSB, 1); ++ set_field_with_mask(&infopacket->sb[VTEM_PB5], MASK_VTEM_PB5__DATA_SET_LENGTH_MSB, 0); ++ set_field_with_mask(&infopacket->sb[VTEM_PB6], MASK_VTEM_PB6__DATA_SET_LENGTH_LSB, 4); ++} ++ ++void mod_build_infopacket_vtem(const struct dc_stream_state *stream, ++ const struct mod_vrr_params *vrr, int fva_factor, ++ struct dc_info_packet *infopacket) ++{ ++ /* VTEM info packet for HdmiVrr */ ++ ++ memset(infopacket, 0, sizeof(struct dc_info_packet)); ++ ++ /* VTEM Packet is structured differently */ ++ build_infopacket_header_vtem(stream->signal, infopacket); ++ build_vtem_infopacket_data(stream, vrr, fva_factor, infopacket); ++ ++ infopacket->valid = true; ++} ++ + void mod_build_adaptive_sync_infopacket(const struct dc_stream_state *stream, + enum adaptive_sync_type asType, + const struct AS_Df_params *param, +diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c +--- a/drivers/gpu/drm/drm_edid.c ++++ b/drivers/gpu/drm/drm_edid.c +@@ -6182,6 +6182,33 @@ static void drm_parse_ycbcr420_deep_color_info(struct drm_connector *connector, + hdmi->y420_dc_modes = dc_mask; + } + ++static void drm_parse_hdmi_gaming_info(struct drm_hdmi_info *hdmi, const u8 *db) ++{ ++ struct drm_hdmi_vrr_cap *vrr = &hdmi->vrr_cap; ++ ++ if (cea_db_payload_len(db) < 8) ++ return; ++ ++ hdmi->fapa_start_location = db[8] & DRM_EDID_FAPA_START_LOCATION; ++ hdmi->allm = db[8] & DRM_EDID_ALLM; ++ vrr->fva = db[8] & DRM_EDID_FVA; ++ vrr->cnmvrr = db[8] & DRM_EDID_CNMVRR; ++ vrr->cinema_vrr = db[8] & DRM_EDID_CINEMA_VRR; ++ vrr->mdelta = db[8] & DRM_EDID_MDELTA; ++ ++ if (cea_db_payload_len(db) < 9) ++ return; ++ ++ vrr->vrr_min = db[9] & DRM_EDID_VRR_MIN_MASK; ++ vrr->supported = (vrr->vrr_min > 0 && vrr->vrr_min <= 48); ++ ++ if (cea_db_payload_len(db) < 10) ++ return; ++ ++ vrr->vrr_max = (db[9] & DRM_EDID_VRR_MAX_UPPER_MASK) << 2 | db[10]; ++ vrr->supported &= (vrr->vrr_max == 0 || vrr->vrr_max >= 100); ++} ++ + static void drm_parse_dsc_info(struct drm_hdmi_dsc_cap *hdmi_dsc, + const u8 *hf_scds) + { +@@ -6308,6 +6335,8 @@ static void drm_parse_hdmi_forum_scds(struct drm_connector *connector, + + drm_parse_ycbcr420_deep_color_info(connector, hf_scds); + ++ drm_parse_hdmi_gaming_info(&connector->display_info.hdmi, hf_scds); ++ + if (cea_db_payload_len(hf_scds) >= 11 && hf_scds[11]) { + drm_parse_dsc_info(hdmi_dsc, hf_scds); + dsc_support = true; +@@ -6317,6 +6346,19 @@ static void drm_parse_hdmi_forum_scds(struct drm_connector *connector, + "[CONNECTOR:%d:%s] HF-VSDB: max TMDS clock: %d KHz, HDMI 2.1 support: %s, DSC 1.2 support: %s\n", + connector->base.id, connector->name, + max_tmds_clock, str_yes_no(max_frl_rate), str_yes_no(dsc_support)); ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] FAPA in blanking: %s, ALLM support: %s, Fast Vactive support: %s\n", ++ connector->base.id, connector->name, str_yes_no(hdmi->fapa_start_location), ++ str_yes_no(hdmi->allm), str_yes_no(hdmi->vrr_cap.fva)); ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] Negative M VRR support: %s, CinemaVRR support: %s, Mdelta: %d\n", ++ connector->base.id, connector->name, str_yes_no(hdmi->vrr_cap.cnmvrr), ++ str_yes_no(hdmi->vrr_cap.cinema_vrr), hdmi->vrr_cap.mdelta); ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] VRRmin: %u, VRRmax: %u, VRR supported: %s\n", ++ connector->base.id, connector->name, hdmi->vrr_cap.vrr_min, ++ hdmi->vrr_cap.vrr_max, str_yes_no(hdmi->vrr_cap.supported)); ++ + } + + static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector, +diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h +--- a/include/drm/drm_connector.h ++++ b/include/drm/drm_connector.h +@@ -254,6 +254,44 @@ struct drm_scdc { + struct drm_scrambling scrambling; + }; + ++/** ++ * struct drm_hdmi_vrr_cap - Information about VRR capabilities of a HDMI sink ++ * ++ * Describes the VRR support provided by HDMI 2.1 sink. The information is ++ * fetched fom additional HFVSDB blocks defined for HDMI 2.1. ++ */ ++struct drm_hdmi_vrr_cap { ++ /** @fva: flag for Fast VActive (Quick Frame Transport) support */ ++ bool fva; ++ ++ /** @mcnmvrr: flag for Negative M VRR support */ ++ bool cnmvrr; ++ ++ /** @mcinema_vrr: flag for Cinema VRR support */ ++ bool cinema_vrr; ++ ++ /** @mdelta: flag for limited frame-to-frame compensation support */ ++ bool mdelta; ++ ++ /** ++ * @vrr_min : minimum supported variable refresh rate in Hz. ++ * Valid values only inide 1 - 48 range ++ */ ++ u16 vrr_min; ++ ++ /** ++ * @vrr_max : maximum supported variable refresh rate in Hz (optional). ++ * Valid values are either 0 (max based on video mode) or >= 100 ++ */ ++ u16 vrr_max; ++ ++ /** ++ * @supported: flag for vrr support based on checking for VRRmin and ++ * VRRmax values having correct values. ++ */ ++ bool supported; ++}; ++ + /** + * struct drm_hdmi_dsc_cap - DSC capabilities of HDMI sink + * +@@ -330,6 +368,15 @@ struct drm_hdmi_info { + /** @max_lanes: supported by sink */ + u8 max_lanes; + ++ /** @fapa_start_location: flag for the FAPA in blanking support */ ++ bool fapa_start_location; ++ ++ /** @allm: flag for Auto Low Latency Mode support by sink */ ++ bool allm; ++ ++ /** @vrr_cap: VRR capabilities of the sink */ ++ struct drm_hdmi_vrr_cap vrr_cap; ++ + /** @dsc_cap: DSC capabilities of the sink */ + struct drm_hdmi_dsc_cap dsc_cap; + }; diff --git a/pkgbuilds/linux-omarchy-bore/0451-amd-vtem-tmds-links.patch b/pkgbuilds/linux-omarchy-bore/0451-amd-vtem-tmds-links.patch new file mode 100644 index 0000000..f61c400 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0451-amd-vtem-tmds-links.patch @@ -0,0 +1,15 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -10069,7 +10069,10 @@ static void update_freesync_state_on_stream( + &vrr_infopacket, + pack_sdp_v1_3); + +- if (new_stream->sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) ++ /* Per HDMI 2.1, VTEM is valid on TMDS as well as FRL */ ++ if (new_stream->sink->sink_signal == SIGNAL_TYPE_HDMI_FRL || ++ (new_stream->sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A && ++ aconn && aconn->base.display_info.hdmi.vrr_cap.supported)) + mod_build_infopacket_vtem(new_stream, &vrr_params, 0, &vrr_infopacket); + + new_crtc_state->freesync_vrr_info_changed |= diff --git a/pkgbuilds/linux-omarchy-bore/0452-amd-hdmi-frl-default.patch b/pkgbuilds/linux-omarchy-bore/0452-amd-hdmi-frl-default.patch new file mode 100644 index 0000000..e392ccc --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0452-amd-hdmi-frl-default.patch @@ -0,0 +1,28 @@ +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +index 1aed121f4ddb..081de37c2083 100644 +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +@@ -216,8 +216,9 @@ int amdgpu_smu_pptable_id = -1; + * DISABLE_FRACTIONAL_PWM (bit 2) disabled by default + * PSR (bit 3) disabled by default + * EDP NO POWER SEQUENCING (bit 4) disabled by default ++ * FRL (bit 10) enabled by default + */ +-uint amdgpu_dc_feature_mask = 2; ++uint amdgpu_dc_feature_mask = DC_MULTI_MON_PP_MCLK_SWITCH_MASK | DC_FRL_MASK; + uint amdgpu_dc_debug_mask; + uint amdgpu_dc_visual_confirm; + int amdgpu_async_gfx_ring = 1; +diff --git a/drivers/gpu/drm/amd/include/amd_shared.h b/drivers/gpu/drm/amd/include/amd_shared.h +index 3fd38323a88b..bdd80c5e378e 100644 +--- a/drivers/gpu/drm/amd/include/amd_shared.h ++++ b/drivers/gpu/drm/amd/include/amd_shared.h +@@ -287,7 +287,7 @@ enum DC_FEATURE_MASK { + */ + DC_REPLAY_MASK = (1 << 9), + /** +- * @DC_FRL_MASK: (0x400) disabled by default ++ * @DC_FRL_MASK: (0x400) enabled by default + */ + DC_FRL_MASK = (1 << 10), + }; diff --git a/pkgbuilds/linux-omarchy-bore/0460-vesa-displayid-dsc-bpp.patch b/pkgbuilds/linux-omarchy-bore/0460-vesa-displayid-dsc-bpp.patch new file mode 100644 index 0000000..1ba6ec7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0460-vesa-displayid-dsc-bpp.patch @@ -0,0 +1,335 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -7096,6 +7096,11 @@ static void fill_stream_properties_from_drm_display_mode( + + stream->output_color_space = get_output_color_space(timing_out, connector_state); + stream->content_type = get_output_content_type(connector_state); ++ ++ /* DisplayID Type VII pass-through timings. */ ++ if (mode_in->dsc_passthrough_timings_support && info->dp_dsc_bpp_x16 != 0) { ++ stream->timing.dsc_fixed_bits_per_pixel_x16 = info->dp_dsc_bpp_x16; ++ } + } + + static void fill_audio_info(struct audio_info *audio_info, +@@ -7616,6 +7621,7 @@ create_stream_for_sink(struct drm_connector *connector, + struct drm_display_mode mode; + struct drm_display_mode saved_mode; + struct drm_display_mode *freesync_mode = NULL; ++ struct drm_display_mode *dsc_passthru_mode = NULL; + bool native_mode_found = false; + bool recalculate_timing = false; + bool scale = dm_state->scaling != RMX_OFF; +@@ -7707,6 +7713,16 @@ create_stream_for_sink(struct drm_connector *connector, + } + } + ++ list_for_each_entry(dsc_passthru_mode, &connector->modes, head) { ++ if (dsc_passthru_mode->hdisplay == mode.hdisplay && ++ dsc_passthru_mode->vdisplay == mode.vdisplay && ++ drm_mode_vrefresh(dsc_passthru_mode) == mode_refresh) { ++ mode.dsc_passthrough_timings_support = ++ dsc_passthru_mode->dsc_passthrough_timings_support; ++ break; ++ } ++ } ++ + if (recalculate_timing) + drm_mode_set_crtcinfo(&saved_mode, 0); + +diff --git a/drivers/gpu/drm/drm_displayid_internal.h b/drivers/gpu/drm/drm_displayid_internal.h +--- a/drivers/gpu/drm/drm_displayid_internal.h ++++ b/drivers/gpu/drm/drm_displayid_internal.h +@@ -98,6 +98,7 @@ struct displayid_header { + u8 ext_count; + } __packed; + ++#define DISPLAYID_BLOCK_REV GENMASK(2, 0) + struct displayid_block { + u8 tag; + u8 rev; +@@ -126,6 +127,7 @@ struct displayid_detailed_timings_1 { + __le16 vsw; + } __packed; + ++#define DISPLAYID_BLOCK_PASSTHROUGH_TIMINGS_SUPPORT BIT(3) + struct displayid_detailed_timing_block { + struct displayid_block base; + struct displayid_detailed_timings_1 timings[]; +@@ -138,19 +140,28 @@ struct displayid_formula_timings_9 { + u8 vrefresh; + } __packed; + ++#define DISPLAYID_BLOCK_DESCRIPTOR_PAYLOAD_BYTES GENMASK(6, 4) + struct displayid_formula_timing_block { + struct displayid_block base; + struct displayid_formula_timings_9 timings[]; + } __packed; + ++#define DISPLAYID_VESA_DP_TYPE GENMASK(2, 0) + #define DISPLAYID_VESA_MSO_OVERLAP GENMASK(3, 0) + #define DISPLAYID_VESA_MSO_MODE GENMASK(6, 5) ++#define DISPLAYID_VESA_DSC_BPP_INT GENMASK(5, 0) ++#define DISPLAYID_VESA_DSC_BPP_FRACT GENMASK(3, 0) ++ ++#define DISPLAYID_VESA_DP_TYPE_EDP 0 ++#define DISPLAYID_VESA_DP_TYPE_DP 1 + + struct displayid_vesa_vendor_specific_block { + struct displayid_block base; + u8 oui[3]; + u8 data_structure_type; + u8 mso; ++ u8 dsc_bpp_int; ++ u8 dsc_bpp_fract; + } __packed; + + /* +diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c +--- a/drivers/gpu/drm/drm_edid.c ++++ b/drivers/gpu/drm/drm_edid.c +@@ -45,6 +45,7 @@ + #include + #include + #include ++#include + #include + + #include "drm_crtc_internal.h" +@@ -6683,12 +6684,13 @@ static bool drm_update_displayid_adaptive_sync_range(struct drm_connector *conne + return false; + } + +-static void drm_parse_vesa_mso_data(struct drm_connector *connector, +- const struct displayid_block *block) ++static void drm_parse_vesa_specific_block(struct drm_connector *connector, ++ const struct displayid_block *block) + { + struct displayid_vesa_vendor_specific_block *vesa = + (struct displayid_vesa_vendor_specific_block *)block; + struct drm_display_info *info = &connector->display_info; ++ int dp_type; + + if (block->num_bytes < 3) { + drm_dbg_kms(connector->dev, +@@ -6700,51 +6702,73 @@ static void drm_parse_vesa_mso_data(struct drm_connector *connector, + if (oui(vesa->oui[0], vesa->oui[1], vesa->oui[2]) != VESA_IEEE_OUI) + return; + +- if (sizeof(*vesa) != sizeof(*block) + block->num_bytes) { ++ if (block->num_bytes < 5) { + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] Unexpected VESA vendor block size\n", + connector->base.id, connector->name); + return; + } + +- switch (FIELD_GET(DISPLAYID_VESA_MSO_MODE, vesa->mso)) { +- default: +- drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Reserved MSO mode value\n", ++ dp_type = FIELD_GET(DISPLAYID_VESA_DP_TYPE, vesa->data_structure_type); ++ if (dp_type > 1) { ++ drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Reserved dp type value\n", + connector->base.id, connector->name); +- fallthrough; +- case 0: +- info->mso_stream_count = 0; +- break; +- case 1: +- info->mso_stream_count = 2; /* 2 or 4 links */ +- break; +- case 2: +- info->mso_stream_count = 4; /* 4 links */ +- break; + } + +- if (!info->mso_stream_count) { ++ /* MSO is only supported for eDP */ ++ if (dp_type == DISPLAYID_VESA_DP_TYPE_EDP) { ++ switch (FIELD_GET(DISPLAYID_VESA_MSO_MODE, vesa->mso)) { ++ default: ++ drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Reserved MSO mode value\n", ++ connector->base.id, connector->name); ++ fallthrough; ++ case 0: ++ info->mso_stream_count = 0; ++ break; ++ case 1: ++ info->mso_stream_count = 2; /* 2 or 4 links */ ++ break; ++ case 2: ++ info->mso_stream_count = 4; /* 4 links */ ++ break; ++ } ++ } ++ ++ if (info->mso_stream_count) { ++ info->mso_pixel_overlap = FIELD_GET(DISPLAYID_VESA_MSO_OVERLAP, vesa->mso); ++ if (info->mso_pixel_overlap > 8) { ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] Reserved MSO pixel overlap value %u\n", ++ connector->base.id, connector->name, ++ info->mso_pixel_overlap); ++ info->mso_pixel_overlap = 8; ++ } ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] MSO stream count %u, pixel overlap %u\n", ++ connector->base.id, connector->name, ++ info->mso_stream_count, info->mso_pixel_overlap); ++ } else { + info->mso_pixel_overlap = 0; ++ } ++ ++ if (block->num_bytes < 7) { ++ /* DSC bpp is optional */ + return; + } + +- info->mso_pixel_overlap = FIELD_GET(DISPLAYID_VESA_MSO_OVERLAP, vesa->mso); +- if (info->mso_pixel_overlap > 8) { +- drm_dbg_kms(connector->dev, +- "[CONNECTOR:%d:%s] Reserved MSO pixel overlap value %u\n", +- connector->base.id, connector->name, +- info->mso_pixel_overlap); +- info->mso_pixel_overlap = 8; +- } ++ info->dp_dsc_bpp_x16 = FIELD_GET(DISPLAYID_VESA_DSC_BPP_INT, vesa->dsc_bpp_int) << 4 | ++ FIELD_GET(DISPLAYID_VESA_DSC_BPP_FRACT, vesa->dsc_bpp_fract); + +- drm_dbg_kms(connector->dev, +- "[CONNECTOR:%d:%s] MSO stream count %u, pixel overlap %u\n", +- connector->base.id, connector->name, +- info->mso_stream_count, info->mso_pixel_overlap); ++ if (info->dp_dsc_bpp_x16 > 0) { ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] DSC bits per pixel " FXP_Q4_FMT "\n", ++ connector->base.id, connector->name, ++ FXP_Q4_ARGS(info->dp_dsc_bpp_x16)); ++ } + } + +-static void drm_update_mso(struct drm_connector *connector, +- const struct drm_edid *drm_edid) ++static void drm_update_vesa_specific_block(struct drm_connector *connector, ++ const struct drm_edid *drm_edid) + { + const struct displayid_block *block; + struct displayid_iter iter; +@@ -6752,7 +6776,7 @@ static void drm_update_mso(struct drm_connector *connector, + displayid_iter_edid_begin(drm_edid, &iter); + displayid_iter_for_each(block, &iter) { + if (block->tag == DATA_BLOCK_2_VENDOR_SPECIFIC) +- drm_parse_vesa_mso_data(connector, block); ++ drm_parse_vesa_specific_block(connector, block); + } + displayid_iter_end(&iter); + } +@@ -6789,6 +6813,7 @@ static void drm_reset_display_info(struct drm_connector *connector) + info->mso_stream_count = 0; + info->mso_pixel_overlap = 0; + info->max_dsc_bpp = 0; ++ info->dp_dsc_bpp_x16 = 0; + + kfree(info->vics); + info->vics = NULL; +@@ -6930,7 +6955,7 @@ static void update_display_info(struct drm_connector *connector, + if (edid->features & DRM_EDID_FEATURE_RGB_YCRCB422) + info->color_formats |= BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422); + +- drm_update_mso(connector, drm_edid); ++ drm_update_vesa_specific_block(connector, drm_edid); + + out: + if (drm_edid_has_internal_quirk(connector, EDID_QUIRK_NON_DESKTOP)) { +@@ -6960,8 +6985,8 @@ static void update_display_info(struct drm_connector *connector, + } + + static struct drm_display_mode *drm_mode_displayid_detailed(struct drm_device *dev, +- const struct displayid_detailed_timings_1 *timings, +- bool type_7) ++ const struct displayid_block *block, ++ const struct displayid_detailed_timings_1 *timings) + { + struct drm_display_mode *mode; + unsigned int pixel_clock = (timings->pixel_clock[0] | +@@ -6977,11 +7002,16 @@ static struct drm_display_mode *drm_mode_displayid_detailed(struct drm_device *d + unsigned int vsync_width = le16_to_cpu(timings->vsw) + 1; + bool hsync_positive = le16_to_cpu(timings->hsync) & (1 << 15); + bool vsync_positive = le16_to_cpu(timings->vsync) & (1 << 15); ++ bool type_7 = block->tag == DATA_BLOCK_2_TYPE_7_DETAILED_TIMING; + + mode = drm_mode_create(dev); + if (!mode) + return NULL; + ++ if (type_7 && FIELD_GET(DISPLAYID_BLOCK_REV, block->rev) >= 1) ++ mode->dsc_passthrough_timings_support = ++ block->rev & DISPLAYID_BLOCK_PASSTHROUGH_TIMINGS_SUPPORT; ++ + /* resolution is kHz for type VII, and 10 kHz for type I */ + mode->clock = type_7 ? pixel_clock : pixel_clock * 10; + mode->hdisplay = hactive; +@@ -7014,7 +7044,6 @@ static int add_displayid_detailed_1_modes(struct drm_connector *connector, + int num_timings; + struct drm_display_mode *newmode; + int num_modes = 0; +- bool type_7 = block->tag == DATA_BLOCK_2_TYPE_7_DETAILED_TIMING; + /* blocks must be multiple of 20 bytes length */ + if (block->num_bytes % 20) + return 0; +@@ -7023,7 +7052,7 @@ static int add_displayid_detailed_1_modes(struct drm_connector *connector, + for (i = 0; i < num_timings; i++) { + struct displayid_detailed_timings_1 *timings = &det->timings[i]; + +- newmode = drm_mode_displayid_detailed(connector->dev, timings, type_7); ++ newmode = drm_mode_displayid_detailed(connector->dev, block, timings); + if (!newmode) + continue; + +@@ -7070,7 +7099,8 @@ static int add_displayid_formula_modes(struct drm_connector *connector, + struct drm_display_mode *newmode; + int num_modes = 0; + bool type_10 = block->tag == DATA_BLOCK_2_TYPE_10_FORMULA_TIMING; +- int timing_size = 6 + ((formula_block->base.rev & 0x70) >> 4); ++ int timing_size = 6 + ++ FIELD_GET(DISPLAYID_BLOCK_DESCRIPTOR_PAYLOAD_BYTES, formula_block->base.rev); + + /* extended blocks are not supported yet */ + if (timing_size != 6) +diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h +--- a/include/drm/drm_connector.h ++++ b/include/drm/drm_connector.h +@@ -939,6 +939,12 @@ struct drm_display_info { + */ + u32 max_dsc_bpp; + ++ /** ++ * @dp_dsc_bpp: DP Display-Stream-Compression (DSC) timing's target ++ * DSC bits per pixel in 6.4 fixed point format. 0 means undefined. ++ */ ++ u16 dp_dsc_bpp_x16; ++ + /** + * @vics: Array of vics_len VICs. Internal to EDID parsing. + */ +diff --git a/include/drm/drm_modes.h b/include/drm/drm_modes.h +--- a/include/drm/drm_modes.h ++++ b/include/drm/drm_modes.h +@@ -417,6 +417,16 @@ struct drm_display_mode { + */ + enum hdmi_picture_aspect picture_aspect_ratio; + ++ /** ++ * @dsc_passthrough_timing_support: ++ * ++ * Indicates whether this mode timing descriptor is supported ++ * with specific target DSC bits per pixel only. ++ * ++ * VESA vendor-specific data block shall exist with the relevant ++ * DSC bits per pixel declaration when this flag is set to true. ++ */ ++ bool dsc_passthrough_timings_support; + }; + + /** diff --git a/pkgbuilds/linux-omarchy-bore/0461-vesa-dsc-passthru-mode-match-fix.patch b/pkgbuilds/linux-omarchy-bore/0461-vesa-dsc-passthru-mode-match-fix.patch new file mode 100644 index 0000000..e27b493 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0461-vesa-dsc-passthru-mode-match-fix.patch @@ -0,0 +1,51 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -7622,6 +7622,7 @@ create_stream_for_sink(struct drm_connector *connector, + struct drm_display_mode saved_mode; + struct drm_display_mode *freesync_mode = NULL; + struct drm_display_mode *dsc_passthru_mode = NULL; ++ unsigned int dsc_passthru_match_flags; + bool native_mode_found = false; + bool recalculate_timing = false; + bool scale = dm_state->scaling != RMX_OFF; +@@ -7688,6 +7689,22 @@ create_stream_for_sink(struct drm_connector *connector, + + mode_refresh = drm_mode_vrefresh(&mode); + ++ dsc_passthru_match_flags = DRM_MODE_MATCH_TIMINGS | ++ DRM_MODE_MATCH_CLOCK | ++ DRM_MODE_MATCH_FLAGS | ++ DRM_MODE_MATCH_3D_FLAGS; ++ if (mode.picture_aspect_ratio) ++ dsc_passthru_match_flags |= DRM_MODE_MATCH_ASPECT_RATIO; ++ ++ list_for_each_entry(dsc_passthru_mode, &connector->modes, head) { ++ if (drm_mode_match(dsc_passthru_mode, &mode, ++ dsc_passthru_match_flags)) { ++ mode.dsc_passthrough_timings_support = ++ dsc_passthru_mode->dsc_passthrough_timings_support; ++ break; ++ } ++ } ++ + if (preferred_mode == NULL) { + /* + * This may not be an error, the use case is when we have no +@@ -7713,16 +7730,6 @@ create_stream_for_sink(struct drm_connector *connector, + } + } + +- list_for_each_entry(dsc_passthru_mode, &connector->modes, head) { +- if (dsc_passthru_mode->hdisplay == mode.hdisplay && +- dsc_passthru_mode->vdisplay == mode.vdisplay && +- drm_mode_vrefresh(dsc_passthru_mode) == mode_refresh) { +- mode.dsc_passthrough_timings_support = +- dsc_passthru_mode->dsc_passthrough_timings_support; +- break; +- } +- } +- + if (recalculate_timing) + drm_mode_set_crtcinfo(&saved_mode, 0); + diff --git a/pkgbuilds/linux-omarchy-bore/0472-amdgpu-userq-post-reset-error.patch b/pkgbuilds/linux-omarchy-bore/0472-amdgpu-userq-post-reset-error.patch new file mode 100644 index 0000000..942772c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0472-amdgpu-userq-post-reset-error.patch @@ -0,0 +1,26 @@ +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +@@ -1426,7 +1426,7 @@ int amdgpu_userq_post_reset(struct amdgpu_device *adev, bool vram_lost) + struct amdgpu_usermode_queue *queue; + const struct amdgpu_userq_funcs *userq_funcs; + unsigned long queue_id; +- int r = 0; ++ int ret = 0, r; + + xa_for_each(&adev->userq_doorbell_xa, queue_id, queue) { + if (queue->state == AMDGPU_USERQ_STATE_HUNG && !vram_lost) { +@@ -1435,11 +1435,12 @@ int amdgpu_userq_post_reset(struct amdgpu_device *adev, bool vram_lost) + r = userq_funcs->map(queue); + if (r) { + dev_err(adev->dev, "Failed to remap queue %ld\n", queue_id); ++ ret = r; + continue; + } + queue->state = AMDGPU_USERQ_STATE_MAPPED; + } + } + +- return r; ++ return ret; + } diff --git a/pkgbuilds/linux-omarchy-bore/0473-i915-ptl-cdclk-sanitize.patch b/pkgbuilds/linux-omarchy-bore/0473-i915-ptl-cdclk-sanitize.patch new file mode 100644 index 0000000..08d39ec --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0473-i915-ptl-cdclk-sanitize.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c b/drivers/gpu/drm/i915/display/intel_cdclk.c +--- a/drivers/gpu/drm/i915/display/intel_cdclk.c ++++ b/drivers/gpu/drm/i915/display/intel_cdclk.c +@@ -2366,8 +2366,10 @@ static void bxt_sanitize_cdclk(struct intel_display *display) + * dividers both syncing to an active pipe, or asynchronously + * (PIPE_NONE). + */ +- cdctl &= ~bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); +- cdctl |= bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); ++ if (DISPLAY_VER(display) < 30) { ++ cdctl &= ~bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); ++ cdctl |= bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); ++ } + + if (cdctl != expected) { + if (DISPLAY_VER(display) < 20) { diff --git a/pkgbuilds/linux-omarchy-bore/0474-amd-display-oled-vesa-backlight.patch b/pkgbuilds/linux-omarchy-bore/0474-amd-display-oled-vesa-backlight.patch new file mode 100644 index 0000000..590d8a0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0474-amd-display-oled-vesa-backlight.patch @@ -0,0 +1,18 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -4160,8 +4160,12 @@ static void update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) + else if (!IS_ERR_OR_NULL(panel_backlight_quirk) && + panel_backlight_quirk->force_pwm) + caps->aux_support = false; +- if (caps->aux_support) +- aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX; ++ if (caps->aux_support) { ++ if (aconnector->dc_link->dpcd_caps.panel_luminance_control) ++ aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_VESA_AUX; ++ else ++ aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX; ++ } + + luminance_range = &conn_base->display_info.luminance_range; + diff --git a/pkgbuilds/linux-omarchy-bore/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch b/pkgbuilds/linux-omarchy-bore/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch new file mode 100644 index 0000000..a58a962 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c +--- a/drivers/gpu/drm/i915/display/intel_dp.c ++++ b/drivers/gpu/drm/i915/display/intel_dp.c +@@ -3219,11 +3219,7 @@ static bool intel_dp_needs_as_sdp(struct intel_dp *intel_dp, + if (drm_dp_is_branch(intel_dp->dpcd)) + return false; + +- if (intel_psr_needs_alpm_aux_less(intel_dp, crtc_state) && +- !intel_psr_pr_async_video_timing_supported(intel_dp)) +- return true; +- +- return intel_vrr_possible(crtc_state); ++ return crtc_state->vrr.enable; + } + + static void intel_dp_compute_as_sdp(struct intel_dp *intel_dp, diff --git a/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch b/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch new file mode 100644 index 0000000..29d72a3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch @@ -0,0 +1,50784 @@ +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -1752,6 +1752,7 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) + * by the drivers/platform/x86/serial-multi-instantiate.c driver, which + * knows which client device id to use for each resource. + */ ++ {"AWDZ8399", }, + {"BSG1160", }, + {"BSG2150", }, + {"CSC3551", }, +diff --git a/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c b/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c +--- a/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c ++++ b/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c +@@ -147,7 +147,7 @@ static const struct reg_default halo_register_defaults[] = { + { 0x2b805c0, 0 }, /* HALO_SCRATCH1 */ + { 0x2b805c8, 0 }, /* HALO_SCRATCH2 */ + { 0x2b805d0, 0 }, /* HALO_SCRATCH3 */ +- { 0x2b805c8, 0 }, /* HALO_SCRATCH4 */ ++ { 0x2b805d8, 0 }, /* HALO_SCRATCH4 */ + { 0x2bc1000, 0 }, /* HALO_CCM_CORE_CONTROL */ + { 0x2bc7000, 0 }, /* HALO_WDT_CONTROL */ + +diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c +--- a/drivers/platform/x86/serial-multi-instantiate.c ++++ b/drivers/platform/x86/serial-multi-instantiate.c +@@ -307,6 +307,15 @@ static void smi_remove(struct platform_device *pdev) + smi_devs_unregister(smi); + } + ++static const struct smi_node aw88399_hda = { ++ .instances = { ++ { "aw88399-hda", IRQ_RESOURCE_AUTO, 0 }, ++ { "aw88399-hda", IRQ_RESOURCE_AUTO, 0 }, ++ {} ++ }, ++ .bus_type = SMI_AUTO_DETECT, ++}; ++ + static const struct smi_node bsg1160_data = { + .instances = { + { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, +@@ -405,6 +414,7 @@ static const struct smi_node tas2781_hda = { + * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). + */ + static const struct acpi_device_id smi_acpi_ids[] = { ++ { "AWDZ8399", (unsigned long)&aw88399_hda }, + { "BSG1160", (unsigned long)&bsg1160_data }, + { "BSG2150", (unsigned long)&bsg2150_data }, + { "CSC3551", (unsigned long)&cs35l41_hda }, +diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c +--- a/drivers/soundwire/bus_type.c ++++ b/drivers/soundwire/bus_type.c +@@ -105,6 +105,9 @@ static int sdw_bus_probe(struct device *dev) + } + slave->index = ret; + ++ /* Create IRQ mapping now so the driver can get it in probe() */ ++ sdw_irq_create_mapping(slave); ++ + ret = drv->probe(slave, id); + if (ret) { + ida_free(&slave->bus->slave_ida, slave->index); +@@ -117,9 +120,6 @@ static int sdw_bus_probe(struct device *dev) + if (drv->ops && drv->ops->read_prop) + drv->ops->read_prop(slave); + +- if (slave->prop.use_domain_irq) +- sdw_irq_create_mapping(slave); +- + /* init the dynamic sysfs attributes we need */ + ret = sdw_slave_sysfs_dpn_init(slave); + if (ret < 0) +diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +--- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h ++++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +@@ -152,6 +152,10 @@ + #define LPI_MI2S_TX_4 146 + #define SENARY_MI2S_RX 147 + #define SENARY_MI2S_TX 148 ++#define LPI_MI2S_RX_5 149 ++#define LPI_MI2S_TX_5 150 ++#define LPI_MI2S_RX_6 151 ++#define LPI_MI2S_TX_6 152 + + #define LPASS_CLK_ID_PRI_MI2S_IBIT 1 + #define LPASS_CLK_ID_PRI_MI2S_EBIT 2 +@@ -233,11 +237,39 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 70 + ++#define LAPSS_CLK_ID_QAIF_IF0_IBIT 71 ++#define LAPSS_CLK_ID_QAIF_IF0_EBIT 72 ++#define LAPSS_CLK_ID_QAIF_IF1_IBIT 73 ++#define LAPSS_CLK_ID_QAIF_IF1_EBIT 74 ++#define LAPSS_CLK_ID_QAIF_IF2_IBIT 75 ++#define LAPSS_CLK_ID_QAIF_IF2_EBIT 76 ++#define LAPSS_CLK_ID_QAIF_IF3_IBIT 77 ++#define LAPSS_CLK_ID_QAIF_IF3_EBIT 78 ++#define LAPSS_CLK_ID_QAIF_IF4_IBIT 79 ++#define LAPSS_CLK_ID_QAIF_IF4_EBIT 80 ++#define LAPSS_CLK_ID_QAIF_IF5_IBIT 81 ++#define LAPSS_CLK_ID_QAIF_IF5_EBIT 82 ++#define LAPSS_CLK_ID_QAIF_IF6_IBIT 83 ++#define LAPSS_CLK_ID_QAIF_IF6_EBIT 84 ++#define LAPSS_CLK_ID_QAIF_IF7_IBIT 85 ++#define LAPSS_CLK_ID_QAIF_IF7_EBIT 86 ++#define LAPSS_CLK_ID_QAIF_IF8_IBIT 87 ++#define LAPSS_CLK_ID_QAIF_IF8_EBIT 88 ++#define LAPSS_CLK_ID_QAIF_IF9_IBIT 89 ++#define LAPSS_CLK_ID_QAIF_IF9_EBIT 90 ++#define LAPSS_CLK_ID_QAIF_IF10_IBIT 91 ++#define LAPSS_CLK_ID_QAIF_IF10_EBIT 92 ++#define LAPSS_CLK_ID_QAIF_IF11_IBIT 93 ++#define LAPSS_CLK_ID_QAIF_IF11_EBIT 94 ++#define LAPSS_CLK_ID_QAIF_IF12_IBIT 95 ++#define LAPSS_CLK_ID_QAIF_IF12_EBIT 96 ++#define LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 97 ++#define LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 98 ++ + #define LPASS_HW_AVTIMER_VOTE 101 + #define LPASS_HW_MACRO_VOTE 102 + #define LPASS_HW_DCODEC_VOTE 103 +- +-#define Q6AFE_MAX_CLK_ID 104 ++#define LPASS_HW_LPR_VOTE 104 + + #define LPASS_CLK_ATTRIBUTE_INVALID 0x0 + #define LPASS_CLK_ATTRIBUTE_COUPLE_NO 0x1 +diff --git a/include/linux/platform_data/omap-twl4030.h b/include/linux/platform_data/omap-twl4030.h +deleted file mode 100644 +--- a/include/linux/platform_data/omap-twl4030.h ++++ /dev/null +@@ -1,42 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-only */ +-/** +- * omap-twl4030.h - ASoC machine driver for TI SoC based boards with twl4030 +- * codec, header. +- * +- * Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com +- * All rights reserved. +- * +- * Author: Peter Ujfalusi +- */ +- +-#ifndef _OMAP_TWL4030_H_ +-#define _OMAP_TWL4030_H_ +- +-/* To select if only one channel is connected in a stereo port */ +-#define OMAP_TWL4030_LEFT (1 << 0) +-#define OMAP_TWL4030_RIGHT (1 << 1) +- +-struct omap_tw4030_pdata { +- const char *card_name; +- /* Voice port is connected to McBSP3 */ +- bool voice_connected; +- +- /* The driver will parse the connection flags if this flag is set */ +- bool custom_routing; +- /* Flags to indicate connected audio ports. */ +- u8 has_hs; +- u8 has_hf; +- u8 has_predriv; +- u8 has_carkit; +- bool has_ear; +- +- bool has_mainmic; +- bool has_submic; +- bool has_hsmic; +- bool has_carkitmic; +- bool has_digimic0; +- bool has_digimic1; +- u8 has_linein; +-}; +- +-#endif /* _OMAP_TWL4030_H_ */ +diff --git a/include/sound/ac97/codec.h b/include/sound/ac97/codec.h +--- a/include/sound/ac97/codec.h ++++ b/include/sound/ac97/codec.h +@@ -33,7 +33,7 @@ struct ac97_id { + }; + + /** +- * ac97_codec_device - a ac97 codec ++ * struct ac97_codec_device - a ac97 codec + * @dev: the core device + * @vendor_id: the vendor_id of the codec, as sensed on the AC-link + * @num: the codec number, 0 is primary, 1 is first slave, etc ... +@@ -53,7 +53,7 @@ struct ac97_codec_device { + }; + + /** +- * ac97_codec_driver - a ac97 codec driver ++ * struct ac97_codec_driver - a ac97 codec driver + * @driver: the device driver structure + * @probe: the function called when a ac97_codec_device is matched + * @remove: the function called when the device is unbound/removed +diff --git a/include/sound/asequencer.h b/include/sound/asequencer.h +--- a/include/sound/asequencer.h ++++ b/include/sound/asequencer.h +@@ -11,9 +11,6 @@ + #include + #include + +-/* helper macro */ +-#define snd_seq_event_bounce_ext_data(ev) ((void*)((char *)(ev)->data.ext.ptr + sizeof(struct snd_seq_event_bounce))) +- + /* + * type check macros + */ +diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h +new file mode 100644 +--- /dev/null ++++ b/include/sound/aw88399.h +@@ -0,0 +1,621 @@ ++/* SPDX-License-Identifier: GPL-2.0-only ++ * ++ * linux/sound/aw88399.h -- Platform data for AW88399 ++ * ++ * Copyright (c) 2023 AWINIC Technology CO., LTD ++ * ++ * Author: Weidong Wang ++ */ ++ ++#ifndef __SOUND_AW88399_H ++#define __SOUND_AW88399_H ++ ++#include ++#include ++ ++/* registers list */ ++#define AW88399_ID_REG (0x00) ++#define AW88399_SYSST_REG (0x01) ++#define AW88399_SYSINT_REG (0x02) ++#define AW88399_SYSINTM_REG (0x03) ++#define AW88399_SYSCTRL_REG (0x04) ++#define AW88399_SYSCTRL2_REG (0x05) ++#define AW88399_I2SCTRL1_REG (0x06) ++#define AW88399_I2SCTRL2_REG (0x07) ++#define AW88399_I2SCTRL3_REG (0x08) ++#define AW88399_DACCFG1_REG (0x09) ++#define AW88399_DACCFG2_REG (0x0A) ++#define AW88399_DACCFG3_REG (0x0B) ++#define AW88399_DACCFG4_REG (0x0C) ++#define AW88399_DACCFG5_REG (0x0D) ++#define AW88399_DACCFG6_REG (0x0E) ++#define AW88399_DACCFG7_REG (0x0F) ++#define AW88399_MPDCFG1_REG (0x10) ++#define AW88399_MPDCFG2_REG (0x11) ++#define AW88399_MPDCFG3_REG (0x12) ++#define AW88399_MPDCFG4_REG (0x13) ++#define AW88399_PWMCTRL1_REG (0x14) ++#define AW88399_PWMCTRL2_REG (0x15) ++#define AW88399_PWMCTRL3_REG (0x16) ++#define AW88399_I2SCFG1_REG (0x17) ++#define AW88399_DBGCTRL_REG (0x18) ++#define AW88399_HAGCST_REG (0x20) ++#define AW88399_VBAT_REG (0x21) ++#define AW88399_TEMP_REG (0x22) ++#define AW88399_PVDD_REG (0x23) ++#define AW88399_ISNDAT_REG (0x24) ++#define AW88399_VSNDAT_REG (0x25) ++#define AW88399_I2SINT_REG (0x26) ++#define AW88399_I2SCAPCNT_REG (0x27) ++#define AW88399_ANASTA1_REG (0x28) ++#define AW88399_ANASTA2_REG (0x29) ++#define AW88399_ANASTA3_REG (0x2A) ++#define AW88399_TESTDET_REG (0x2B) ++#define AW88399_DSMCFG1_REG (0x30) ++#define AW88399_DSMCFG2_REG (0x31) ++#define AW88399_DSMCFG3_REG (0x32) ++#define AW88399_DSMCFG4_REG (0x33) ++#define AW88399_DSMCFG5_REG (0x34) ++#define AW88399_DSMCFG6_REG (0x35) ++#define AW88399_DSMCFG7_REG (0x36) ++#define AW88399_DSMCFG8_REG (0x37) ++#define AW88399_TESTIN_REG (0x38) ++#define AW88399_TESTOUT_REG (0x39) ++#define AW88399_MEMTEST_REG (0x3A) ++#define AW88399_VSNCTRL1_REG (0x3B) ++#define AW88399_ISNCTRL1_REG (0x3C) ++#define AW88399_ISNCTRL2_REG (0x3D) ++#define AW88399_DSPMADD_REG (0x40) ++#define AW88399_DSPMDAT_REG (0x41) ++#define AW88399_WDT_REG (0x42) ++#define AW88399_ACR1_REG (0x43) ++#define AW88399_ACR2_REG (0x44) ++#define AW88399_ASR1_REG (0x45) ++#define AW88399_ASR2_REG (0x46) ++#define AW88399_DSPCFG_REG (0x47) ++#define AW88399_ASR3_REG (0x48) ++#define AW88399_ASR4_REG (0x49) ++#define AW88399_DSPVCALB_REG (0x4A) ++#define AW88399_CRCCTRL_REG (0x4B) ++#define AW88399_DSPDBG1_REG (0x4C) ++#define AW88399_DSPDBG2_REG (0x4D) ++#define AW88399_DSPDBG3_REG (0x4E) ++#define AW88399_PLLCTRL1_REG (0x50) ++#define AW88399_PLLCTRL2_REG (0x51) ++#define AW88399_PLLCTRL3_REG (0x52) ++#define AW88399_CDACTRL1_REG (0x53) ++#define AW88399_CDACTRL2_REG (0x54) ++#define AW88399_CDACTRL3_REG (0x55) ++#define AW88399_SADCCTRL1_REG (0x56) ++#define AW88399_SADCCTRL2_REG (0x57) ++#define AW88399_BOPCTRL1_REG (0x58) ++#define AW88399_BOPCTRL2_REG (0x5A) ++#define AW88399_BOPCTRL3_REG (0x5B) ++#define AW88399_BOPCTRL4_REG (0x5C) ++#define AW88399_BOPCTRL5_REG (0x5D) ++#define AW88399_BOPCTRL6_REG (0x5E) ++#define AW88399_BOPCTRL7_REG (0x5F) ++#define AW88399_BSTCTRL1_REG (0x60) ++#define AW88399_BSTCTRL2_REG (0x61) ++#define AW88399_BSTCTRL3_REG (0x62) ++#define AW88399_BSTCTRL4_REG (0x63) ++#define AW88399_BSTCTRL5_REG (0x64) ++#define AW88399_BSTCTRL6_REG (0x65) ++#define AW88399_BSTCTRL7_REG (0x66) ++#define AW88399_BSTCTRL8_REG (0x67) ++#define AW88399_BSTCTRL9_REG (0x68) ++#define AW88399_BSTCTRL10_REG (0x69) ++#define AW88399_CPCTRL_REG (0x6A) ++#define AW88399_EFWH_REG (0x6C) ++#define AW88399_EFWM2_REG (0x6D) ++#define AW88399_EFWM1_REG (0x6E) ++#define AW88399_EFWL_REG (0x6F) ++#define AW88399_TESTCTRL1_REG (0x70) ++#define AW88399_TESTCTRL2_REG (0x71) ++#define AW88399_EFCTRL1_REG (0x72) ++#define AW88399_EFCTRL2_REG (0x73) ++#define AW88399_EFRH4_REG (0x74) ++#define AW88399_EFRH3_REG (0x75) ++#define AW88399_EFRH2_REG (0x76) ++#define AW88399_EFRH1_REG (0x77) ++#define AW88399_EFRL4_REG (0x78) ++#define AW88399_EFRL3_REG (0x79) ++#define AW88399_EFRL2_REG (0x7A) ++#define AW88399_EFRL1_REG (0x7B) ++#define AW88399_TM_REG (0x7C) ++#define AW88399_TM2_REG (0x7D) ++ ++#define AW88399_REG_MAX (0x7E) ++#define AW88399_MUTE_VOL (1023) ++ ++#define AW88399_DSP_CFG_ADDR (0x9B00) ++#define AW88399_DSP_REG_CFG_ADPZ_RA (0x9B68) ++#define AW88399_DSP_FW_ADDR (0x8980) ++#define AW88399_DSP_ROM_CHECK_ADDR (0x1F40) ++#define AW88399_DSP_ROM_CHECK_DATA (0x4638) ++ ++#define AW88399_CALI_RE_HBITS_MASK (~(0xFFFF0000)) ++#define AW88399_CALI_RE_HBITS_SHIFT (16) ++ ++#define AW88399_CALI_RE_LBITS_MASK (~(0xFFFF)) ++#define AW88399_CALI_RE_LBITS_SHIFT (0) ++ ++#define AW88399_I2STXEN_START_BIT (9) ++#define AW88399_I2STXEN_BITS_LEN (1) ++#define AW88399_I2STXEN_MASK \ ++ (~(((1<> (shift)) ++#define AW88399_SHOW_RE_TO_DSP_RE(re, shift) (((re) << shift) / (1000)) ++#define AW88399_CRC_CHECK_PASS_VAL (0x4) ++ ++#define AW88399_CRC_CFG_BASE_ADDR (0xD80) ++#define AW88399_CRC_FW_BASE_ADDR (0x4C0) ++#define AW88399_ACF_FILE "aw88399_acf.bin" ++#define AW88399_DEV_SYSST_CHECK_MAX (10) ++#define AW88399_CHIP_ID 0x2183 ++ ++#define AW88399_START_RETRIES (5) ++#define AW88399_START_WORK_DELAY_MS (0) ++ ++enum { ++ AW_EF_AND_CHECK = 0, ++ AW_EF_OR_CHECK, ++}; ++ ++enum { ++ AW88399_DEV_VDSEL_DAC = 0, ++ AW88399_DEV_VDSEL_VSENSE = 32, ++}; ++ ++enum { ++ AW88399_DSP_CRC_NA = 0, ++ AW88399_DSP_CRC_OK = 1, ++}; ++ ++enum { ++ AW88399_DSP_FW_UPDATE_OFF = 0, ++ AW88399_DSP_FW_UPDATE_ON = 1, ++}; ++ ++enum { ++ AW88399_FORCE_UPDATE_OFF = 0, ++ AW88399_FORCE_UPDATE_ON = 1, ++}; ++ ++enum { ++ AW88399_1000_US = 1000, ++ AW88399_2000_US = 2000, ++ AW88399_3000_US = 3000, ++ AW88399_4000_US = 4000, ++}; ++ ++enum AW88399_DEV_STATUS { ++ AW88399_DEV_PW_OFF = 0, ++ AW88399_DEV_PW_ON, ++}; ++ ++enum AW88399_DEV_FW_STATUS { ++ AW88399_DEV_FW_FAILED = 0, ++ AW88399_DEV_FW_OK, ++}; ++ ++enum AW88399_DEV_MEMCLK { ++ AW88399_DEV_MEMCLK_OSC = 0, ++ AW88399_DEV_MEMCLK_PLL = 1, ++}; ++ ++enum AW88399_DEV_DSP_CFG { ++ AW88399_DEV_DSP_WORK = 0, ++ AW88399_DEV_DSP_BYPASS = 1, ++}; ++ ++enum { ++ AW88399_NOT_RCV_MODE = 0, ++ AW88399_RCV_MODE = 1, ++}; ++ ++enum { ++ AW88399_SYNC_START = 0, ++ AW88399_ASYNC_START, ++}; ++ ++struct aw_device; ++struct aw_container; ++struct aw_cali_desc; ++struct gpio_desc; ++struct i2c_client; ++struct regmap; ++ ++struct aw88399 { ++ struct aw_device *aw_pa; ++ struct mutex lock; ++ struct gpio_desc *reset_gpio; ++ struct delayed_work start_work; ++ struct regmap *regmap; ++ struct aw_container *aw_cfg; ++ ++ unsigned int check_val; ++ unsigned int crc_init_val; ++ unsigned int vcalb_init_val; ++ unsigned int dither_st; ++ bool bsts_unreliable; ++ bool fw_needs_reload; ++}; ++ ++int aw_dev_check_syspll(struct aw_device *aw_dev); ++void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable); ++int aw_dev_get_dsp_status(struct aw_device *aw_dev); ++int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value); ++int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc); ++int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name); ++void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute); ++void aw88399_dev_set_channel(struct aw88399 *aw88399, int channel); ++void aw88399_hw_reset(struct aw88399 *aw88399); ++int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap); ++extern const struct regmap_config aw88399_remap_config; ++int aw88399_request_firmware_file(struct aw88399 *aw88399); ++void aw88399_start(struct aw88399 *aw88399, bool sync_start); ++void aw88399_startup_work(struct work_struct *work); ++int aw88399_stop(struct aw_device *aw_dev); ++ ++#endif /* __SOUND_AW88399_H */ +diff --git a/include/sound/control.h b/include/sound/control.h +--- a/include/sound/control.h ++++ b/include/sound/control.h +@@ -7,6 +7,7 @@ + * Copyright (c) by Jaroslav Kysela + */ + ++#include + #include + #include + #include +@@ -167,6 +168,8 @@ snd_ctl_find_id_mixer(struct snd_card *card, const char *name) + + int snd_ctl_create(struct snd_card *card); + ++extern struct rw_semaphore snd_ioctl_rwsem; ++ + int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn); + int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn); + #ifdef CONFIG_COMPAT +diff --git a/include/sound/core.h b/include/sound/core.h +--- a/include/sound/core.h ++++ b/include/sound/core.h +@@ -108,8 +108,8 @@ struct snd_card { + char longname[80]; /* name of this soundcard */ + char irq_descr[32]; /* Interrupt description */ + char mixername[80]; /* mixer name */ +- char components[128]; /* card components delimited with +- space */ ++ char *components; /* card components, space-delimited */ ++ unsigned int components_alloc_size; /* current allocation size of components */ + struct module *module; /* top-level module */ + + void *private_data; /* private data for soundcard */ +diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h +--- a/include/sound/cs35l56.h ++++ b/include/sound/cs35l56.h +@@ -417,7 +417,6 @@ void cs35l56_wait_control_port_ready(void); + void cs35l56_wait_min_reset_pulse(void); + void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire); + int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq); +-irqreturn_t cs35l56_irq(int irq, void *data); + int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base); + int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base); + int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire); +diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h +--- a/include/sound/dmaengine_pcm.h ++++ b/include/sound/dmaengine_pcm.h +@@ -175,12 +175,6 @@ int snd_dmaengine_pcm_prepare_slave_config(struct snd_pcm_substream *substream, + struct dmaengine_pcm { + struct dma_chan *chan[SNDRV_PCM_STREAM_LAST + 1]; + const struct snd_dmaengine_pcm_config *config; +- struct snd_soc_component component; + unsigned int flags; + }; +- +-static inline struct dmaengine_pcm *soc_component_to_pcm(struct snd_soc_component *p) +-{ +- return container_of(p, struct dmaengine_pcm, component); +-} + #endif +diff --git a/include/sound/hda-mlink.h b/include/sound/hda-mlink.h +--- a/include/sound/hda-mlink.h ++++ b/include/sound/hda-mlink.h +@@ -9,6 +9,22 @@ + struct hdac_bus; + struct hdac_ext_link; + ++/** ++ * enum hda_bus_ml_link_type - mlink link type, used by SOF link DMA ++ * allocator constraints (see struct sof_intel_hda_dev). ++ * ++ * @HDA_BUS_ML_LINK_HDA: non-alt link, i.e. HDA codec or iDisp ++ * @HDA_BUS_ML_LINK_SDW: alt link, SoundWire ++ * @HDA_BUS_ML_LINK_UAOL: alt link, USB Audio Offload ++ * @HDA_BUS_ML_LINK_OTHER: alt link, SSP or DMIC ++ */ ++enum hda_bus_ml_link_type { ++ HDA_BUS_ML_LINK_HDA, ++ HDA_BUS_ML_LINK_SDW, ++ HDA_BUS_ML_LINK_UAOL, ++ HDA_BUS_ML_LINK_OTHER, ++}; ++ + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK) + + int hda_bus_ml_init(struct hdac_bus *bus); +@@ -49,11 +65,12 @@ int hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num); + int hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y, + int channel_mask, int stream_id, int dir); + +-void hda_bus_ml_put_all(struct hdac_bus *bus); + void hda_bus_ml_reset_losidv(struct hdac_bus *bus); + int hda_bus_ml_resume(struct hdac_bus *bus); + int hda_bus_ml_suspend(struct hdac_bus *bus); + ++enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink); ++ + struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus); + struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus); + struct hdac_ext_link *hdac_bus_eml_sdw_get_hlink(struct hdac_bus *bus); +@@ -169,11 +186,13 @@ hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y, + return 0; + } + +-static inline void hda_bus_ml_put_all(struct hdac_bus *bus) { } + static inline void hda_bus_ml_reset_losidv(struct hdac_bus *bus) { } + static inline int hda_bus_ml_resume(struct hdac_bus *bus) { return 0; } + static inline int hda_bus_ml_suspend(struct hdac_bus *bus) { return 0; } + ++static inline enum hda_bus_ml_link_type ++hda_bus_ml_link_get_type(struct hdac_ext_link *hlink) { return HDA_BUS_ML_LINK_HDA; } ++ + static inline struct hdac_ext_link * + hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus) { return NULL; } + +diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h +--- a/include/sound/hda_codec.h ++++ b/include/sound/hda_codec.h +@@ -256,6 +256,7 @@ struct hda_codec { + unsigned int link_down_at_suspend:1; /* link down at runtime suspend */ + unsigned int relaxed_resume:1; /* don't resume forcibly for jack */ + unsigned int forced_resume:1; /* forced resume for jack */ ++ unsigned int acomp_requested_resume:1; /* resume requested by acomp */ + unsigned int no_stream_clean_at_suspend:1; /* do not clean streams at suspend */ + unsigned int ctl_dev_id:1; /* old control element id build behaviour */ + unsigned int eld_jack_detect:1; /* Machine jack-detection by ELD */ +diff --git a/include/sound/hda_regmap.h b/include/sound/hda_regmap.h +--- a/include/sound/hda_regmap.h ++++ b/include/sound/hda_regmap.h +@@ -69,11 +69,14 @@ void snd_hdac_regmap_sync(struct hdac_device *codec); + + /** + * snd_hdac_regmap_write - Write a verb with caching ++ * @codec: HD-audio codec base device + * @nid: codec NID +- * @reg: verb to write ++ * @verb: verb to write + * @val: value to write + * + * For writing an amp value, use snd_hdac_regmap_update_amp(). ++ * ++ * Returns: %0 if successful or a negative error code. + */ + static inline int + snd_hdac_regmap_write(struct hdac_device *codec, hda_nid_t nid, +@@ -85,13 +88,16 @@ snd_hdac_regmap_write(struct hdac_device *codec, hda_nid_t nid, + } + + /** +- * snd_hda_regmap_update - Update a verb value with caching ++ * snd_hdac_regmap_update - Update a verb value with caching ++ * @codec: HD-audio codec + * @nid: codec NID + * @verb: verb to update + * @mask: bit mask to update + * @val: value to update + * + * For updating an amp value, use snd_hdac_regmap_update_amp(). ++ * ++ * Returns: %0 if successful or a negative error code. + */ + static inline int + snd_hdac_regmap_update(struct hdac_device *codec, hda_nid_t nid, +@@ -104,12 +110,15 @@ snd_hdac_regmap_update(struct hdac_device *codec, hda_nid_t nid, + } + + /** +- * snd_hda_regmap_read - Read a verb with caching ++ * snd_hdac_regmap_read - Read a verb with caching ++ * @codec: HD-audio codec + * @nid: codec NID + * @verb: verb to read + * @val: pointer to store the value + * + * For reading an amp value, use snd_hda_regmap_get_amp(). ++ * ++ * Returns: %0 if successful or a negative error code. + */ + static inline int + snd_hdac_regmap_read(struct hdac_device *codec, hda_nid_t nid, +@@ -125,12 +134,12 @@ snd_hdac_regmap_read(struct hdac_device *codec, hda_nid_t nid, + * @codec: HD-audio codec + * @nid: NID to read the AMP value + * @ch: channel (left=0 or right=1) +- * @direction: #HDA_INPUT or #HDA_OUTPUT +- * @index: the index value (only for input direction) +- * @val: the pointer to store the value ++ * @dir: #HDA_INPUT or #HDA_OUTPUT ++ * @idx: the index value (only for input direction) + * + * Read AMP value. The volume is between 0 to 0x7f, 0x80 = mute bit. +- * Returns the value or a negative error. ++ * ++ * Returns: the value or a negative error. + */ + static inline int + snd_hdac_regmap_get_amp(struct hdac_device *codec, hda_nid_t nid, +@@ -148,13 +157,14 @@ snd_hdac_regmap_get_amp(struct hdac_device *codec, hda_nid_t nid, + * @codec: HD-audio codec + * @nid: NID to read the AMP value + * @ch: channel (left=0 or right=1) +- * @direction: #HDA_INPUT or #HDA_OUTPUT ++ * @dir: #HDA_INPUT or #HDA_OUTPUT + * @idx: the index value (only for input direction) + * @mask: bit mask to set + * @val: the bits value to set + * + * Update the AMP value with a bit mask. +- * Returns 0 if the value is unchanged, 1 if changed, or a negative error. ++ * ++ * Returns: 0 if the value is unchanged, 1 if changed, or a negative error. + */ + static inline int + snd_hdac_regmap_update_amp(struct hdac_device *codec, hda_nid_t nid, +@@ -169,13 +179,12 @@ snd_hdac_regmap_update_amp(struct hdac_device *codec, hda_nid_t nid, + * snd_hdac_regmap_get_amp_stereo - Read stereo AMP values + * @codec: HD-audio codec + * @nid: NID to read the AMP value +- * @ch: channel (left=0 or right=1) +- * @direction: #HDA_INPUT or #HDA_OUTPUT +- * @index: the index value (only for input direction) +- * @val: the pointer to store the value ++ * @dir: #HDA_INPUT or #HDA_OUTPUT ++ * @idx: the index value (only for input direction) + * + * Read stereo AMP values. The lower byte is left, the upper byte is right. +- * Returns the value or a negative error. ++ * ++ * Returns: the value or a negative error. + */ + static inline int + snd_hdac_regmap_get_amp_stereo(struct hdac_device *codec, hda_nid_t nid, +@@ -192,14 +201,15 @@ snd_hdac_regmap_get_amp_stereo(struct hdac_device *codec, hda_nid_t nid, + * snd_hdac_regmap_update_amp_stereo - update the stereo AMP value + * @codec: HD-audio codec + * @nid: NID to read the AMP value +- * @direction: #HDA_INPUT or #HDA_OUTPUT ++ * @dir: #HDA_INPUT or #HDA_OUTPUT + * @idx: the index value (only for input direction) + * @mask: bit mask to set + * @val: the bits value to set + * + * Update the stereo AMP value with a bit mask. + * The lower byte is left, the upper byte is right. +- * Returns 0 if the value is unchanged, 1 if changed, or a negative error. ++ * ++ * Returns: 0 if the value is unchanged, 1 if changed, or a negative error. + */ + static inline int + snd_hdac_regmap_update_amp_stereo(struct hdac_device *codec, hda_nid_t nid, +diff --git a/include/sound/pcm.h b/include/sound/pcm.h +--- a/include/sound/pcm.h ++++ b/include/sound/pcm.h +@@ -145,61 +145,60 @@ struct snd_pcm_ops { + #define SNDRV_PCM_RATE_8000_768000 (SNDRV_PCM_RATE_8000_384000|\ + SNDRV_PCM_RATE_705600|\ + SNDRV_PCM_RATE_768000) +-#define _SNDRV_PCM_FMTBIT(fmt) (1ULL << (__force int)SNDRV_PCM_FORMAT_##fmt) +-#define SNDRV_PCM_FMTBIT_S8 _SNDRV_PCM_FMTBIT(S8) +-#define SNDRV_PCM_FMTBIT_U8 _SNDRV_PCM_FMTBIT(U8) +-#define SNDRV_PCM_FMTBIT_S16_LE _SNDRV_PCM_FMTBIT(S16_LE) +-#define SNDRV_PCM_FMTBIT_S16_BE _SNDRV_PCM_FMTBIT(S16_BE) +-#define SNDRV_PCM_FMTBIT_U16_LE _SNDRV_PCM_FMTBIT(U16_LE) +-#define SNDRV_PCM_FMTBIT_U16_BE _SNDRV_PCM_FMTBIT(U16_BE) +-#define SNDRV_PCM_FMTBIT_S24_LE _SNDRV_PCM_FMTBIT(S24_LE) +-#define SNDRV_PCM_FMTBIT_S24_BE _SNDRV_PCM_FMTBIT(S24_BE) +-#define SNDRV_PCM_FMTBIT_U24_LE _SNDRV_PCM_FMTBIT(U24_LE) +-#define SNDRV_PCM_FMTBIT_U24_BE _SNDRV_PCM_FMTBIT(U24_BE) ++#define SNDRV_PCM_FMTBIT_S8 (1ULL << SNDRV_PCM_FORMAT_S8) ++#define SNDRV_PCM_FMTBIT_U8 (1ULL << SNDRV_PCM_FORMAT_U8) ++#define SNDRV_PCM_FMTBIT_S16_LE (1ULL << SNDRV_PCM_FORMAT_S16_LE) ++#define SNDRV_PCM_FMTBIT_S16_BE (1ULL << SNDRV_PCM_FORMAT_S16_BE) ++#define SNDRV_PCM_FMTBIT_U16_LE (1ULL << SNDRV_PCM_FORMAT_U16_LE) ++#define SNDRV_PCM_FMTBIT_U16_BE (1ULL << SNDRV_PCM_FORMAT_U16_BE) ++#define SNDRV_PCM_FMTBIT_S24_LE (1ULL << SNDRV_PCM_FORMAT_S24_LE) ++#define SNDRV_PCM_FMTBIT_S24_BE (1ULL << SNDRV_PCM_FORMAT_S24_BE) ++#define SNDRV_PCM_FMTBIT_U24_LE (1ULL << SNDRV_PCM_FORMAT_U24_LE) ++#define SNDRV_PCM_FMTBIT_U24_BE (1ULL << SNDRV_PCM_FORMAT_U24_BE) + // For S32/U32 formats, 'msbits' hardware parameter is often used to deliver information about the + // available bit count in most significant bit. It's for the case of so-called 'left-justified' or + // `right-padding` sample which has less width than 32 bit. +-#define SNDRV_PCM_FMTBIT_S32_LE _SNDRV_PCM_FMTBIT(S32_LE) +-#define SNDRV_PCM_FMTBIT_S32_BE _SNDRV_PCM_FMTBIT(S32_BE) +-#define SNDRV_PCM_FMTBIT_U32_LE _SNDRV_PCM_FMTBIT(U32_LE) +-#define SNDRV_PCM_FMTBIT_U32_BE _SNDRV_PCM_FMTBIT(U32_BE) +-#define SNDRV_PCM_FMTBIT_FLOAT_LE _SNDRV_PCM_FMTBIT(FLOAT_LE) +-#define SNDRV_PCM_FMTBIT_FLOAT_BE _SNDRV_PCM_FMTBIT(FLOAT_BE) +-#define SNDRV_PCM_FMTBIT_FLOAT64_LE _SNDRV_PCM_FMTBIT(FLOAT64_LE) +-#define SNDRV_PCM_FMTBIT_FLOAT64_BE _SNDRV_PCM_FMTBIT(FLOAT64_BE) +-#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE _SNDRV_PCM_FMTBIT(IEC958_SUBFRAME_LE) +-#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE _SNDRV_PCM_FMTBIT(IEC958_SUBFRAME_BE) +-#define SNDRV_PCM_FMTBIT_MU_LAW _SNDRV_PCM_FMTBIT(MU_LAW) +-#define SNDRV_PCM_FMTBIT_A_LAW _SNDRV_PCM_FMTBIT(A_LAW) +-#define SNDRV_PCM_FMTBIT_IMA_ADPCM _SNDRV_PCM_FMTBIT(IMA_ADPCM) +-#define SNDRV_PCM_FMTBIT_MPEG _SNDRV_PCM_FMTBIT(MPEG) +-#define SNDRV_PCM_FMTBIT_GSM _SNDRV_PCM_FMTBIT(GSM) +-#define SNDRV_PCM_FMTBIT_S20_LE _SNDRV_PCM_FMTBIT(S20_LE) +-#define SNDRV_PCM_FMTBIT_U20_LE _SNDRV_PCM_FMTBIT(U20_LE) +-#define SNDRV_PCM_FMTBIT_S20_BE _SNDRV_PCM_FMTBIT(S20_BE) +-#define SNDRV_PCM_FMTBIT_U20_BE _SNDRV_PCM_FMTBIT(U20_BE) +-#define SNDRV_PCM_FMTBIT_SPECIAL _SNDRV_PCM_FMTBIT(SPECIAL) +-#define SNDRV_PCM_FMTBIT_S24_3LE _SNDRV_PCM_FMTBIT(S24_3LE) +-#define SNDRV_PCM_FMTBIT_U24_3LE _SNDRV_PCM_FMTBIT(U24_3LE) +-#define SNDRV_PCM_FMTBIT_S24_3BE _SNDRV_PCM_FMTBIT(S24_3BE) +-#define SNDRV_PCM_FMTBIT_U24_3BE _SNDRV_PCM_FMTBIT(U24_3BE) +-#define SNDRV_PCM_FMTBIT_S20_3LE _SNDRV_PCM_FMTBIT(S20_3LE) +-#define SNDRV_PCM_FMTBIT_U20_3LE _SNDRV_PCM_FMTBIT(U20_3LE) +-#define SNDRV_PCM_FMTBIT_S20_3BE _SNDRV_PCM_FMTBIT(S20_3BE) +-#define SNDRV_PCM_FMTBIT_U20_3BE _SNDRV_PCM_FMTBIT(U20_3BE) +-#define SNDRV_PCM_FMTBIT_S18_3LE _SNDRV_PCM_FMTBIT(S18_3LE) +-#define SNDRV_PCM_FMTBIT_U18_3LE _SNDRV_PCM_FMTBIT(U18_3LE) +-#define SNDRV_PCM_FMTBIT_S18_3BE _SNDRV_PCM_FMTBIT(S18_3BE) +-#define SNDRV_PCM_FMTBIT_U18_3BE _SNDRV_PCM_FMTBIT(U18_3BE) +-#define SNDRV_PCM_FMTBIT_G723_24 _SNDRV_PCM_FMTBIT(G723_24) +-#define SNDRV_PCM_FMTBIT_G723_24_1B _SNDRV_PCM_FMTBIT(G723_24_1B) +-#define SNDRV_PCM_FMTBIT_G723_40 _SNDRV_PCM_FMTBIT(G723_40) +-#define SNDRV_PCM_FMTBIT_G723_40_1B _SNDRV_PCM_FMTBIT(G723_40_1B) +-#define SNDRV_PCM_FMTBIT_DSD_U8 _SNDRV_PCM_FMTBIT(DSD_U8) +-#define SNDRV_PCM_FMTBIT_DSD_U16_LE _SNDRV_PCM_FMTBIT(DSD_U16_LE) +-#define SNDRV_PCM_FMTBIT_DSD_U32_LE _SNDRV_PCM_FMTBIT(DSD_U32_LE) +-#define SNDRV_PCM_FMTBIT_DSD_U16_BE _SNDRV_PCM_FMTBIT(DSD_U16_BE) +-#define SNDRV_PCM_FMTBIT_DSD_U32_BE _SNDRV_PCM_FMTBIT(DSD_U32_BE) ++#define SNDRV_PCM_FMTBIT_S32_LE (1ULL << SNDRV_PCM_FORMAT_S32_LE) ++#define SNDRV_PCM_FMTBIT_S32_BE (1ULL << SNDRV_PCM_FORMAT_S32_BE) ++#define SNDRV_PCM_FMTBIT_U32_LE (1ULL << SNDRV_PCM_FORMAT_U32_LE) ++#define SNDRV_PCM_FMTBIT_U32_BE (1ULL << SNDRV_PCM_FORMAT_U32_BE) ++#define SNDRV_PCM_FMTBIT_FLOAT_LE (1ULL << SNDRV_PCM_FORMAT_FLOAT_LE) ++#define SNDRV_PCM_FMTBIT_FLOAT_BE (1ULL << SNDRV_PCM_FORMAT_FLOAT_BE) ++#define SNDRV_PCM_FMTBIT_FLOAT64_LE (1ULL << SNDRV_PCM_FORMAT_FLOAT64_LE) ++#define SNDRV_PCM_FMTBIT_FLOAT64_BE (1ULL << SNDRV_PCM_FORMAT_FLOAT64_BE) ++#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE (1ULL << SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE) ++#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE (1ULL << SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE) ++#define SNDRV_PCM_FMTBIT_MU_LAW (1ULL << SNDRV_PCM_FORMAT_MU_LAW) ++#define SNDRV_PCM_FMTBIT_A_LAW (1ULL << SNDRV_PCM_FORMAT_A_LAW) ++#define SNDRV_PCM_FMTBIT_IMA_ADPCM (1ULL << SNDRV_PCM_FORMAT_IMA_ADPCM) ++#define SNDRV_PCM_FMTBIT_MPEG (1ULL << SNDRV_PCM_FORMAT_MPEG) ++#define SNDRV_PCM_FMTBIT_GSM (1ULL << SNDRV_PCM_FORMAT_GSM) ++#define SNDRV_PCM_FMTBIT_S20_LE (1ULL << SNDRV_PCM_FORMAT_S20_LE) ++#define SNDRV_PCM_FMTBIT_U20_LE (1ULL << SNDRV_PCM_FORMAT_U20_LE) ++#define SNDRV_PCM_FMTBIT_S20_BE (1ULL << SNDRV_PCM_FORMAT_S20_BE) ++#define SNDRV_PCM_FMTBIT_U20_BE (1ULL << SNDRV_PCM_FORMAT_U20_BE) ++#define SNDRV_PCM_FMTBIT_SPECIAL (1ULL << SNDRV_PCM_FORMAT_SPECIAL) ++#define SNDRV_PCM_FMTBIT_S24_3LE (1ULL << SNDRV_PCM_FORMAT_S24_3LE) ++#define SNDRV_PCM_FMTBIT_U24_3LE (1ULL << SNDRV_PCM_FORMAT_U24_3LE) ++#define SNDRV_PCM_FMTBIT_S24_3BE (1ULL << SNDRV_PCM_FORMAT_S24_3BE) ++#define SNDRV_PCM_FMTBIT_U24_3BE (1ULL << SNDRV_PCM_FORMAT_U24_3BE) ++#define SNDRV_PCM_FMTBIT_S20_3LE (1ULL << SNDRV_PCM_FORMAT_S20_3LE) ++#define SNDRV_PCM_FMTBIT_U20_3LE (1ULL << SNDRV_PCM_FORMAT_U20_3LE) ++#define SNDRV_PCM_FMTBIT_S20_3BE (1ULL << SNDRV_PCM_FORMAT_S20_3BE) ++#define SNDRV_PCM_FMTBIT_U20_3BE (1ULL << SNDRV_PCM_FORMAT_U20_3BE) ++#define SNDRV_PCM_FMTBIT_S18_3LE (1ULL << SNDRV_PCM_FORMAT_S18_3LE) ++#define SNDRV_PCM_FMTBIT_U18_3LE (1ULL << SNDRV_PCM_FORMAT_U18_3LE) ++#define SNDRV_PCM_FMTBIT_S18_3BE (1ULL << SNDRV_PCM_FORMAT_S18_3BE) ++#define SNDRV_PCM_FMTBIT_U18_3BE (1ULL << SNDRV_PCM_FORMAT_U18_3BE) ++#define SNDRV_PCM_FMTBIT_G723_24 (1ULL << SNDRV_PCM_FORMAT_G723_24) ++#define SNDRV_PCM_FMTBIT_G723_24_1B (1ULL << SNDRV_PCM_FORMAT_G723_24_1B) ++#define SNDRV_PCM_FMTBIT_G723_40 (1ULL << SNDRV_PCM_FORMAT_G723_40) ++#define SNDRV_PCM_FMTBIT_G723_40_1B (1ULL << SNDRV_PCM_FORMAT_G723_40_1B) ++#define SNDRV_PCM_FMTBIT_DSD_U8 (1ULL << SNDRV_PCM_FORMAT_DSD_U8) ++#define SNDRV_PCM_FMTBIT_DSD_U16_LE (1ULL << SNDRV_PCM_FORMAT_DSD_U16_LE) ++#define SNDRV_PCM_FMTBIT_DSD_U32_LE (1ULL << SNDRV_PCM_FORMAT_DSD_U32_LE) ++#define SNDRV_PCM_FMTBIT_DSD_U16_BE (1ULL << SNDRV_PCM_FORMAT_DSD_U16_BE) ++#define SNDRV_PCM_FMTBIT_DSD_U32_BE (1ULL << SNDRV_PCM_FORMAT_DSD_U32_BE) + + #ifdef SNDRV_LITTLE_ENDIAN + #define SNDRV_PCM_FMTBIT_S16 SNDRV_PCM_FMTBIT_S16_LE +@@ -228,11 +227,10 @@ struct snd_pcm_ops { + #define SNDRV_PCM_FMTBIT_U20 SNDRV_PCM_FMTBIT_U20_BE + #endif + +-#define _SNDRV_PCM_SUBFMTBIT(fmt) BIT((__force int)SNDRV_PCM_SUBFORMAT_##fmt) +-#define SNDRV_PCM_SUBFMTBIT_STD _SNDRV_PCM_SUBFMTBIT(STD) +-#define SNDRV_PCM_SUBFMTBIT_MSBITS_MAX _SNDRV_PCM_SUBFMTBIT(MSBITS_MAX) +-#define SNDRV_PCM_SUBFMTBIT_MSBITS_20 _SNDRV_PCM_SUBFMTBIT(MSBITS_20) +-#define SNDRV_PCM_SUBFMTBIT_MSBITS_24 _SNDRV_PCM_SUBFMTBIT(MSBITS_24) ++#define SNDRV_PCM_SUBFMTBIT_STD (1U << SNDRV_PCM_SUBFORMAT_STD) ++#define SNDRV_PCM_SUBFMTBIT_MSBITS_MAX (1U << SNDRV_PCM_SUBFORMAT_MSBITS_MAX) ++#define SNDRV_PCM_SUBFMTBIT_MSBITS_20 (1U << SNDRV_PCM_SUBFORMAT_MSBITS_20) ++#define SNDRV_PCM_SUBFMTBIT_MSBITS_24 (1U << SNDRV_PCM_SUBFORMAT_MSBITS_24) + + struct snd_pcm_file { + struct snd_pcm_substream *substream; +@@ -1515,7 +1513,7 @@ int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream, + */ + static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format) + { +- return 1ULL << (__force int) pcm_format; ++ return 1ULL << pcm_format; + } + + /** +@@ -1523,9 +1521,7 @@ static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format) + * @f: the iterator variable in snd_pcm_format_t type + */ + #define pcm_for_each_format(f) \ +- for ((f) = SNDRV_PCM_FORMAT_FIRST; \ +- (__force int)(f) <= (__force int)SNDRV_PCM_FORMAT_LAST; \ +- (f) = (__force snd_pcm_format_t)((__force int)(f) + 1)) ++ for ((f) = SNDRV_PCM_FORMAT_FIRST; (f) <= SNDRV_PCM_FORMAT_LAST; (f)++) + + /* printk helpers */ + #define pcm_err(pcm, fmt, args...) \ +diff --git a/include/sound/pcm_params.h b/include/sound/pcm_params.h +--- a/include/sound/pcm_params.h ++++ b/include/sound/pcm_params.h +@@ -71,7 +71,7 @@ static inline void snd_mask_set(struct snd_mask *mask, unsigned int val) + static inline void snd_mask_set_format(struct snd_mask *mask, + snd_pcm_format_t format) + { +- snd_mask_set(mask, (__force unsigned int)format); ++ snd_mask_set(mask, format); + } + + static inline void snd_mask_reset(struct snd_mask *mask, unsigned int val) +@@ -132,7 +132,7 @@ static inline int snd_mask_test(const struct snd_mask *mask, unsigned int val) + static inline int snd_mask_test_format(const struct snd_mask *mask, + snd_pcm_format_t format) + { +- return snd_mask_test(mask, (__force unsigned int)format); ++ return snd_mask_test(mask, format); + } + + static inline int snd_mask_single(const struct snd_mask *mask) +@@ -302,8 +302,7 @@ static inline int snd_interval_eq(const struct snd_interval *i1, const struct sn + */ + static inline snd_pcm_access_t params_access(const struct snd_pcm_hw_params *p) + { +- return (__force snd_pcm_access_t)snd_mask_min(hw_param_mask_c(p, +- SNDRV_PCM_HW_PARAM_ACCESS)); ++ return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_ACCESS)); + } + + /** +@@ -312,8 +311,7 @@ static inline snd_pcm_access_t params_access(const struct snd_pcm_hw_params *p) + */ + static inline snd_pcm_format_t params_format(const struct snd_pcm_hw_params *p) + { +- return (__force snd_pcm_format_t)snd_mask_min(hw_param_mask_c(p, +- SNDRV_PCM_HW_PARAM_FORMAT)); ++ return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_FORMAT)); + } + + /** +@@ -323,8 +321,7 @@ static inline snd_pcm_format_t params_format(const struct snd_pcm_hw_params *p) + static inline snd_pcm_subformat_t + params_subformat(const struct snd_pcm_hw_params *p) + { +- return (__force snd_pcm_subformat_t)snd_mask_min(hw_param_mask_c(p, +- SNDRV_PCM_HW_PARAM_SUBFORMAT)); ++ return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_SUBFORMAT)); + } + + /** +diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h +--- a/include/sound/rawmidi.h ++++ b/include/sound/rawmidi.h +@@ -176,8 +176,9 @@ int snd_rawmidi_proceed(struct snd_rawmidi_substream *substream); + /* main midi functions */ + + int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info); +-int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, +- int mode, struct snd_rawmidi_file *rfile); ++int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, ++ int mode, struct snd_rawmidi_file *rfile, ++ int depth); + int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile); + int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params); +@@ -191,6 +192,15 @@ long snd_rawmidi_kernel_read(struct snd_rawmidi_substream *substream, + long snd_rawmidi_kernel_write(struct snd_rawmidi_substream *substream, + const unsigned char *buf, long count); + ++/* non-nested version */ ++static inline int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, ++ int subdevice, ++ int mode, ++ struct snd_rawmidi_file *rfile) ++{ ++ return snd_rawmidi_kernel_open_nested(rmidi, subdevice, mode, rfile, 0); ++} ++ + /* set up the tied devices */ + static inline void snd_rawmidi_tie_devices(struct snd_rawmidi *r1, + struct snd_rawmidi *r2) +diff --git a/include/sound/sdca_asoc.h b/include/sound/sdca_asoc.h +--- a/include/sound/sdca_asoc.h ++++ b/include/sound/sdca_asoc.h +@@ -25,6 +25,8 @@ struct snd_soc_dai_driver; + struct snd_soc_dai_ops; + struct snd_soc_dapm_route; + struct snd_soc_dapm_widget; ++struct snd_soc_pcm_stream; ++struct sdca_entity; + + /* convenient macro to handle the mono volume in 7.8 fixed format representation */ + #define SDCA_SINGLE_Q78_TLV(xname, xreg, xmin, xmax, xstep, tlv_array) \ +@@ -82,6 +84,11 @@ int sdca_asoc_populate_component(struct device *dev, + struct snd_soc_dai_driver **dai_drv, int *num_dai_drv, + const struct snd_soc_dai_ops *ops); + ++int sdca_asoc_populate_rate_format(struct device *dev, ++ struct sdca_function_data *function, ++ struct sdca_entity *entity, ++ struct snd_soc_pcm_stream *stream); ++ + int sdca_asoc_set_constraints(struct device *dev, struct regmap *regmap, + struct sdca_function_data *function, + struct snd_pcm_substream *substream, +@@ -100,9 +107,9 @@ int sdca_asoc_q78_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + int sdca_asoc_q78_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +-int sdca_asoc_pde_poll_actual_ps(struct device *dev, struct regmap *regmap, ++int sdca_asoc_pde_poll_actual_ps(struct regmap *regmap, + int function_id, int entity_id, +- int from_ps, int to_ps, +- const struct sdca_pde_delay *pde_delays, +- int num_delays); ++ int from_ps, int to_ps, ++ const struct sdca_pde_delay *pde_delays, ++ int num_delays); + #endif // __SDCA_ASOC_H__ +diff --git a/include/sound/sdca_fdl.h b/include/sound/sdca_fdl.h +--- a/include/sound/sdca_fdl.h ++++ b/include/sound/sdca_fdl.h +@@ -83,6 +83,10 @@ static inline int sdca_fdl_alloc_state(struct sdca_interrupt *interrupt) + return 0; + } + ++static inline void sdca_fdl_free_state(struct sdca_interrupt *interrupt) ++{ ++} ++ + static inline int sdca_fdl_process(struct sdca_interrupt *interrupt) + { + return 0; +diff --git a/include/sound/sdca_function.h b/include/sound/sdca_function.h +--- a/include/sound/sdca_function.h ++++ b/include/sound/sdca_function.h +@@ -298,7 +298,7 @@ enum sdca_xu_controls { + }; + + /** +- * enum sdca_set_index_range - Column definitions UMP SetIndex ++ * enum sdca_fdl_set_index_range - Column definitions UMP SetIndex + */ + enum sdca_fdl_set_index_range { + SDCA_FDL_SET_INDEX_SET_NUMBER = 0, +@@ -803,6 +803,8 @@ struct sdca_control_range { + * @mode: Access mode of the Control. + * @layers: Bitmask of access layers of the Control. + * @deferrable: Indicates if the access to the Control can be deferred. ++ * @is_volatile: Indicates the Control registers are forced to be treated ++ * as volatile. + * @has_default: Indicates the Control has a default value to be written. + * @has_reset: Indicates the Control has a defined reset value. + * @has_fixed: Indicates the Control only supports a single value. +@@ -1116,7 +1118,6 @@ struct sdca_entity_ge { + + /** + * struct sdca_entity_hide - information specific to HIDE Entities +- * @hid: HID device structure + * @num_hidtx_ids: number of HIDTx Report ID + * @num_hidrx_ids: number of HIDRx Report ID + * @hidtx_ids: HIDTx Report ID +@@ -1127,11 +1128,8 @@ struct sdca_entity_ge { + * within this Device + * @max_delay: the maximum time in microseconds allowed for the Device + * to change the ownership from Device to Host +- * @hid_report_desc: HID Report Descriptor for the HIDE Entity +- * @hid_desc: HID descriptor for the HIDE Entity + */ + struct sdca_entity_hide { +- struct hid_device *hid; + unsigned int *hidtx_ids; + unsigned int *hidrx_ids; + int num_hidtx_ids; +@@ -1139,12 +1137,10 @@ struct sdca_entity_hide { + unsigned int af_number_list[SDCA_MAX_FUNCTION_COUNT]; + unsigned int hide_reside_function_num; + unsigned int max_delay; +- unsigned char *hid_report_desc; +- struct hid_descriptor hid_desc; + }; + + /** +- * enum sdca_xu_reset_machanism - SDCA FDL Resets ++ * enum sdca_xu_reset_mechanism - SDCA FDL Resets + */ + enum sdca_xu_reset_mechanism { + SDCA_XU_RESET_FUNCTION = 0x0, +@@ -1401,6 +1397,16 @@ struct sdca_fdl_data { + int num_sets; + }; + ++/** ++ * struct sdca_function_hid - information about a function's HID descriptors ++ * @report_desc: HID Report Descriptor for the HID Function ++ * @desc: HID descriptor for the HID Function ++ */ ++struct sdca_function_hid { ++ unsigned char *report_desc; ++ struct hid_descriptor desc; ++}; ++ + /** + * struct sdca_function_data - top-level information for one SDCA function + * @desc: Pointer to short descriptor from initial parsing. +@@ -1415,6 +1421,7 @@ struct sdca_fdl_data { + * @reset_max_delay: Maximum Function reset delay in microseconds, before an + * error should be reported. + * @fdl_data: FDL data for this Function, if available. ++ * @hid: HID data for this Function, if available. + */ + struct sdca_function_data { + struct sdca_function_desc *desc; +@@ -1430,6 +1437,10 @@ struct sdca_function_data { + unsigned int reset_max_delay; + + struct sdca_fdl_data fdl_data; ++ ++ union { ++ struct sdca_function_hid hid; ++ }; + }; + + static inline u32 sdca_range(struct sdca_control_range *range, +@@ -1451,8 +1462,7 @@ static inline u32 sdca_range_search(struct sdca_control_range *range, + return 0; + } + +-int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, +- struct sdca_function_data *function); ++int sdca_parse_function(struct device *dev, struct sdca_function_data *function); + + const char *sdca_find_terminal_name(enum sdca_terminal_type type); + +@@ -1469,5 +1479,7 @@ struct sdca_control_range *sdca_selector_find_range(struct device *dev, + struct sdca_cluster *sdca_id_find_cluster(struct device *dev, + struct sdca_function_data *function, + const int id); ++struct sdca_entity *sdca_find_entity_by_label(struct sdca_function_data *function, ++ const char *entity_label); + + #endif +diff --git a/include/sound/sdca_hid.h b/include/sound/sdca_hid.h +--- a/include/sound/sdca_hid.h ++++ b/include/sound/sdca_hid.h +@@ -16,18 +16,21 @@ struct sdca_interrupt; + + #if IS_ENABLED(CONFIG_SND_SOC_SDCA_HID) + +-int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, +- struct sdca_entity *entity); ++int sdca_add_hid_device(struct sdca_interrupt *interrupt); ++void sdca_destroy_hid_device(struct sdca_interrupt *interrupt); + int sdca_hid_process_report(struct sdca_interrupt *interrupt); + + #else + +-static inline int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, +- struct sdca_entity *entity) ++static inline int sdca_add_hid_device(struct sdca_interrupt *interrupt) + { + return 0; + } + ++static inline void sdca_destroy_hid_device(struct sdca_interrupt *interrupt) ++{ ++} ++ + static inline int sdca_hid_process_report(struct sdca_interrupt *interrupt) + { + return 0; +diff --git a/include/sound/seq_virmidi.h b/include/sound/seq_virmidi.h +--- a/include/sound/seq_virmidi.h ++++ b/include/sound/seq_virmidi.h +@@ -46,7 +46,6 @@ struct snd_virmidi_dev { + int client; /* created/attached client */ + int port; /* created/attached port */ + unsigned int flags; /* SNDRV_VIRMIDI_* */ +- rwlock_t filelist_lock; + struct rw_semaphore filelist_sem; + struct list_head filelist; + }; +diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h +--- a/include/sound/simple_card_utils.h ++++ b/include/sound/simple_card_utils.h +@@ -181,7 +181,7 @@ void simple_util_canonicalize_platform(struct snd_soc_dai_link_component *platfo + void simple_util_canonicalize_cpu(struct snd_soc_dai_link_component *cpus, + int is_single_links); + +-void simple_util_clean_reference(struct snd_soc_card *card); ++void simple_util_clean_reference(struct simple_util_priv *priv); + + void simple_util_parse_convert(struct device_node *np, char *prefix, + struct simple_util_data *data); +@@ -189,18 +189,37 @@ bool simple_util_is_convert_required(const struct simple_util_data *data); + + int simple_util_get_sample_fmt(struct simple_util_data *data); + +-int simple_util_parse_routing(struct snd_soc_card *card, +- char *prefix); +-int simple_util_parse_widgets(struct snd_soc_card *card, +- char *prefix); +-int simple_util_parse_pin_switches(struct snd_soc_card *card, +- char *prefix); ++int simple_util_parse_property(struct simple_util_priv *priv, ++ int (*func)(struct snd_soc_card *card, const char *propname), ++ char *prefix, char *property); ++static inline int simple_util_parse_routing(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_audio_routing, ++ prefix, "routing"); ++} ++ ++static inline int simple_util_parse_widgets(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_audio_simple_widgets, ++ prefix, "widgets"); ++} ++ ++static inline int simple_util_parse_pin_switches(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_pin_switches, ++ prefix, "pin-switches"); ++} ++ ++static inline int simple_util_parse_aux_devs(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_aux_devs, ++ prefix, "aux-devs"); ++} + + int simple_util_init_jack(struct snd_soc_card *card, + struct simple_util_jack *sjack, + int is_hp, char *prefix, char *pin); +-int simple_util_init_aux_jacks(struct simple_util_priv *priv, +- char *prefix); ++int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix); + int simple_util_init_priv(struct simple_util_priv *priv, + struct link_info *li); + void simple_util_remove(struct platform_device *pdev); +diff --git a/include/sound/soc-acpi.h b/include/sound/soc-acpi.h +--- a/include/sound/soc-acpi.h ++++ b/include/sound/soc-acpi.h +@@ -57,7 +57,7 @@ static inline struct snd_soc_acpi_mach *snd_soc_acpi_codec_list(void *arg) + #endif + + /** +- * snd_soc_acpi_mach_params: interface for machine driver configuration ++ * struct snd_soc_acpi_mach_params - interface for machine driver configuration + * + * @acpi_ipc_irq_index: used for BYT-CR detection + * @platform: string used for HDAudio codec support +@@ -93,7 +93,7 @@ struct snd_soc_acpi_mach_params { + }; + + /** +- * snd_soc_acpi_endpoint - endpoint descriptor ++ * struct snd_soc_acpi_endpoint - endpoint descriptor + * @num: endpoint number (mandatory, unique per device) + * @aggregated: 0 (independent) or 1 (logically grouped) + * @group_position: zero-based order (only when @aggregated is 1) +@@ -107,7 +107,7 @@ struct snd_soc_acpi_endpoint { + }; + + /** +- * snd_soc_acpi_adr_device - descriptor for _ADR-enumerated device ++ * struct snd_soc_acpi_adr_device - descriptor for _ADR-enumerated device + * @adr: 64 bit ACPI _ADR value + * @num_endpoints: number of endpoints for this device + * @endpoints: array of endpoints +@@ -121,7 +121,7 @@ struct snd_soc_acpi_adr_device { + }; + + /** +- * snd_soc_acpi_link_adr - ACPI-based list of _ADR enumerated devices ++ * struct snd_soc_acpi_link_adr - ACPI-based list of _ADR enumerated devices + * @mask: one bit set indicates the link this list applies to + * @num_adr: ARRAY_SIZE of devices + * @adr_d: array of devices +@@ -167,8 +167,8 @@ struct snd_soc_acpi_link_adr { + #define SND_SOC_ACPI_TPLG_INTEL_CODEC_NAME BIT(4) + + /** +- * snd_soc_acpi_mach: ACPI-based machine descriptor. Most of the fields are +- * related to the hardware, except for the firmware and topology file names. ++ * struct snd_soc_acpi_mach - ACPI-based machine descriptor. Most of the fields ++ * are related to the hardware, except for the firmware and topology file names. + * A platform supported by legacy and Sound Open Firmware (SOF) would expose + * all firmware/topology related fields. + * +@@ -192,6 +192,7 @@ struct snd_soc_acpi_link_adr { + * the initial selection in the snd_soc_acpi_mach table. + * @pdata: intended for platform data or machine specific-ops. This structure + * is not constant since this field may be updated at run-time ++ * @mach_params: machine driver configuration + * @sof_tplg_filename: Sound Open Firmware topology file name, if enabled + * @tplg_quirk_mask: quirks to select different topology files dynamically + * @get_function_tplg_files: This is an optional callback, if specified then instead of +@@ -199,11 +200,11 @@ struct snd_soc_acpi_link_adr { + * files to be loaded. + * Return value: The number of the files or negative ERRNO. 0 means that the single topology + * file should be used, no function topology split can be used on the machine. +- * @card: the pointer of the card +- * @mach: the pointer of the machine driver +- * @prefix: the prefix of the topology file name. Typically, it is the path. +- * @tplg_files: the pointer of the array of the topology file names. +- * @best_effort: ignore non supported links and try to build the card in best effort ++ * card: the pointer of the card ++ * mach: the pointer of the machine driver ++ * prefix: the prefix of the topology file name. Typically, it is the path. ++ * tplg_files: the pointer of the array of the topology file names. ++ * best_effort: ignore non supported links and try to build the card in best effort + * with supported links + */ + /* Descriptor for SST ASoC machine driver */ +diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h +--- a/include/sound/soc-component.h ++++ b/include/sound/soc-component.h +@@ -253,6 +253,9 @@ struct snd_soc_component { + void *mark_pm; + + struct dentry *debugfs_root; ++ ++ /* Component private data */ ++ void *priv; + }; + + #define for_each_component_dais(component, dai)\ +@@ -283,6 +286,14 @@ static inline int snd_soc_component_cache_sync( + return regcache_sync(component->regmap); + } + ++struct snd_soc_component *snd_soc_component_alloc(struct device *dev); ++ ++void snd_soc_component_set_name(struct snd_soc_component *component, const char *name); ++const char *snd_soc_component_name(struct snd_soc_component *component); ++ ++void snd_soc_component_set_priv(struct snd_soc_component *component, void *priv); ++void *snd_soc_component_to_priv(struct snd_soc_component *component); ++ + void snd_soc_component_set_aux(struct snd_soc_component *component, + struct snd_soc_aux_dev *aux); + int snd_soc_component_init(struct snd_soc_component *component); +diff --git a/include/sound/soc.h b/include/sound/soc.h +--- a/include/sound/soc.h ++++ b/include/sound/soc.h +@@ -447,15 +447,16 @@ static inline int snd_soc_resume(struct device *dev) + } + #endif + int snd_soc_poweroff(struct device *dev); +-int snd_soc_component_initialize(struct snd_soc_component *component, +- const struct snd_soc_component_driver *driver, +- struct device *dev); +-int snd_soc_add_component(struct snd_soc_component *component, +- struct snd_soc_dai_driver *dai_drv, +- int num_dai); +-int snd_soc_register_component(struct device *dev, ++int snd_soc_register_component_c(struct snd_soc_component *component, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, int num_dai); ++int snd_soc_register_component_d(struct device *dev, ++ const struct snd_soc_component_driver *component_driver, ++ struct snd_soc_dai_driver *dai_drv, int num_dai); ++#define snd_soc_register_component(x, ...) _Generic((x), \ ++struct device * : snd_soc_register_component_d, \ ++struct snd_soc_component * : snd_soc_register_component_c)(x, __VA_ARGS__) ++ + int devm_snd_soc_register_component(struct device *dev, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, int num_dai); +diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h +--- a/include/sound/soc_sdw_utils.h ++++ b/include/sound/soc_sdw_utils.h +@@ -44,6 +44,18 @@ + + struct asoc_sdw_codec_info; + ++struct asoc_sdw_mc_private { ++ struct snd_soc_card card; ++ struct snd_soc_jack sdw_headset; ++ struct device *headset_codec_dev; /* only one headset per card */ ++ struct device *amp_dev1, *amp_dev2; ++ bool append_dai_type; ++ bool ignore_internal_dmic; ++ void *private; ++ unsigned long mc_quirk; ++ int codec_info_list_count; ++}; ++ + struct asoc_sdw_dai_info { + const bool direction[2]; /* playback & capture support */ + const char *codec_name; +@@ -88,25 +100,13 @@ struct asoc_sdw_codec_info { + + int (*codec_card_late_probe)(struct snd_soc_card *card); + +- int (*count_sidecar)(struct snd_soc_card *card, ++ int (*count_sidecar)(struct asoc_sdw_mc_private *ctx, + int *num_dais, int *num_devs); + int (*add_sidecar)(struct snd_soc_card *card, + struct snd_soc_dai_link **dai_links, + struct snd_soc_codec_conf **codec_conf); + }; + +-struct asoc_sdw_mc_private { +- struct snd_soc_card card; +- struct snd_soc_jack sdw_headset; +- struct device *headset_codec_dev; /* only one headset per card */ +- struct device *amp_dev1, *amp_dev2; +- bool append_dai_type; +- bool ignore_internal_dmic; +- void *private; +- unsigned long mc_quirk; +- int codec_info_list_count; +-}; +- + struct asoc_sdw_endpoint { + struct list_head list; + +@@ -182,7 +182,8 @@ struct asoc_sdw_dailink *asoc_sdw_find_dailink(struct asoc_sdw_dailink *dailinks + const struct snd_soc_acpi_endpoint *new); + int asoc_sdw_get_dai_type(u32 type); + +-int asoc_sdw_parse_sdw_endpoints(struct snd_soc_card *card, ++int asoc_sdw_parse_sdw_endpoints(struct device *dev, ++ struct asoc_sdw_mc_private *ctx, + struct snd_soc_aux_dev *soc_aux, + struct asoc_sdw_dailink *soc_dais, + struct asoc_sdw_endpoint *soc_ends, +@@ -235,7 +236,7 @@ int asoc_sdw_es9356_amp_init(struct snd_soc_card *card, + int asoc_sdw_es9356_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); + + /* CS AMP support */ +-int asoc_sdw_bridge_cs35l56_count_sidecar(struct snd_soc_card *card, ++int asoc_sdw_bridge_cs35l56_count_sidecar(struct asoc_sdw_mc_private *ctx, + int *num_dais, int *num_devs); + int asoc_sdw_bridge_cs35l56_add_sidecar(struct snd_soc_card *card, + struct snd_soc_dai_link **dai_links, +diff --git a/include/sound/sof/dai-amd.h b/include/sound/sof/dai-amd.h +--- a/include/sound/sof/dai-amd.h ++++ b/include/sound/sof/dai-amd.h +@@ -18,6 +18,7 @@ struct sof_ipc_dai_acp_params { + uint32_t fsync_rate; /* FSYNC frequency in Hz */ + uint32_t tdm_slots; + uint32_t tdm_mode; ++ uint32_t format; + } __packed; + + /* ACPDMIC Configuration Request - SOF_IPC_DAI_AMD_CONFIG */ +diff --git a/include/sound/sof/dai.h b/include/sound/sof/dai.h +--- a/include/sound/sof/dai.h ++++ b/include/sound/sof/dai.h +@@ -90,6 +90,8 @@ enum sof_ipc_dai_type { + SOF_DAI_AMD_HS_VIRTUAL, /**< AMD ACP HS VIRTUAL */ + SOF_DAI_IMX_MICFIL, /** < i.MX MICFIL PDM */ + SOF_DAI_AMD_SDW, /**< AMD ACP SDW */ ++ SOF_DAI_INTEL_UAOL, /**< Intel UAOL */ ++ SOF_DAI_AMD_I2S, /**< AMD ACP I2S */ + }; + + /* general purpose DAI configuration */ +@@ -121,6 +123,7 @@ struct sof_ipc_dai_config { + struct sof_ipc_dai_mtk_afe_params afe; + struct sof_ipc_dai_micfil_params micfil; + struct sof_ipc_dai_acp_sdw_params acp_sdw; ++ struct sof_ipc_dai_acp_params acp_i2s; + }; + } __packed; + +diff --git a/include/sound/sof/header.h b/include/sound/sof/header.h +--- a/include/sound/sof/header.h ++++ b/include/sound/sof/header.h +@@ -179,7 +179,7 @@ struct sof_ipc_compound_hdr { + uint32_t count; /**< count of 0 means end of compound sequence */ + } __packed; + +-/** ++/* + * OOPS header architecture specific data. + */ + struct sof_ipc_dsp_oops_arch_hdr { +@@ -187,7 +187,7 @@ struct sof_ipc_dsp_oops_arch_hdr { + uint32_t totalsize; /* Total size of oops message */ + } __packed; + +-/** ++/* + * OOPS header platform specific data. + */ + struct sof_ipc_dsp_oops_plat_hdr { +diff --git a/include/sound/sof/ipc4/header.h b/include/sound/sof/ipc4/header.h +--- a/include/sound/sof/ipc4/header.h ++++ b/include/sound/sof/ipc4/header.h +@@ -187,6 +187,10 @@ enum sof_ipc4_pipeline_state { + #define SOF_IPC4_GLB_PIPE_EXT_CORE_ID_MASK GENMASK(23, 20) + #define SOF_IPC4_GLB_PIPE_EXT_CORE_ID(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_CORE_ID_SHIFT) + ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_SHIFT 29 ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_MASK BIT(29) ++#define SOF_IPC4_GLB_PIPE_PAYLOAD(x) ((x) << SOF_IPC4_GLB_PIPE_PAYLOAD_SHIFT) ++ + /* pipeline set state ipc msg */ + #define SOF_IPC4_GLB_PIPE_STATE_ID_SHIFT 16 + #define SOF_IPC4_GLB_PIPE_STATE_ID_MASK GENMASK(23, 16) +@@ -536,12 +540,63 @@ enum sof_ipc4_notification_type { + SOF_IPC4_NOTIFY_TYPE_LAST, + }; + ++enum sof_ipc4_resource_type { ++ SOF_IPC4_MODULE_INSTANCE, ++ SOF_IPC4_PIPELINE, ++ SOF_IPC4_GATEWAY, ++ SOF_IPC4_EDF_TASK, ++ SOF_IPC4_INVALID_RESOURCE_TYPE, ++}; ++ ++enum sof_ipc4_event_type { ++ /* Underrun detected by the Mixer */ ++ SOF_IPC4_MIXER_UNDERRUN_DETECTED = 1, ++ /* Error caught during data processing */ ++ SOF_IPC4_PROCESS_DATA_ERROR = 3, ++ /* Underrun detected by gateway. */ ++ SOF_IPC4_GATEWAY_UNDERRUN_DETECTED = 6, ++ /* Overrun detected by gateway */ ++ SOF_IPC4_GATEWAY_OVERRUN_DETECTED, ++}; ++ ++/** ++ * struct sof_ipc4_process_data_error_event_data - process data error event payload ++ * @error_code: Error code returned by data processing function ++ */ ++struct sof_ipc4_process_data_error_event_data { ++ uint32_t error_code; ++}; ++ ++/** ++ * struct sof_ipc4_mixer_underrun_event_data - mixer underrun event payload ++ * @eos_flag: Indicates EndOfStream ++ * @data_mixed: Data processed by module (in bytes) ++ * @expected_data_mixed: Expected data to be processed (in bytes) ++ */ ++struct sof_ipc4_mixer_underrun_event_data { ++ uint32_t eos_flag; ++ uint32_t data_mixed; ++ uint32_t expected_data_mixed; ++}; ++ ++/** ++ * union sof_ipc4_resource_event_data - resource event specific payload ++ * @dws: Raw event data payload as six dwords ++ * @process_data_error: SOF_IPC4_PROCESS_DATA_ERROR payload ++ * @mixer_underrun: SOF_IPC4_MIXER_UNDERRUN_DETECTED payload ++ */ ++union sof_ipc4_resource_event_data { ++ uint32_t dws[6]; ++ struct sof_ipc4_process_data_error_event_data process_data_error; ++ struct sof_ipc4_mixer_underrun_event_data mixer_underrun; ++}; ++ + struct sof_ipc4_notify_resource_data { + uint32_t resource_type; + uint32_t resource_id; + uint32_t event_type; + uint32_t reserved; +- uint32_t data[6]; ++ union sof_ipc4_resource_event_data data; + } __packed __aligned(4); + + #define SOF_IPC4_DEBUG_DESCRIPTOR_SIZE 12 /* 3 x u32 */ +@@ -654,11 +709,81 @@ enum sof_ipc4_mod_init_ext_obj_id { + SOF_IPC4_MOD_INIT_DATA_ID_MAX = SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA, + }; + +-/* DP module memory configuration data object for ext_init object array */ ++/* DP module memory configuration data object for object array */ + struct sof_ipc4_mod_init_ext_dp_memory_data { ++ u32 domain_id; /* userspace domain ID */ ++ u32 stack_bytes; /* required stack size in bytes */ ++ u32 heap_bytes; /* required heap size in bytes */ ++} __packed __aligned(4); ++ ++/* ++ * This set of macros are very similar to the set above, but these are ++ * for building payload to SOF_IPC4_GLB_CREATE_PIPELINE message. ++ * ++ * Macros for creating struct sof_ipc4_glb_pipe_payload payload with ++ * its associated data. struct sof_ipc4_glb_pipe_payload should be the ++ * first piece of payload following SOF_IPC4_GLB_CREATE_PIPELINE msg, ++ * and its existence is indicated with SOF_IPC4_GLB_PIPE_PAYLOAD bit. ++ * ++ * The macros below apply to sof_ipc4_glb_pipe_payload.word0 ++ */ ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS_SHIFT 0 ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS_MASK GENMASK(23, 0) ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS(x) ((x) << SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS_SHIFT) ++ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_SHIFT 24 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_MASK BIT(24) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_SHIFT) ++ ++struct sof_ipc4_glb_pipe_payload { ++ u32 word0; ++ u32 rsvd1; ++ u32 rsvd2; ++} __packed __aligned(4); ++ ++/* ++ * SOF_IPC4_GLB_CREATE_PIPELINE payload may be followed by arbitrary ++ * number of object array objects. SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY-bit ++ * indicates that an array object follows struct ++ * sof_ipc4_glb_pipe_payload. ++ * ++ * The object header's SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST-bit in struct ++ * sof_ipc4_glb_pipe_ext_object indicates if the array is continued ++ * with another object. The header has also fields to identify the ++ * object, SOF_IPC4_GLB_PIPE_EXT_OBJ_ID, and to indicate the object's ++ * size in 32-bit words, SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS, not ++ * including the header itself. ++ * ++ * The macros below apply to sof_ipc4_glb_pipe_ext_object.header ++ */ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_SHIFT 0 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_MASK BIT(0) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_SHIFT) ++ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ID_SHIFT 1 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ID_MASK GENMASK(15, 1) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ID(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_ID_SHIFT) ++ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS_SHIFT 16 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS_MASK GENMASK(31, 16) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS_SHIFT) ++ ++struct sof_ipc4_glb_pipe_ext_object { ++ u32 header; ++ u32 data[]; ++} __packed __aligned(4); ++ ++enum sof_ipc4_glb_pipe_ext_obj_id { ++ SOF_IPC4_GLB_PIPE_DATA_ID_INVALID = 0, ++ SOF_IPC4_GLB_PIPE_DATA_ID_MEM_DATA, ++ SOF_IPC4_GLB_PIPE_DATA_ID_MAX = SOF_IPC4_GLB_PIPE_DATA_ID_MEM_DATA, ++}; ++ ++/* Pipeline memory configuration data object for ext_init object array */ ++struct sof_ipc4_glb_pipe_ext_obj_memory_data { + u32 domain_id; /* userspace domain ID */ +- u32 stack_bytes; /* stack size in bytes, 0 means default size */ +- u32 heap_bytes; /* stack size in bytes, 0 means default size */ ++ u32 stack_bytes; /* stack size in bytes */ ++ u32 heap_bytes; /* heap size in bytes */ + } __packed __aligned(4); + + /** @}*/ +diff --git a/include/sound/tas2781-dsp.h b/include/sound/tas2781-dsp.h +--- a/include/sound/tas2781-dsp.h ++++ b/include/sound/tas2781-dsp.h +@@ -201,6 +201,11 @@ struct tasdevice_rca { + int ncfgs; + struct tasdevice_config_info **cfg_info; + int profile_cfg_id; ++ /* ++ * Used among SmartAMP for PDM microphone recording or IV data ++ * capture. ++ */ ++ int capture_profile_id; + /* + * Since version 0x105, the keyword 'init' was introduced into the + * profile, which is used for chip initialization, particularly to +@@ -222,7 +227,7 @@ void tasdevice_calbin_remove(void *context); + int tasdevice_select_tuningprm_cfg(void *context, int prm, + int cfg_no, int rca_conf_no); + int tasdevice_prmg_load(void *context, int prm_no); +-void tasdevice_tuning_switch(void *context, int state); ++void tasdevice_tuning_switch(void *context, int state, bool is_cap); + int tas2781_load_calibration(void *context, char *file_name, + unsigned short i); + +diff --git a/include/sound/tlv320aic32x4.h b/include/sound/tlv320aic32x4.h +deleted file mode 100644 +--- a/include/sound/tlv320aic32x4.h ++++ /dev/null +@@ -1,43 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-only */ +-/* +- * tlv320aic32x4.h -- TLV320AIC32X4 Soc Audio driver platform data +- * +- * Copyright 2011 Vista Silicon S.L. +- * +- * Author: Javier Martin +- */ +- +-#ifndef _AIC32X4_PDATA_H +-#define _AIC32X4_PDATA_H +- +-#define AIC32X4_PWR_MICBIAS_2075_LDOIN 0x00000001 +-#define AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE 0x00000002 +-#define AIC32X4_PWR_AIC32X4_LDO_ENABLE 0x00000004 +-#define AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36 0x00000008 +-#define AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED 0x00000010 +- +-#define AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K 0x00000001 +-#define AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K 0x00000002 +- +-/* GPIO API */ +-#define AIC32X4_MFPX_DEFAULT_VALUE 0xff +- +-#define AIC32X4_MFP1_DIN_DISABLED 0 +-#define AIC32X4_MFP1_DIN_ENABLED 0x2 +-#define AIC32X4_MFP1_GPIO_IN 0x4 +- +-#define AIC32X4_MFP2_GPIO_OUT_LOW 0x0 +-#define AIC32X4_MFP2_GPIO_OUT_HIGH 0x1 +- +-#define AIC32X4_MFP_GPIO_ENABLED 0x4 +- +-#define AIC32X4_MFP5_GPIO_DISABLED 0x0 +-#define AIC32X4_MFP5_GPIO_INPUT 0x8 +-#define AIC32X4_MFP5_GPIO_OUTPUT 0xc +-#define AIC32X4_MFP5_GPIO_OUT_LOW 0x0 +-#define AIC32X4_MFP5_GPIO_OUT_HIGH 0x1 +- +-struct aic32x4_setup_data { +- unsigned int gpio_func[5]; +-}; +-#endif +diff --git a/include/sound/wm8904.h b/include/sound/wm8904.h +--- a/include/sound/wm8904.h ++++ b/include/sound/wm8904.h +@@ -116,7 +116,7 @@ + #define WM8904_DRC_REGS 4 + #define WM8904_EQ_REGS 24 + +-/** ++/* + * DRC configurations are specified with a label and a set of register + * values to write (the enable bits will be ignored). At runtime an + * enumerated control will be presented for each DRC block allowing +@@ -131,7 +131,7 @@ struct wm8904_drc_cfg { + u16 regs[WM8904_DRC_REGS]; + }; + +-/** ++/* + * ReTune Mobile configurations are specified with a label, sample + * rate and set of values to write (the enable bits will be ignored). + * +diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h +--- a/include/uapi/sound/asequencer.h ++++ b/include/uapi/sound/asequencer.h +@@ -308,16 +308,6 @@ struct snd_seq_ump_event { + }; + }; + +-/* +- * bounce event - stored as variable size data +- */ +-struct snd_seq_event_bounce { +- int err; +- struct snd_seq_event event; +- /* external data follows here. */ +-}; +- +- + /* system information */ + struct snd_seq_system_info { + int queues; /* maximum queues count */ +@@ -348,10 +338,10 @@ struct snd_seq_running_info { + + + /* client types */ +-typedef int __bitwise snd_seq_client_type_t; +-#define NO_CLIENT ((__force snd_seq_client_type_t) 0) +-#define USER_CLIENT ((__force snd_seq_client_type_t) 1) +-#define KERNEL_CLIENT ((__force snd_seq_client_type_t) 2) ++typedef int snd_seq_client_type_t; ++#define NO_CLIENT 0 ++#define USER_CLIENT 1 ++#define KERNEL_CLIENT 2 + + /* event filter flags */ + #define SNDRV_SEQ_FILTER_BROADCAST (1U<<0) /* accept broadcast messages */ +diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h +--- a/include/uapi/sound/asound.h ++++ b/include/uapi/sound/asound.h +@@ -169,72 +169,72 @@ enum { + SNDRV_PCM_STREAM_LAST = SNDRV_PCM_STREAM_CAPTURE, + }; + +-typedef int __bitwise snd_pcm_access_t; +-#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED ((__force snd_pcm_access_t) 0) /* interleaved mmap */ +-#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED ((__force snd_pcm_access_t) 1) /* noninterleaved mmap */ +-#define SNDRV_PCM_ACCESS_MMAP_COMPLEX ((__force snd_pcm_access_t) 2) /* complex mmap */ +-#define SNDRV_PCM_ACCESS_RW_INTERLEAVED ((__force snd_pcm_access_t) 3) /* readi/writei */ +-#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED ((__force snd_pcm_access_t) 4) /* readn/writen */ ++typedef int snd_pcm_access_t; ++#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED 0 /* interleaved mmap */ ++#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED 1 /* noninterleaved mmap */ ++#define SNDRV_PCM_ACCESS_MMAP_COMPLEX 2 /* complex mmap */ ++#define SNDRV_PCM_ACCESS_RW_INTERLEAVED 3 /* readi/writei */ ++#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED 4 /* readn/writen */ + #define SNDRV_PCM_ACCESS_LAST SNDRV_PCM_ACCESS_RW_NONINTERLEAVED + +-typedef int __bitwise snd_pcm_format_t; +-#define SNDRV_PCM_FORMAT_S8 ((__force snd_pcm_format_t) 0) +-#define SNDRV_PCM_FORMAT_U8 ((__force snd_pcm_format_t) 1) +-#define SNDRV_PCM_FORMAT_S16_LE ((__force snd_pcm_format_t) 2) +-#define SNDRV_PCM_FORMAT_S16_BE ((__force snd_pcm_format_t) 3) +-#define SNDRV_PCM_FORMAT_U16_LE ((__force snd_pcm_format_t) 4) +-#define SNDRV_PCM_FORMAT_U16_BE ((__force snd_pcm_format_t) 5) +-#define SNDRV_PCM_FORMAT_S24_LE ((__force snd_pcm_format_t) 6) /* low three bytes */ +-#define SNDRV_PCM_FORMAT_S24_BE ((__force snd_pcm_format_t) 7) /* low three bytes */ +-#define SNDRV_PCM_FORMAT_U24_LE ((__force snd_pcm_format_t) 8) /* low three bytes */ +-#define SNDRV_PCM_FORMAT_U24_BE ((__force snd_pcm_format_t) 9) /* low three bytes */ ++typedef int snd_pcm_format_t; ++#define SNDRV_PCM_FORMAT_S8 0 ++#define SNDRV_PCM_FORMAT_U8 1 ++#define SNDRV_PCM_FORMAT_S16_LE 2 ++#define SNDRV_PCM_FORMAT_S16_BE 3 ++#define SNDRV_PCM_FORMAT_U16_LE 4 ++#define SNDRV_PCM_FORMAT_U16_BE 5 ++#define SNDRV_PCM_FORMAT_S24_LE 6 /* low three bytes */ ++#define SNDRV_PCM_FORMAT_S24_BE 7 /* low three bytes */ ++#define SNDRV_PCM_FORMAT_U24_LE 8 /* low three bytes */ ++#define SNDRV_PCM_FORMAT_U24_BE 9 /* low three bytes */ + /* + * For S32/U32 formats, 'msbits' hardware parameter is often used to deliver information about the + * available bit count in most significant bit. It's for the case of so-called 'left-justified' or + * `right-padding` sample which has less width than 32 bit. + */ +-#define SNDRV_PCM_FORMAT_S32_LE ((__force snd_pcm_format_t) 10) +-#define SNDRV_PCM_FORMAT_S32_BE ((__force snd_pcm_format_t) 11) +-#define SNDRV_PCM_FORMAT_U32_LE ((__force snd_pcm_format_t) 12) +-#define SNDRV_PCM_FORMAT_U32_BE ((__force snd_pcm_format_t) 13) +-#define SNDRV_PCM_FORMAT_FLOAT_LE ((__force snd_pcm_format_t) 14) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_FLOAT_BE ((__force snd_pcm_format_t) 15) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_FLOAT64_LE ((__force snd_pcm_format_t) 16) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_FLOAT64_BE ((__force snd_pcm_format_t) 17) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE ((__force snd_pcm_format_t) 18) /* IEC-958 subframe, Little Endian */ +-#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE ((__force snd_pcm_format_t) 19) /* IEC-958 subframe, Big Endian */ +-#define SNDRV_PCM_FORMAT_MU_LAW ((__force snd_pcm_format_t) 20) +-#define SNDRV_PCM_FORMAT_A_LAW ((__force snd_pcm_format_t) 21) +-#define SNDRV_PCM_FORMAT_IMA_ADPCM ((__force snd_pcm_format_t) 22) +-#define SNDRV_PCM_FORMAT_MPEG ((__force snd_pcm_format_t) 23) +-#define SNDRV_PCM_FORMAT_GSM ((__force snd_pcm_format_t) 24) +-#define SNDRV_PCM_FORMAT_S20_LE ((__force snd_pcm_format_t) 25) /* in four bytes, LSB justified */ +-#define SNDRV_PCM_FORMAT_S20_BE ((__force snd_pcm_format_t) 26) /* in four bytes, LSB justified */ +-#define SNDRV_PCM_FORMAT_U20_LE ((__force snd_pcm_format_t) 27) /* in four bytes, LSB justified */ +-#define SNDRV_PCM_FORMAT_U20_BE ((__force snd_pcm_format_t) 28) /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_S32_LE 10 ++#define SNDRV_PCM_FORMAT_S32_BE 11 ++#define SNDRV_PCM_FORMAT_U32_LE 12 ++#define SNDRV_PCM_FORMAT_U32_BE 13 ++#define SNDRV_PCM_FORMAT_FLOAT_LE 14 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_FLOAT_BE 15 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_FLOAT64_LE 16 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_FLOAT64_BE 17 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE 18 /* IEC-958 subframe, Little Endian */ ++#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE 19 /* IEC-958 subframe, Big Endian */ ++#define SNDRV_PCM_FORMAT_MU_LAW 20 ++#define SNDRV_PCM_FORMAT_A_LAW 21 ++#define SNDRV_PCM_FORMAT_IMA_ADPCM 22 ++#define SNDRV_PCM_FORMAT_MPEG 23 ++#define SNDRV_PCM_FORMAT_GSM 24 ++#define SNDRV_PCM_FORMAT_S20_LE 25 /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_S20_BE 26 /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_U20_LE 27 /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_U20_BE 28 /* in four bytes, LSB justified */ + /* gap in the numbering for a future standard linear format */ +-#define SNDRV_PCM_FORMAT_SPECIAL ((__force snd_pcm_format_t) 31) +-#define SNDRV_PCM_FORMAT_S24_3LE ((__force snd_pcm_format_t) 32) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S24_3BE ((__force snd_pcm_format_t) 33) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U24_3LE ((__force snd_pcm_format_t) 34) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U24_3BE ((__force snd_pcm_format_t) 35) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S20_3LE ((__force snd_pcm_format_t) 36) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S20_3BE ((__force snd_pcm_format_t) 37) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U20_3LE ((__force snd_pcm_format_t) 38) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U20_3BE ((__force snd_pcm_format_t) 39) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S18_3LE ((__force snd_pcm_format_t) 40) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S18_3BE ((__force snd_pcm_format_t) 41) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U18_3LE ((__force snd_pcm_format_t) 42) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U18_3BE ((__force snd_pcm_format_t) 43) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_G723_24 ((__force snd_pcm_format_t) 44) /* 8 samples in 3 bytes */ +-#define SNDRV_PCM_FORMAT_G723_24_1B ((__force snd_pcm_format_t) 45) /* 1 sample in 1 byte */ +-#define SNDRV_PCM_FORMAT_G723_40 ((__force snd_pcm_format_t) 46) /* 8 Samples in 5 bytes */ +-#define SNDRV_PCM_FORMAT_G723_40_1B ((__force snd_pcm_format_t) 47) /* 1 sample in 1 byte */ +-#define SNDRV_PCM_FORMAT_DSD_U8 ((__force snd_pcm_format_t) 48) /* DSD, 1-byte samples DSD (x8) */ +-#define SNDRV_PCM_FORMAT_DSD_U16_LE ((__force snd_pcm_format_t) 49) /* DSD, 2-byte samples DSD (x16), little endian */ +-#define SNDRV_PCM_FORMAT_DSD_U32_LE ((__force snd_pcm_format_t) 50) /* DSD, 4-byte samples DSD (x32), little endian */ +-#define SNDRV_PCM_FORMAT_DSD_U16_BE ((__force snd_pcm_format_t) 51) /* DSD, 2-byte samples DSD (x16), big endian */ +-#define SNDRV_PCM_FORMAT_DSD_U32_BE ((__force snd_pcm_format_t) 52) /* DSD, 4-byte samples DSD (x32), big endian */ ++#define SNDRV_PCM_FORMAT_SPECIAL 31 ++#define SNDRV_PCM_FORMAT_S24_3LE 32 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S24_3BE 33 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U24_3LE 34 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U24_3BE 35 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S20_3LE 36 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S20_3BE 37 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U20_3LE 38 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U20_3BE 39 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S18_3LE 40 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S18_3BE 41 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U18_3LE 42 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U18_3BE 43 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_G723_24 44 /* 8 samples in 3 bytes */ ++#define SNDRV_PCM_FORMAT_G723_24_1B 45 /* 1 sample in 1 byte */ ++#define SNDRV_PCM_FORMAT_G723_40 46 /* 8 Samples in 5 bytes */ ++#define SNDRV_PCM_FORMAT_G723_40_1B 47 /* 1 sample in 1 byte */ ++#define SNDRV_PCM_FORMAT_DSD_U8 48 /* DSD, 1-byte samples DSD (x8) */ ++#define SNDRV_PCM_FORMAT_DSD_U16_LE 49 /* DSD, 2-byte samples DSD (x16), little endian */ ++#define SNDRV_PCM_FORMAT_DSD_U32_LE 50 /* DSD, 4-byte samples DSD (x32), little endian */ ++#define SNDRV_PCM_FORMAT_DSD_U16_BE 51 /* DSD, 2-byte samples DSD (x16), big endian */ ++#define SNDRV_PCM_FORMAT_DSD_U32_BE 52 /* DSD, 4-byte samples DSD (x32), big endian */ + #define SNDRV_PCM_FORMAT_LAST SNDRV_PCM_FORMAT_DSD_U32_BE + #define SNDRV_PCM_FORMAT_FIRST SNDRV_PCM_FORMAT_S8 + +@@ -265,11 +265,11 @@ typedef int __bitwise snd_pcm_format_t; + #define SNDRV_PCM_FORMAT_U20 SNDRV_PCM_FORMAT_U20_BE + #endif + +-typedef int __bitwise snd_pcm_subformat_t; +-#define SNDRV_PCM_SUBFORMAT_STD ((__force snd_pcm_subformat_t) 0) +-#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX ((__force snd_pcm_subformat_t) 1) +-#define SNDRV_PCM_SUBFORMAT_MSBITS_20 ((__force snd_pcm_subformat_t) 2) +-#define SNDRV_PCM_SUBFORMAT_MSBITS_24 ((__force snd_pcm_subformat_t) 3) ++typedef int snd_pcm_subformat_t; ++#define SNDRV_PCM_SUBFORMAT_STD 0 ++#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX 1 ++#define SNDRV_PCM_SUBFORMAT_MSBITS_20 2 ++#define SNDRV_PCM_SUBFORMAT_MSBITS_24 3 + #define SNDRV_PCM_SUBFORMAT_LAST SNDRV_PCM_SUBFORMAT_MSBITS_24 + + #define SNDRV_PCM_INFO_MMAP 0x00000001 /* hardware supports mmap */ +@@ -303,16 +303,16 @@ typedef int __bitwise snd_pcm_subformat_t; + #define __SND_STRUCT_TIME64 + #endif + +-typedef int __bitwise snd_pcm_state_t; +-#define SNDRV_PCM_STATE_OPEN ((__force snd_pcm_state_t) 0) /* stream is open */ +-#define SNDRV_PCM_STATE_SETUP ((__force snd_pcm_state_t) 1) /* stream has a setup */ +-#define SNDRV_PCM_STATE_PREPARED ((__force snd_pcm_state_t) 2) /* stream is ready to start */ +-#define SNDRV_PCM_STATE_RUNNING ((__force snd_pcm_state_t) 3) /* stream is running */ +-#define SNDRV_PCM_STATE_XRUN ((__force snd_pcm_state_t) 4) /* stream reached an xrun */ +-#define SNDRV_PCM_STATE_DRAINING ((__force snd_pcm_state_t) 5) /* stream is draining */ +-#define SNDRV_PCM_STATE_PAUSED ((__force snd_pcm_state_t) 6) /* stream is paused */ +-#define SNDRV_PCM_STATE_SUSPENDED ((__force snd_pcm_state_t) 7) /* hardware is suspended */ +-#define SNDRV_PCM_STATE_DISCONNECTED ((__force snd_pcm_state_t) 8) /* hardware is disconnected */ ++typedef int snd_pcm_state_t; ++#define SNDRV_PCM_STATE_OPEN 0 /* stream is open */ ++#define SNDRV_PCM_STATE_SETUP 1 /* stream has a setup */ ++#define SNDRV_PCM_STATE_PREPARED 2 /* stream is ready to start */ ++#define SNDRV_PCM_STATE_RUNNING 3 /* stream is running */ ++#define SNDRV_PCM_STATE_XRUN 4 /* stream reached an xrun */ ++#define SNDRV_PCM_STATE_DRAINING 5 /* stream is draining */ ++#define SNDRV_PCM_STATE_PAUSED 6 /* stream is paused */ ++#define SNDRV_PCM_STATE_SUSPENDED 7 /* hardware is suspended */ ++#define SNDRV_PCM_STATE_DISCONNECTED 8 /* hardware is disconnected */ + #define SNDRV_PCM_STATE_LAST SNDRV_PCM_STATE_DISCONNECTED + + enum { +@@ -1058,7 +1058,7 @@ struct snd_timer_tread { + * * + ****************************************************************************/ + +-#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 9) ++#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 10) + + struct snd_ctl_card_info { + int card; /* card number */ +@@ -1072,24 +1072,43 @@ struct snd_ctl_card_info { + unsigned char components[128]; /* card components / fine identification, delimited with one space (AC97 etc..) */ + }; + +-typedef int __bitwise snd_ctl_elem_type_t; +-#define SNDRV_CTL_ELEM_TYPE_NONE ((__force snd_ctl_elem_type_t) 0) /* invalid */ +-#define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((__force snd_ctl_elem_type_t) 1) /* boolean type */ +-#define SNDRV_CTL_ELEM_TYPE_INTEGER ((__force snd_ctl_elem_type_t) 2) /* integer type */ +-#define SNDRV_CTL_ELEM_TYPE_ENUMERATED ((__force snd_ctl_elem_type_t) 3) /* enumerated type */ +-#define SNDRV_CTL_ELEM_TYPE_BYTES ((__force snd_ctl_elem_type_t) 4) /* byte array */ +-#define SNDRV_CTL_ELEM_TYPE_IEC958 ((__force snd_ctl_elem_type_t) 5) /* IEC958 (S/PDIF) setup */ +-#define SNDRV_CTL_ELEM_TYPE_INTEGER64 ((__force snd_ctl_elem_type_t) 6) /* 64-bit integer type */ ++/* ++ * Card components can exceed the fixed 128 bytes in snd_ctl_card_info. ++ * Use SNDRV_CTL_IOCTL_CARD_BYTES with type SND_CTL_CARD_BTYPE_COMPONENTS ++ * to retrieve the full string. ++ */ ++ ++/* Type values for struct snd_ctl_card_bytes::type */ ++enum { ++ SND_CTL_CARD_BTYPE_COMPONENTS = 1, /* full card components string */ ++}; ++ ++struct snd_ctl_card_bytes { ++ __u32 type; /* SND_CTL_CARD_BTYPE_* */ ++ __u32 data_allocated; /* size of @data buffer in bytes */ ++ __u32 data_len; /* in/out: actual data length in bytes */ ++ __u32 reserved; /* explicit pad */ ++ __u64 data; /* user buffer (pointer stored as __u64) */ ++}; ++ ++typedef int snd_ctl_elem_type_t; ++#define SNDRV_CTL_ELEM_TYPE_NONE 0 /* invalid */ ++#define SNDRV_CTL_ELEM_TYPE_BOOLEAN 1 /* boolean type */ ++#define SNDRV_CTL_ELEM_TYPE_INTEGER 2 /* integer type */ ++#define SNDRV_CTL_ELEM_TYPE_ENUMERATED 3 /* enumerated type */ ++#define SNDRV_CTL_ELEM_TYPE_BYTES 4 /* byte array */ ++#define SNDRV_CTL_ELEM_TYPE_IEC958 5 /* IEC958 (S/PDIF) setup */ ++#define SNDRV_CTL_ELEM_TYPE_INTEGER64 6 /* 64-bit integer type */ + #define SNDRV_CTL_ELEM_TYPE_LAST SNDRV_CTL_ELEM_TYPE_INTEGER64 + +-typedef int __bitwise snd_ctl_elem_iface_t; +-#define SNDRV_CTL_ELEM_IFACE_CARD ((__force snd_ctl_elem_iface_t) 0) /* global control */ +-#define SNDRV_CTL_ELEM_IFACE_HWDEP ((__force snd_ctl_elem_iface_t) 1) /* hardware dependent device */ +-#define SNDRV_CTL_ELEM_IFACE_MIXER ((__force snd_ctl_elem_iface_t) 2) /* virtual mixer device */ +-#define SNDRV_CTL_ELEM_IFACE_PCM ((__force snd_ctl_elem_iface_t) 3) /* PCM device */ +-#define SNDRV_CTL_ELEM_IFACE_RAWMIDI ((__force snd_ctl_elem_iface_t) 4) /* RawMidi device */ +-#define SNDRV_CTL_ELEM_IFACE_TIMER ((__force snd_ctl_elem_iface_t) 5) /* timer device */ +-#define SNDRV_CTL_ELEM_IFACE_SEQUENCER ((__force snd_ctl_elem_iface_t) 6) /* sequencer client */ ++typedef int snd_ctl_elem_iface_t; ++#define SNDRV_CTL_ELEM_IFACE_CARD 0 /* global control */ ++#define SNDRV_CTL_ELEM_IFACE_HWDEP 1 /* hardware dependent device */ ++#define SNDRV_CTL_ELEM_IFACE_MIXER 2 /* virtual mixer device */ ++#define SNDRV_CTL_ELEM_IFACE_PCM 3 /* PCM device */ ++#define SNDRV_CTL_ELEM_IFACE_RAWMIDI 4 /* RawMidi device */ ++#define SNDRV_CTL_ELEM_IFACE_TIMER 5 /* timer device */ ++#define SNDRV_CTL_ELEM_IFACE_SEQUENCER 6 /* sequencer client */ + #define SNDRV_CTL_ELEM_IFACE_LAST SNDRV_CTL_ELEM_IFACE_SEQUENCER + + #define SNDRV_CTL_ELEM_ACCESS_READ (1<<0) +@@ -1198,6 +1217,7 @@ struct snd_ctl_tlv { + + #define SNDRV_CTL_IOCTL_PVERSION _IOR('U', 0x00, int) + #define SNDRV_CTL_IOCTL_CARD_INFO _IOR('U', 0x01, struct snd_ctl_card_info) ++#define SNDRV_CTL_IOCTL_CARD_BYTES _IOWR('U', 0x02, struct snd_ctl_card_bytes) + #define SNDRV_CTL_IOCTL_ELEM_LIST _IOWR('U', 0x10, struct snd_ctl_elem_list) + #define SNDRV_CTL_IOCTL_ELEM_INFO _IOWR('U', 0x11, struct snd_ctl_elem_info) + #define SNDRV_CTL_IOCTL_ELEM_READ _IOWR('U', 0x12, struct snd_ctl_elem_value) +diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h +--- a/include/uapi/sound/firewire.h ++++ b/include/uapi/sound/firewire.h +@@ -79,11 +79,12 @@ struct snd_firewire_event_motu_register_dsp_change { + * + * @type: Fixed to SNDRV_FIREWIRE_EVENT_FF400_MESSAGE. + * @message_count: The number of messages. ++ * @messages: Array of @message_count messages + * @messages.message: The messages expressing hardware knob operation. + * @messages.tstamp: The isochronous cycle at which the request subaction of asynchronous +- * transaction was sent to deliver the message. It has 16 bit unsigned integer ++ * transaction was sent to deliver the message. It has 16-bit unsigned integer + * value. The higher 3 bits of value expresses the lower three bits of second +- * field in the format of CYCLE_TIME, up to 7. The rest 13 bits expresses cycle ++ * field in the format of CYCLE_TIME, up to 7. The remaining 13 bits express cycle + * field up to 7999. + * + * The structure expresses message transmitted by Fireface 400 when operating hardware knob. +@@ -191,8 +192,9 @@ struct snd_firewire_motu_register_dsp_meter { + #define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_ALIGNED_INPUT_COUNT (SNDRV_FIREWIRE_MOTU_REGISTER_DSP_INPUT_COUNT + 2) + + /** +- * snd_firewire_motu_register_dsp_parameter - the container for parameters of DSP controlled +- * by register access. ++ * struct snd_firewire_motu_register_dsp_parameter - the container for parameters ++ * of DSP controlled by register access. ++ * @mixer: aggregate of @mixer.source and @mixer.output + * @mixer.source.gain: The gain of source to mixer. + * @mixer.source.pan: The L/R balance of source to mixer. + * @mixer.source.flag: The flag of source to mixer, including mute, solo. +@@ -200,21 +202,25 @@ struct snd_firewire_motu_register_dsp_meter { + * Audio Express. + * @mixer.source.paired_width: The width of paired source to mixer, only for 4 pre and + * Audio Express. ++ * @mixer.output: FIXME + * @mixer.output.paired_volume: The volume of paired output from mixer. + * @mixer.output.paired_flag: The flag of paired output from mixer. ++ * @output: output parameters + * @output.main_paired_volume: The volume of paired main output. + * @output.hp_paired_volume: The volume of paired hp output. + * @output.hp_paired_assignment: The source assigned to paired hp output. +- * @output.reserved: Padding for 32 bit alignment for future extension. ++ * @output.reserved: Padding for 32-bit alignment for future extension. ++ * @line_input: line input parameters + * @line_input.boost_flag: The flags of boost for line inputs, only for 828mk2 and Traveler. + * @line_input.nominal_level_flag: The flags of nominal level for line inputs, only for 828mk2 and + * Traveler. +- * @line_input.reserved: Padding for 32 bit alignment for future extension. ++ * @line_input.reserved: Padding for 32-bit alignment for future extension. ++ * @input: input parameters + * @input.gain_and_invert: The value including gain and invert for input, only for Ultralite, 4 pre + * and Audio Express. + * @input.flag: The flag of input; e.g. jack detection, phantom power, and pad, only for Ultralite, + * 4 pre and Audio express. +- * @reserved: Padding so that the size of structure is kept to 512 byte, but for future extension. ++ * @reserved: Padding so that the size of structure is kept to 512 bytes, but for future extension. + * + * The structure expresses the set of parameters for DSP controlled by register access. + */ +@@ -272,8 +278,8 @@ struct snd_firewire_motu_register_dsp_parameter { + * controlled by command + * @data: Signal level meters. The mapping between position and signal channel is model-dependent. + * +- * The structure expresses the part of DSP status for hardware meter. The 32 bit storage is +- * estimated to include IEEE 764 32 bit single precision floating point (binary32) value. It is ++ * The structure expresses the part of DSP status for hardware meter. The 32-bit storage is ++ * estimated to include IEEE 764 32-bit single precision floating point (binary32) value. It is + * expected to be linear value (not logarithm) for audio signal level between 0.0 and +1.0. + */ + struct snd_firewire_motu_command_dsp_meter { +diff --git a/include/uapi/sound/skl-tplg-interface.h b/include/uapi/sound/skl-tplg-interface.h +deleted file mode 100644 +--- a/include/uapi/sound/skl-tplg-interface.h ++++ /dev/null +@@ -1,168 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +-/* +- * skl-tplg-interface.h - Intel DSP FW private data interface +- * +- * Copyright (C) 2015 Intel Corp +- * Author: Jeeja KP +- * Nilofer, Samreen +- */ +- +-#ifndef __HDA_TPLG_INTERFACE_H__ +-#define __HDA_TPLG_INTERFACE_H__ +- +-#include +- +-/* +- * Default types range from 0~12. type can range from 0 to 0xff +- * SST types start at higher to avoid any overlapping in future +- */ +-#define SKL_CONTROL_TYPE_BYTE_TLV 0x100 +-#define SKL_CONTROL_TYPE_MIC_SELECT 0x102 +-#define SKL_CONTROL_TYPE_MULTI_IO_SELECT 0x103 +-#define SKL_CONTROL_TYPE_MULTI_IO_SELECT_DMIC 0x104 +- +-#define HDA_SST_CFG_MAX 900 /* size of copier cfg*/ +-#define MAX_IN_QUEUE 8 +-#define MAX_OUT_QUEUE 8 +- +-#define SKL_UUID_STR_SZ 40 +-/* Event types goes here */ +-/* Reserve event type 0 for no event handlers */ +-enum skl_event_types { +- SKL_EVENT_NONE = 0, +- SKL_MIXER_EVENT, +- SKL_MUX_EVENT, +- SKL_VMIXER_EVENT, +- SKL_PGA_EVENT +-}; +- +-/** +- * enum skl_ch_cfg - channel configuration +- * +- * @SKL_CH_CFG_MONO: One channel only +- * @SKL_CH_CFG_STEREO: L & R +- * @SKL_CH_CFG_2_1: L, R & LFE +- * @SKL_CH_CFG_3_0: L, C & R +- * @SKL_CH_CFG_3_1: L, C, R & LFE +- * @SKL_CH_CFG_QUATRO: L, R, Ls & Rs +- * @SKL_CH_CFG_4_0: L, C, R & Cs +- * @SKL_CH_CFG_5_0: L, C, R, Ls & Rs +- * @SKL_CH_CFG_5_1: L, C, R, Ls, Rs & LFE +- * @SKL_CH_CFG_DUAL_MONO: One channel replicated in two +- * @SKL_CH_CFG_I2S_DUAL_STEREO_0: Stereo(L,R) in 4 slots, 1st stream:[ L, R, -, - ] +- * @SKL_CH_CFG_I2S_DUAL_STEREO_1: Stereo(L,R) in 4 slots, 2nd stream:[ -, -, L, R ] +- * @SKL_CH_CFG_INVALID: Invalid +- */ +-enum skl_ch_cfg { +- SKL_CH_CFG_MONO = 0, +- SKL_CH_CFG_STEREO = 1, +- SKL_CH_CFG_2_1 = 2, +- SKL_CH_CFG_3_0 = 3, +- SKL_CH_CFG_3_1 = 4, +- SKL_CH_CFG_QUATRO = 5, +- SKL_CH_CFG_4_0 = 6, +- SKL_CH_CFG_5_0 = 7, +- SKL_CH_CFG_5_1 = 8, +- SKL_CH_CFG_DUAL_MONO = 9, +- SKL_CH_CFG_I2S_DUAL_STEREO_0 = 10, +- SKL_CH_CFG_I2S_DUAL_STEREO_1 = 11, +- SKL_CH_CFG_7_1 = 12, +- SKL_CH_CFG_4_CHANNEL = SKL_CH_CFG_7_1, +- SKL_CH_CFG_INVALID +-}; +- +-enum skl_module_type { +- SKL_MODULE_TYPE_MIXER = 0, +- SKL_MODULE_TYPE_COPIER, +- SKL_MODULE_TYPE_UPDWMIX, +- SKL_MODULE_TYPE_SRCINT, +- SKL_MODULE_TYPE_ALGO, +- SKL_MODULE_TYPE_BASE_OUTFMT, +- SKL_MODULE_TYPE_KPB, +- SKL_MODULE_TYPE_MIC_SELECT, +-}; +- +-enum skl_core_affinity { +- SKL_AFFINITY_CORE_0 = 0, +- SKL_AFFINITY_CORE_1, +- SKL_AFFINITY_CORE_MAX +-}; +- +-enum skl_pipe_conn_type { +- SKL_PIPE_CONN_TYPE_NONE = 0, +- SKL_PIPE_CONN_TYPE_FE, +- SKL_PIPE_CONN_TYPE_BE +-}; +- +-enum skl_hw_conn_type { +- SKL_CONN_NONE = 0, +- SKL_CONN_SOURCE = 1, +- SKL_CONN_SINK = 2 +-}; +- +-enum skl_dev_type { +- SKL_DEVICE_BT = 0x0, +- SKL_DEVICE_DMIC = 0x1, +- SKL_DEVICE_I2S = 0x2, +- SKL_DEVICE_SLIMBUS = 0x3, +- SKL_DEVICE_HDALINK = 0x4, +- SKL_DEVICE_HDAHOST = 0x5, +- SKL_DEVICE_NONE +-}; +- +-/** +- * enum skl_interleaving - interleaving style +- * +- * @SKL_INTERLEAVING_PER_CHANNEL: [s1_ch1...s1_chN,...,sM_ch1...sM_chN] +- * @SKL_INTERLEAVING_PER_SAMPLE: [s1_ch1...sM_ch1,...,s1_chN...sM_chN] +- */ +-enum skl_interleaving { +- SKL_INTERLEAVING_PER_CHANNEL = 0, +- SKL_INTERLEAVING_PER_SAMPLE = 1, +-}; +- +-enum skl_sample_type { +- SKL_SAMPLE_TYPE_INT_MSB = 0, +- SKL_SAMPLE_TYPE_INT_LSB = 1, +- SKL_SAMPLE_TYPE_INT_SIGNED = 2, +- SKL_SAMPLE_TYPE_INT_UNSIGNED = 3, +- SKL_SAMPLE_TYPE_FLOAT = 4 +-}; +- +-enum module_pin_type { +- /* All pins of the module takes same PCM inputs or outputs +- * e.g. mixout +- */ +- SKL_PIN_TYPE_HOMOGENEOUS, +- /* All pins of the module takes different PCM inputs or outputs +- * e.g mux +- */ +- SKL_PIN_TYPE_HETEROGENEOUS, +-}; +- +-enum skl_module_param_type { +- SKL_PARAM_DEFAULT = 0, +- SKL_PARAM_INIT, +- SKL_PARAM_SET, +- SKL_PARAM_BIND +-}; +- +-struct skl_dfw_algo_data { +- __u32 set_params:2; +- __u32 rsvd:30; +- __u32 param_id; +- __u32 max; +- char params[]; +-} __packed; +- +-enum skl_tkn_dir { +- SKL_DIR_IN, +- SKL_DIR_OUT +-}; +- +-enum skl_tuple_type { +- SKL_TYPE_TUPLE, +- SKL_TYPE_DATA +-}; +- +-#endif +diff --git a/include/uapi/sound/snd_ar_tokens.h b/include/uapi/sound/snd_ar_tokens.h +--- a/include/uapi/sound/snd_ar_tokens.h ++++ b/include/uapi/sound/snd_ar_tokens.h +@@ -58,7 +58,7 @@ enum ar_event_types { + #define SND_SOC_AR_TPLG_FE_BE_GRAPH_CTL_MIX 256 + #define SND_SOC_AR_TPLG_VOL_CTL 257 + +-/** ++/* + * %AR_TKN_U32_SUB_GRAPH_INSTANCE_ID: Sub Graph Instance Id + * + * %AR_TKN_U32_SUB_GRAPH_PERF_MODE: Performance mode of subgraph +@@ -168,6 +168,60 @@ enum ar_event_types { + * LOG_WAIT = 0, + * LOG_IMMEDIATELY = 1 + * ++ * %AR_TKN_U16_MODULE_SYNC_SRC: Frame sync source ++ * AR_AUDIO_IF_SYNC_SRC_EXTERNAL = 0, ++ * AR_AUDIO_IF_SYNC_SRC_INTERNAL = 1 ++ * ++ * %AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE: Enable data-out tri-state control ++ * AR_AUDIO_IF_CTRL_DATA_OE_DISABLE = 0, ++ * AR_AUDIO_IF_CTRL_DATA_OE_ENABLE = 1 ++ * ++ * %AR_TKN_U32_MODULE_SLOT_MASK: Active TDM slot bitmask ++ * ++ * %AR_TKN_U16_MODULE_NSLOTS_PER_FRAME: Number of slots per TDM frame ++ * ++ * %AR_TKN_U16_MODULE_SLOT_WIDTH: Slot width in bits (16 or 32) ++ * ++ * %AR_TKN_U16_MODULE_SYNC_MODE: Frame sync mode ++ * AR_AUDIO_IF_FRAME_SYNC_MODE_SHORT = 0, ++ * AR_AUDIO_IF_FRAME_SYNC_MODE_ONE_SLOT = 1, ++ * AR_AUDIO_IF_FRAME_SYNC_MODE_LONG = 2 ++ * ++ * %AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE: Invert frame sync pulse polarity ++ * AR_AUDIO_IF_SYNC_NORMAL = 0, ++ * AR_AUDIO_IF_SYNC_INVERTED = 1 ++ * ++ * %AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY: Data delay relative to frame sync ++ * AR_AUDIO_IF_DATA_DELAY_NONE = 0, ++ * AR_AUDIO_IF_DATA_DELAY_1_CYCLE = 1, ++ * AR_AUDIO_IF_DATA_DELAY_2_CYCLE = 2 ++ * ++ * %AR_TKN_U16_MODULE_INTF_MODE: Audio IF interface mode ++ * AR_AUDIO_IF_INTF_MODE_TDM = 0, ++ * AR_AUDIO_IF_INTF_MODE_PCM = 1, ++ * AR_AUDIO_IF_INTF_MODE_I2S = 2 ++ * ++ * %AR_TKN_U16_MODULE_QAIF_TYPE: QAIF hardware port type index ++ * AR_AUDIO_IF_QAIF = 0, ++ * AR_AUDIO_IF_QAIF_VA = 1 ++ * ++ * %AR_TKN_U32_MODULE_ACTIVE_LANE_MASK: Active lane bitmask for multi-lane ++ * ++ * %AR_TKN_U32_MODULE_FRAME_SYNC_RATE: Frame sync rate in Hz ++ * ++ * %AR_TKN_U16_MODULE_BIT_CLK_TYPE: Bit clock type ++ * AR_AUDIO_IF_BIT_CLK_INTERNAL = 0, ++ * AR_AUDIO_IF_BIT_CLK_EXTERNAL = 1, ++ * AR_AUDIO_IF_BIT_CLK_SKIP = 2 ++ * ++ * %AR_TKN_U8_MODULE_INV_INT_BIT_CLK: Invert internal bit clock ++ * AR_AUDIO_IF_CLK_NORMAL = 0, ++ * AR_AUDIO_IF_CLK_INVERTED = 1 ++ * ++ * %AR_TKN_U8_MODULE_INV_EXT_BIT_CLK: Invert external bit clock ++ * AR_AUDIO_IF_CLK_NORMAL = 0, ++ * AR_AUDIO_IF_CLK_INVERTED = 1 ++ * + * %AR_TKN_DAI_INDEX: dai index + * + */ +@@ -240,6 +294,45 @@ enum ar_event_types { + #define AR_TKN_U32_MODULE_LOG_TAP_POINT_ID 260 + #define AR_TKN_U32_MODULE_LOG_MODE 261 + ++#define AR_TKN_U16_MODULE_SYNC_SRC 262 ++#define AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE 263 ++#define AR_TKN_U32_MODULE_SLOT_MASK 264 ++#define AR_TKN_U16_MODULE_NSLOTS_PER_FRAME 265 ++#define AR_TKN_U16_MODULE_SLOT_WIDTH 266 ++#define AR_TKN_U16_MODULE_SYNC_MODE 267 ++#define AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE 268 ++#define AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY 269 ++#define AR_TKN_U16_MODULE_INTF_MODE 270 ++#define AR_TKN_U16_MODULE_QAIF_TYPE 271 ++#define AR_TKN_U32_MODULE_ACTIVE_LANE_MASK 272 ++#define AR_TKN_U32_MODULE_FRAME_SYNC_RATE 273 ++#define AR_TKN_U16_MODULE_BIT_CLK_TYPE 274 ++#define AR_TKN_U8_MODULE_INV_INT_BIT_CLK 275 ++#define AR_TKN_U8_MODULE_INV_EXT_BIT_CLK 276 ++ ++#define AR_AUDIO_IF_SYNC_SRC_EXTERNAL 0 ++#define AR_AUDIO_IF_SYNC_SRC_INTERNAL 1 ++#define AR_AUDIO_IF_CTRL_DATA_OE_DISABLE 0 ++#define AR_AUDIO_IF_CTRL_DATA_OE_ENABLE 1 ++#define AR_AUDIO_IF_INTF_MODE_TDM 0 ++#define AR_AUDIO_IF_INTF_MODE_PCM 1 ++#define AR_AUDIO_IF_INTF_MODE_I2S 2 ++#define AR_AUDIO_IF_QAIF 0 ++#define AR_AUDIO_IF_QAIF_VA 1 ++#define AR_AUDIO_IF_FRAME_SYNC_MODE_SHORT 0 ++#define AR_AUDIO_IF_FRAME_SYNC_MODE_ONE_SLOT 1 ++#define AR_AUDIO_IF_FRAME_SYNC_MODE_LONG 2 ++#define AR_AUDIO_IF_SYNC_NORMAL 0 ++#define AR_AUDIO_IF_SYNC_INVERTED 1 ++#define AR_AUDIO_IF_DATA_DELAY_NONE 0 ++#define AR_AUDIO_IF_DATA_DELAY_1_CYCLE 1 ++#define AR_AUDIO_IF_DATA_DELAY_2_CYCLE 2 ++#define AR_AUDIO_IF_BIT_CLK_INTERNAL 0 ++#define AR_AUDIO_IF_BIT_CLK_EXTERNAL 1 ++#define AR_AUDIO_IF_BIT_CLK_SKIP 2 ++#define AR_AUDIO_IF_CLK_NORMAL 0 ++#define AR_AUDIO_IF_CLK_INVERTED 1 ++ + #define SND_SOC_AR_TPLG_MODULE_CFG_TYPE 0x01001006 + struct audioreach_module_priv_data { + __le32 size; /* size in bytes of the array, including all elements */ +diff --git a/include/uapi/sound/snd_sst_tokens.h b/include/uapi/sound/snd_sst_tokens.h +deleted file mode 100644 +--- a/include/uapi/sound/snd_sst_tokens.h ++++ /dev/null +@@ -1,324 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +-/* +- * snd_sst_tokens.h - Intel SST tokens definition +- * +- * Copyright (C) 2016 Intel Corp +- * Author: Shreyas NC +- */ +-#ifndef __SND_SST_TOKENS_H__ +-#define __SND_SST_TOKENS_H__ +- +-/** +- * %SKL_TKN_UUID: Module UUID +- * +- * %SKL_TKN_U8_BLOCK_TYPE: Type of the private data block.Can be: +- * tuples, bytes, short and words +- * +- * %SKL_TKN_U8_IN_PIN_TYPE: Input pin type, +- * homogenous=0, heterogenous=1 +- * +- * %SKL_TKN_U8_OUT_PIN_TYPE: Output pin type, +- * homogenous=0, heterogenous=1 +- * %SKL_TKN_U8_DYN_IN_PIN: Configure Input pin dynamically +- * if true +- * +- * %SKL_TKN_U8_DYN_OUT_PIN: Configure Output pin dynamically +- * if true +- * +- * %SKL_TKN_U8_IN_QUEUE_COUNT: Store the number of Input pins +- * +- * %SKL_TKN_U8_OUT_QUEUE_COUNT: Store the number of Output pins +- * +- * %SKL_TKN_U8_TIME_SLOT: TDM slot number +- * +- * %SKL_TKN_U8_CORE_ID: Stores module affinity value.Can take +- * the values: +- * SKL_AFFINITY_CORE_0 = 0, +- * SKL_AFFINITY_CORE_1, +- * SKL_AFFINITY_CORE_MAX +- * +- * %SKL_TKN_U8_MOD_TYPE: Module type value. +- * +- * %SKL_TKN_U8_CONN_TYPE: Module connection type can be a FE, +- * BE or NONE as defined : +- * SKL_PIPE_CONN_TYPE_NONE = 0, +- * SKL_PIPE_CONN_TYPE_FE = 1 (HOST_DMA) +- * SKL_PIPE_CONN_TYPE_BE = 2 (LINK_DMA) +- * +- * %SKL_TKN_U8_DEV_TYPE: Type of device to which the module is +- * connected +- * Can take the values: +- * SKL_DEVICE_BT = 0x0, +- * SKL_DEVICE_DMIC = 0x1, +- * SKL_DEVICE_I2S = 0x2, +- * SKL_DEVICE_SLIMBUS = 0x3, +- * SKL_DEVICE_HDALINK = 0x4, +- * SKL_DEVICE_HDAHOST = 0x5, +- * SKL_DEVICE_NONE +- * +- * %SKL_TKN_U8_HW_CONN_TYPE: Connection type of the HW to which the +- * module is connected +- * SKL_CONN_NONE = 0, +- * SKL_CONN_SOURCE = 1, +- * SKL_CONN_SINK = 2 +- * +- * %SKL_TKN_U16_PIN_INST_ID: Stores the pin instance id +- * +- * %SKL_TKN_U16_MOD_INST_ID: Stores the mdule instance id +- * +- * %SKL_TKN_U32_MAX_MCPS: Module max mcps value +- * +- * %SKL_TKN_U32_MEM_PAGES: Module resource pages +- * +- * %SKL_TKN_U32_OBS: Stores Output Buffer size +- * +- * %SKL_TKN_U32_IBS: Stores input buffer size +- * +- * %SKL_TKN_U32_VBUS_ID: Module VBUS_ID. PDM=0, SSP0=0, +- * SSP1=1,SSP2=2, +- * SSP3=3, SSP4=4, +- * SSP5=5, SSP6=6,INVALID +- * +- * %SKL_TKN_U32_PARAMS_FIXUP: Module Params fixup mask +- * %SKL_TKN_U32_CONVERTER: Module params converter mask +- * %SKL_TKN_U32_PIPE_ID: Stores the pipe id +- * +- * %SKL_TKN_U32_PIPE_CONN_TYPE: Type of the token to which the pipe is +- * connected to. It can be +- * SKL_PIPE_CONN_TYPE_NONE = 0, +- * SKL_PIPE_CONN_TYPE_FE = 1 (HOST_DMA), +- * SKL_PIPE_CONN_TYPE_BE = 2 (LINK_DMA), +- * +- * %SKL_TKN_U32_PIPE_PRIORITY: Pipe priority value +- * %SKL_TKN_U32_PIPE_MEM_PGS: Pipe resource pages +- * +- * %SKL_TKN_U32_DIR_PIN_COUNT: Value for the direction to set input/output +- * formats and the pin count. +- * The first 4 bits have the direction +- * value and the next 4 have +- * the pin count value. +- * SKL_DIR_IN = 0, SKL_DIR_OUT = 1. +- * The input and output formats +- * share the same set of tokens +- * with the distinction between input +- * and output made by reading direction +- * token. +- * +- * %SKL_TKN_U32_FMT_CH: Supported channel count +- * +- * %SKL_TKN_U32_FMT_FREQ: Supported frequency/sample rate +- * +- * %SKL_TKN_U32_FMT_BIT_DEPTH: Supported container size +- * +- * %SKL_TKN_U32_FMT_SAMPLE_SIZE:Number of samples in the container +- * +- * %SKL_TKN_U32_FMT_CH_CONFIG: Supported channel configurations for the +- * input/output. +- * +- * %SKL_TKN_U32_FMT_INTERLEAVE: Interleaving style which can be per +- * channel or per sample. The values can be : +- * SKL_INTERLEAVING_PER_CHANNEL = 0, +- * SKL_INTERLEAVING_PER_SAMPLE = 1, +- * +- * %SKL_TKN_U32_FMT_SAMPLE_TYPE: +- * Specifies the sample type. Can take the +- * values: SKL_SAMPLE_TYPE_INT_MSB = 0, +- * SKL_SAMPLE_TYPE_INT_LSB = 1, +- * SKL_SAMPLE_TYPE_INT_SIGNED = 2, +- * SKL_SAMPLE_TYPE_INT_UNSIGNED = 3, +- * SKL_SAMPLE_TYPE_FLOAT = 4 +- * +- * %SKL_TKN_U32_CH_MAP: Channel map values +- * %SKL_TKN_U32_MOD_SET_PARAMS: It can take these values: +- * SKL_PARAM_DEFAULT, SKL_PARAM_INIT, +- * SKL_PARAM_SET, SKL_PARAM_BIND +- * +- * %SKL_TKN_U32_MOD_PARAM_ID: ID of the module params +- * +- * %SKL_TKN_U32_CAPS_SET_PARAMS: +- * Set params value +- * +- * %SKL_TKN_U32_CAPS_PARAMS_ID: Params ID +- * +- * %SKL_TKN_U32_CAPS_SIZE: Caps size +- * +- * %SKL_TKN_U32_PROC_DOMAIN: Specify processing domain +- * +- * %SKL_TKN_U32_LIB_COUNT: Specifies the number of libraries +- * +- * %SKL_TKN_STR_LIB_NAME: Specifies the library name +- * +- * %SKL_TKN_U32_PMODE: Specifies the power mode for pipe +- * +- * %SKL_TKL_U32_D0I3_CAPS: Specifies the D0i3 capability for module +- * +- * %SKL_TKN_U32_DMA_BUF_SIZE: DMA buffer size in millisec +- * +- * %SKL_TKN_U32_PIPE_DIR: Specifies pipe direction. Can be +- * playback/capture. +- * +- * %SKL_TKN_U32_NUM_CONFIGS: Number of pipe configs +- * +- * %SKL_TKN_U32_PATH_MEM_PGS: Size of memory (in pages) required for pipeline +- * and its data +- * +- * %SKL_TKN_U32_PIPE_CONFIG_ID: Config id for the modules in the pipe +- * and PCM params supported by that pipe +- * config. This is used as index to fill +- * up the pipe config and module config +- * structure. +- * +- * %SKL_TKN_U32_CFG_FREQ: +- * %SKL_TKN_U8_CFG_CHAN: +- * %SKL_TKN_U8_CFG_BPS: PCM params (freq, channels, bits per sample) +- * supported for each of the pipe configs. +- * +- * %SKL_TKN_CFG_MOD_RES_ID: Module's resource index for each of the +- * pipe config +- * +- * %SKL_TKN_CFG_MOD_FMT_ID: Module's interface index for each of the +- * pipe config +- * +- * %SKL_TKN_U8_NUM_MOD: Number of modules in the manifest +- * +- * %SKL_TKN_MM_U8_MOD_IDX: Current index of the module in the manifest +- * +- * %SKL_TKN_MM_U8_NUM_RES: Number of resources for the module +- * +- * %SKL_TKN_MM_U8_NUM_INTF: Number of interfaces for the module +- * +- * %SKL_TKN_MM_U32_RES_ID: Resource index for the resource info to +- * be filled into. +- * A module can support multiple resource +- * configuration and is represnted as a +- * resource table. This index is used to +- * fill information into appropriate index. +- * +- * %SKL_TKN_MM_U32_CPS: DSP cycles per second +- * +- * %SKL_TKN_MM_U32_DMA_SIZE: Allocated buffer size for gateway DMA +- * +- * %SKL_TKN_MM_U32_CPC: DSP cycles allocated per frame +- * +- * %SKL_TKN_MM_U32_RES_PIN_ID: Resource pin index in the module +- * +- * %SKL_TKN_MM_U32_INTF_PIN_ID: Interface index in the module +- * +- * %SKL_TKN_MM_U32_PIN_BUF: Buffer size of the module pin +- * +- * %SKL_TKN_MM_U32_FMT_ID: Format index for each of the interface/ +- * format information to be filled into. +- * +- * %SKL_TKN_MM_U32_NUM_IN_FMT: Number of input formats +- * %SKL_TKN_MM_U32_NUM_OUT_FMT: Number of output formats +- * +- * %SKL_TKN_U32_ASTATE_IDX: Table Index for the A-State entry to be filled +- * with kcps and clock source +- * +- * %SKL_TKN_U32_ASTATE_COUNT: Number of valid entries in A-State table +- * +- * %SKL_TKN_U32_ASTATE_KCPS: Specifies the core load threshold (in kilo +- * cycles per second) below which DSP is clocked +- * from source specified by clock source. +- * +- * %SKL_TKN_U32_ASTATE_CLK_SRC: Clock source for A-State entry +- * +- * %SKL_TKN_U32_FMT_CFG_IDX: Format config index +- * +- * module_id and loadable flags dont have tokens as these values will be +- * read from the DSP FW manifest +- * +- * Tokens defined can be used either in the manifest or widget private data. +- * +- * SKL_TKN_MM is used as a suffix for all tokens that represent +- * module data in the manifest. +- */ +-enum SKL_TKNS { +- SKL_TKN_UUID = 1, +- SKL_TKN_U8_NUM_BLOCKS, +- SKL_TKN_U8_BLOCK_TYPE, +- SKL_TKN_U8_IN_PIN_TYPE, +- SKL_TKN_U8_OUT_PIN_TYPE, +- SKL_TKN_U8_DYN_IN_PIN, +- SKL_TKN_U8_DYN_OUT_PIN, +- SKL_TKN_U8_IN_QUEUE_COUNT, +- SKL_TKN_U8_OUT_QUEUE_COUNT, +- SKL_TKN_U8_TIME_SLOT, +- SKL_TKN_U8_CORE_ID, +- SKL_TKN_U8_MOD_TYPE, +- SKL_TKN_U8_CONN_TYPE, +- SKL_TKN_U8_DEV_TYPE, +- SKL_TKN_U8_HW_CONN_TYPE, +- SKL_TKN_U16_MOD_INST_ID, +- SKL_TKN_U16_BLOCK_SIZE, +- SKL_TKN_U32_MAX_MCPS, +- SKL_TKN_U32_MEM_PAGES, +- SKL_TKN_U32_OBS, +- SKL_TKN_U32_IBS, +- SKL_TKN_U32_VBUS_ID, +- SKL_TKN_U32_PARAMS_FIXUP, +- SKL_TKN_U32_CONVERTER, +- SKL_TKN_U32_PIPE_ID, +- SKL_TKN_U32_PIPE_CONN_TYPE, +- SKL_TKN_U32_PIPE_PRIORITY, +- SKL_TKN_U32_PIPE_MEM_PGS, +- SKL_TKN_U32_DIR_PIN_COUNT, +- SKL_TKN_U32_FMT_CH, +- SKL_TKN_U32_FMT_FREQ, +- SKL_TKN_U32_FMT_BIT_DEPTH, +- SKL_TKN_U32_FMT_SAMPLE_SIZE, +- SKL_TKN_U32_FMT_CH_CONFIG, +- SKL_TKN_U32_FMT_INTERLEAVE, +- SKL_TKN_U32_FMT_SAMPLE_TYPE, +- SKL_TKN_U32_FMT_CH_MAP, +- SKL_TKN_U32_PIN_MOD_ID, +- SKL_TKN_U32_PIN_INST_ID, +- SKL_TKN_U32_MOD_SET_PARAMS, +- SKL_TKN_U32_MOD_PARAM_ID, +- SKL_TKN_U32_CAPS_SET_PARAMS, +- SKL_TKN_U32_CAPS_PARAMS_ID, +- SKL_TKN_U32_CAPS_SIZE, +- SKL_TKN_U32_PROC_DOMAIN, +- SKL_TKN_U32_LIB_COUNT, +- SKL_TKN_STR_LIB_NAME, +- SKL_TKN_U32_PMODE, +- SKL_TKL_U32_D0I3_CAPS, /* Typo added at v4.10 */ +- SKL_TKN_U32_D0I3_CAPS = SKL_TKL_U32_D0I3_CAPS, +- SKL_TKN_U32_DMA_BUF_SIZE, +- +- SKL_TKN_U32_PIPE_DIRECTION, +- SKL_TKN_U32_PIPE_CONFIG_ID, +- SKL_TKN_U32_NUM_CONFIGS, +- SKL_TKN_U32_PATH_MEM_PGS, +- +- SKL_TKN_U32_CFG_FREQ, +- SKL_TKN_U8_CFG_CHAN, +- SKL_TKN_U8_CFG_BPS, +- SKL_TKN_CFG_MOD_RES_ID, +- SKL_TKN_CFG_MOD_FMT_ID, +- SKL_TKN_U8_NUM_MOD, +- +- SKL_TKN_MM_U8_MOD_IDX, +- SKL_TKN_MM_U8_NUM_RES, +- SKL_TKN_MM_U8_NUM_INTF, +- SKL_TKN_MM_U32_RES_ID, +- SKL_TKN_MM_U32_CPS, +- SKL_TKN_MM_U32_DMA_SIZE, +- SKL_TKN_MM_U32_CPC, +- SKL_TKN_MM_U32_RES_PIN_ID, +- SKL_TKN_MM_U32_INTF_PIN_ID, +- SKL_TKN_MM_U32_PIN_BUF, +- SKL_TKN_MM_U32_FMT_ID, +- SKL_TKN_MM_U32_NUM_IN_FMT, +- SKL_TKN_MM_U32_NUM_OUT_FMT, +- +- SKL_TKN_U32_ASTATE_IDX, +- SKL_TKN_U32_ASTATE_COUNT, +- SKL_TKN_U32_ASTATE_KCPS, +- SKL_TKN_U32_ASTATE_CLK_SRC, +- +- SKL_TKN_U32_FMT_CFG_IDX = 96, +- SKL_TKN_MAX = SKL_TKN_U32_FMT_CFG_IDX, +-}; +- +-#endif +diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h +--- a/include/uapi/sound/sof/tokens.h ++++ b/include/uapi/sound/sof/tokens.h +@@ -111,8 +111,8 @@ + + #define SOF_TKN_COMP_SCHED_DOMAIN 418 + #define SOF_TKN_COMP_DOMAIN_ID 419 +-#define SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT 420 +-#define SOF_TKN_COMP_STACK_BYTES_REQUIREMENT 421 ++#define SOF_TKN_COMP_STACK_BYTES_REQUIREMENT 420 ++#define SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT 421 + + /* SSP */ + #define SOF_TKN_INTEL_SSP_CLKS_CONTROL 500 +@@ -223,6 +223,7 @@ + #define SOF_TKN_AMD_ACPI2S_RATE 1700 + #define SOF_TKN_AMD_ACPI2S_CH 1701 + #define SOF_TKN_AMD_ACPI2S_TDM_MODE 1702 ++#define SOF_TKN_AMD_ACPI2S_FORMAT 1703 + + /* MICFIL PDM */ + #define SOF_TKN_IMX_MICFIL_RATE 2000 +diff --git a/sound/atmel/ac97c.c b/sound/atmel/ac97c.c +--- a/sound/atmel/ac97c.c ++++ b/sound/atmel/ac97c.c +@@ -693,7 +693,7 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + struct device *dev = &pdev->dev; + struct snd_card *card; + struct atmel_ac97c *chip; +- struct resource *regs; ++ void __iomem *regs; + struct clk *pclk; + static const struct snd_ac97_bus_ops ops = { + .write = atmel_ac97c_write, +@@ -702,42 +702,34 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + int retval; + int irq; + +- regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- if (!regs) { +- dev_dbg(&pdev->dev, "no memory resource\n"); +- return -ENXIO; +- } ++ regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); + + irq = platform_get_irq(pdev, 0); +- if (irq < 0) { +- dev_dbg(&pdev->dev, "could not get irq: %d\n", irq); ++ if (irq < 0) + return irq; +- } + +- pclk = clk_get(&pdev->dev, "ac97_clk"); ++ pclk = devm_clk_get_enabled(&pdev->dev, "ac97_clk"); + if (IS_ERR(pclk)) { + dev_dbg(&pdev->dev, "no peripheral clock\n"); + return PTR_ERR(pclk); + } +- retval = clk_prepare_enable(pclk); +- if (retval) +- goto err_prepare_enable; + +- retval = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, ++ retval = snd_devm_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, THIS_MODULE, + sizeof(struct atmel_ac97c), &card); + if (retval) { + dev_dbg(&pdev->dev, "could not create sound card device\n"); +- goto err_snd_card_new; ++ return retval; + } + + chip = get_chip(card); + +- retval = request_irq(irq, atmel_ac97c_interrupt, 0, "AC97C", chip); +- if (retval) { +- dev_dbg(&pdev->dev, "unable to request irq %d\n", irq); +- goto err_request_irq; +- } ++ retval = devm_request_irq(&pdev->dev, irq, atmel_ac97c_interrupt, 0, "AC97C", chip); ++ if (retval) ++ return retval; ++ + chip->irq = irq; + + spin_lock_init(&chip->lock); +@@ -749,13 +741,7 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + chip->card = card; + chip->pclk = pclk; + chip->pdev = pdev; +- chip->regs = ioremap(regs->start, resource_size(regs)); +- +- if (!chip->regs) { +- dev_dbg(&pdev->dev, "could not remap register memory\n"); +- retval = -ENOMEM; +- goto err_ioremap; +- } ++ chip->regs = regs; + + chip->reset_pin = devm_gpiod_get_index(dev, "ac97", 2, GPIOD_OUT_HIGH); + if (IS_ERR(chip->reset_pin)) +@@ -770,25 +756,25 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + retval = snd_ac97_bus(card, 0, &ops, chip, &chip->ac97_bus); + if (retval) { + dev_dbg(&pdev->dev, "could not register on ac97 bus\n"); +- goto err_ac97_bus; ++ return retval; + } + + retval = atmel_ac97c_mixer_new(chip); + if (retval) { + dev_dbg(&pdev->dev, "could not register ac97 mixer\n"); +- goto err_ac97_bus; ++ return retval; + } + + retval = atmel_ac97c_pcm_new(chip); + if (retval) { + dev_dbg(&pdev->dev, "could not register ac97 pcm device\n"); +- goto err_ac97_bus; ++ return retval; + } + + retval = snd_card_register(card); + if (retval) { + dev_dbg(&pdev->dev, "could not register sound card\n"); +- goto err_ac97_bus; ++ return retval; + } + + platform_set_drvdata(pdev, card); +@@ -797,18 +783,6 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + chip->regs, irq); + + return 0; +- +-err_ac97_bus: +- iounmap(chip->regs); +-err_ioremap: +- free_irq(irq, chip); +-err_request_irq: +- snd_card_free(card); +-err_snd_card_new: +- clk_disable_unprepare(pclk); +-err_prepare_enable: +- clk_put(pclk); +- return retval; + } + + static int atmel_ac97c_suspend(struct device *pdev) +@@ -839,13 +813,6 @@ static void atmel_ac97c_remove(struct platform_device *pdev) + ac97c_writel(chip, CAMR, 0); + ac97c_writel(chip, COMR, 0); + ac97c_writel(chip, MR, 0); +- +- clk_disable_unprepare(chip->pclk); +- clk_put(chip->pclk); +- iounmap(chip->regs); +- free_irq(chip->irq, chip); +- +- snd_card_free(card); + } + + static struct platform_driver atmel_ac97c_driver = { +diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c +--- a/sound/core/compress_offload.c ++++ b/sound/core/compress_offload.c +@@ -1393,17 +1393,17 @@ static long snd_compr_ioctl_compat(struct file *file, unsigned int cmd, + #endif + + static const struct file_operations snd_compr_file_ops = { +- .owner = THIS_MODULE, +- .open = snd_compr_open, +- .release = snd_compr_free, +- .write = snd_compr_write, +- .read = snd_compr_read, +- .unlocked_ioctl = snd_compr_ioctl, ++ .owner = THIS_MODULE, ++ .open = snd_compr_open, ++ .release = snd_compr_free, ++ .write = snd_compr_write, ++ .read = snd_compr_read, ++ .unlocked_ioctl = snd_compr_ioctl, + #ifdef CONFIG_COMPAT +- .compat_ioctl = snd_compr_ioctl_compat, ++ .compat_ioctl = snd_compr_ioctl_compat, + #endif +- .mmap = snd_compr_mmap, +- .poll = snd_compr_poll, ++ .mmap = snd_compr_mmap, ++ .poll = snd_compr_poll, + }; + + static int snd_compress_dev_register(struct snd_device *device) +diff --git a/sound/core/control.c b/sound/core/control.c +--- a/sound/core/control.c ++++ b/sound/core/control.c +@@ -38,7 +38,7 @@ struct snd_kctl_ioctl { + snd_kctl_ioctl_func_t fioctl; + }; + +-static DECLARE_RWSEM(snd_ioctl_rwsem); ++DECLARE_RWSEM(snd_ioctl_rwsem); + static DECLARE_RWSEM(snd_ctl_layer_rwsem); + static LIST_HEAD(snd_control_ioctls); + #ifdef CONFIG_COMPAT +@@ -55,9 +55,7 @@ static int snd_ctl_open(struct inode *inode, struct file *file) + struct snd_ctl_file *ctl; + int i, err; + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + card = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_CONTROL); + if (!card) { +@@ -65,12 +63,10 @@ static int snd_ctl_open(struct inode *inode, struct file *file) + goto __error1; + } + err = snd_card_file_add(card, file); +- if (err < 0) { +- err = -ENODEV; ++ if (err < 0) + goto __error1; +- } + if (!try_module_get(card->module)) { +- err = -EFAULT; ++ err = -ENODEV; + goto __error2; + } + ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); +@@ -876,23 +872,81 @@ static int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl, + { + struct snd_ctl_card_info *info __free(kfree) = + kzalloc(sizeof(*info), GFP_KERNEL); ++ ssize_t n; + + if (! info) + return -ENOMEM; ++ ++ static_assert(sizeof(info->components) >= 2); ++ + scoped_guard(rwsem_read, &snd_ioctl_rwsem) { ++ const char *components = card->components; ++ ++ if (!components) ++ components = ""; ++ + info->card = card->number; + strscpy(info->id, card->id, sizeof(info->id)); + strscpy(info->driver, card->driver, sizeof(info->driver)); + strscpy(info->name, card->shortname, sizeof(info->name)); + strscpy(info->longname, card->longname, sizeof(info->longname)); + strscpy(info->mixername, card->mixername, sizeof(info->mixername)); +- strscpy(info->components, card->components, sizeof(info->components)); ++ n = strscpy(info->components, components, sizeof(info->components)); ++ if (n < 0) // mark the truncation with '>' before NULL terminator ++ info->components[sizeof(info->components) - 2] = '>'; + } + if (copy_to_user(arg, info, sizeof(struct snd_ctl_card_info))) + return -EFAULT; + return 0; + } + ++static int snd_ctl_card_bytes(struct snd_card *card, ++ struct snd_ctl_card_bytes *info, ++ unsigned int __user *data_len_out) ++{ ++ unsigned int data_len; ++ ++ switch (info->type) { ++ case SND_CTL_CARD_BTYPE_COMPONENTS: ++ scoped_guard(rwsem_read, &snd_ioctl_rwsem) { ++ const char *components = card->components; ++ ++ if (!components) ++ components = ""; ++ ++ data_len = strlen(components) + 1; ++ ++ if (!info->data || info->data_allocated == 0) ++ break; ++ ++ if (info->data_allocated < data_len) ++ return -ENOMEM; ++ ++ if (copy_to_user(u64_to_user_ptr(info->data), components, data_len)) ++ return -EFAULT; ++ } ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ if (put_user(data_len, data_len_out)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static int snd_ctl_card_bytes_user(struct snd_card *card, ++ struct snd_ctl_card_bytes __user *_info) ++{ ++ struct snd_ctl_card_bytes info; ++ ++ if (copy_from_user(&info, _info, sizeof(info))) ++ return -EFAULT; ++ ++ return snd_ctl_card_bytes(card, &info, &_info->data_len); ++} ++ + static int snd_ctl_elem_list(struct snd_card *card, + struct snd_ctl_elem_list *list) + { +@@ -1990,6 +2044,8 @@ static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg + return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0; + case SNDRV_CTL_IOCTL_CARD_INFO: + return snd_ctl_card_info(card, ctl, cmd, argp); ++ case SNDRV_CTL_IOCTL_CARD_BYTES: ++ return snd_ctl_card_bytes_user(card, argp); + case SNDRV_CTL_IOCTL_ELEM_LIST: + return snd_ctl_elem_list_user(card, argp); + case SNDRV_CTL_IOCTL_ELEM_INFO: +@@ -2339,16 +2395,15 @@ EXPORT_SYMBOL_GPL(snd_ctl_disconnect_layer); + * INIT PART + */ + +-static const struct file_operations snd_ctl_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = snd_ctl_read, +- .open = snd_ctl_open, +- .release = snd_ctl_release, +- .poll = snd_ctl_poll, +- .unlocked_ioctl = snd_ctl_ioctl, +- .compat_ioctl = snd_ctl_ioctl_compat, +- .fasync = snd_ctl_fasync, ++static const struct file_operations snd_ctl_f_ops = { ++ .owner = THIS_MODULE, ++ .read = snd_ctl_read, ++ .open = snd_ctl_open, ++ .release = snd_ctl_release, ++ .poll = snd_ctl_poll, ++ .unlocked_ioctl = snd_ctl_ioctl, ++ .compat_ioctl = snd_ctl_ioctl_compat, ++ .fasync = snd_ctl_fasync, + }; + + /* call lops under rwsems; called from snd_ctl_dev_*() below() */ +diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c +--- a/sound/core/control_compat.c ++++ b/sound/core/control_compat.c +@@ -226,8 +226,8 @@ static int copy_ctl_value_from_user(struct snd_card *card, + if (type < 0) + return type; + +- if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || +- type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { ++ if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || ++ type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + s32 __user *intp = valuep; + int val; +@@ -236,7 +236,7 @@ static int copy_ctl_value_from_user(struct snd_card *card, + data->value.integer.value[i] = val; + } + } else { +- size = get_elem_size((__force snd_ctl_elem_type_t)type, count); ++ size = get_elem_size(type, count); + if (size < 0) { + dev_err(card->dev, "snd_ioctl32_ctl_elem_value: unknown type %d\n", type); + return -EINVAL; +@@ -259,8 +259,8 @@ static int copy_ctl_value_to_user(void __user *userdata, + struct snd_ctl_elem_value32 __user *data32 = userdata; + int i, size; + +- if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || +- type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { ++ if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || ++ type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + s32 __user *intp = valuep; + int val; +@@ -269,7 +269,7 @@ static int copy_ctl_value_to_user(void __user *userdata, + return -EFAULT; + } + } else { +- size = get_elem_size((__force snd_ctl_elem_type_t)type, count); ++ size = get_elem_size(type, count); + if (copy_to_user(valuep, data->value.bytes.data, size)) + return -EFAULT; + } +@@ -417,7 +417,7 @@ static int snd_ctl_elem_add_compat(struct snd_ctl_file *file, + break; + } + return snd_ctl_elem_add(file, data, replace); +-} ++} + + enum { + SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct snd_ctl_elem_list32), +@@ -446,6 +446,7 @@ static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, uns + switch (cmd) { + case SNDRV_CTL_IOCTL_PVERSION: + case SNDRV_CTL_IOCTL_CARD_INFO: ++ case SNDRV_CTL_IOCTL_CARD_BYTES: + case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: + case SNDRV_CTL_IOCTL_POWER: + case SNDRV_CTL_IOCTL_POWER_STATE: +diff --git a/sound/core/hwdep.c b/sound/core/hwdep.c +--- a/sound/core/hwdep.c ++++ b/sound/core/hwdep.c +@@ -87,7 +87,7 @@ static int snd_hwdep_open(struct inode *inode, struct file * file) + + if (!try_module_get(hw->card->module)) { + snd_card_unref(hw->card); +- return -EFAULT; ++ return -ENODEV; + } + + init_waitqueue_entry(&wait, current); +@@ -323,18 +323,17 @@ static int snd_hwdep_control_ioctl(struct snd_card *card, + + */ + +-static const struct file_operations snd_hwdep_f_ops = +-{ +- .owner = THIS_MODULE, +- .llseek = snd_hwdep_llseek, +- .read = snd_hwdep_read, +- .write = snd_hwdep_write, +- .open = snd_hwdep_open, +- .release = snd_hwdep_release, +- .poll = snd_hwdep_poll, +- .unlocked_ioctl = snd_hwdep_ioctl, +- .compat_ioctl = snd_hwdep_ioctl_compat, +- .mmap = snd_hwdep_mmap, ++static const struct file_operations snd_hwdep_f_ops = { ++ .owner = THIS_MODULE, ++ .llseek = snd_hwdep_llseek, ++ .read = snd_hwdep_read, ++ .write = snd_hwdep_write, ++ .open = snd_hwdep_open, ++ .release = snd_hwdep_release, ++ .poll = snd_hwdep_poll, ++ .unlocked_ioctl = snd_hwdep_ioctl, ++ .compat_ioctl = snd_hwdep_ioctl_compat, ++ .mmap = snd_hwdep_mmap, + }; + + static void snd_hwdep_free(struct snd_hwdep *hwdep) +diff --git a/sound/core/info.c b/sound/core/info.c +--- a/sound/core/info.c ++++ b/sound/core/info.c +@@ -78,7 +78,7 @@ static int alloc_info_private(struct snd_info_entry *entry, + if (!entry || !entry->p) + return -ENODEV; + if (!try_module_get(entry->module)) +- return -EFAULT; ++ return -ENODEV; + data = kzalloc_obj(*data); + if (!data) { + module_put(entry->module); +diff --git a/sound/core/init.c b/sound/core/init.c +--- a/sound/core/init.c ++++ b/sound/core/init.c +@@ -466,20 +466,19 @@ static int snd_disconnect_fasync(int fd, struct file *file, int on) + return -ENODEV; + } + +-static const struct file_operations snd_shutdown_f_ops = +-{ +- .owner = THIS_MODULE, +- .llseek = snd_disconnect_llseek, +- .read = snd_disconnect_read, +- .write = snd_disconnect_write, +- .release = snd_disconnect_release, +- .poll = snd_disconnect_poll, +- .unlocked_ioctl = snd_disconnect_ioctl, ++static const struct file_operations snd_shutdown_f_ops = { ++ .owner = THIS_MODULE, ++ .llseek = snd_disconnect_llseek, ++ .read = snd_disconnect_read, ++ .write = snd_disconnect_write, ++ .release = snd_disconnect_release, ++ .poll = snd_disconnect_poll, ++ .unlocked_ioctl = snd_disconnect_ioctl, + #ifdef CONFIG_COMPAT +- .compat_ioctl = snd_disconnect_ioctl, ++ .compat_ioctl = snd_disconnect_ioctl, + #endif +- .mmap = snd_disconnect_mmap, +- .fasync = snd_disconnect_fasync ++ .mmap = snd_disconnect_mmap, ++ .fasync = snd_disconnect_fasync + }; + + /** +@@ -584,12 +583,17 @@ EXPORT_SYMBOL_GPL(snd_card_disconnect_sync); + + static int snd_card_do_free(struct snd_card *card) + { ++ bool managed = card->managed; ++ + card->releasing = true; + #if IS_ENABLED(CONFIG_SND_MIXER_OSS) + if (snd_mixer_oss_notify_callback) + snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); + #endif + snd_device_free_all(card); ++ kfree(card->components); ++ card->components = NULL; ++ card->components_alloc_size = 0; + if (card->private_free) + card->private_free(card); + #ifdef CONFIG_SND_CTL_DEBUG +@@ -601,7 +605,7 @@ static int snd_card_do_free(struct snd_card *card) + } + if (card->release_completion) + complete(card->release_completion); +- if (!card->managed) ++ if (!managed) + kfree(card); + return 0; + } +@@ -722,7 +726,7 @@ static void snd_card_set_id_no_lock(struct snd_card *card, const char *src, + int len, loops; + bool is_default = false; + char *id; +- ++ + copy_valid_id_string(card, src, nid); + id = card->id; + +@@ -1031,21 +1035,44 @@ int __init snd_card_info_init(void) + * + * Return: Zero otherwise a negative error code. + */ +- ++ + int snd_component_add(struct snd_card *card, const char *component) + { + char *ptr; + int len = strlen(component); ++ unsigned int cur_len, need_len; + +- ptr = strstr(card->components, component); +- if (ptr != NULL) { +- if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */ +- return 1; ++ guard(rwsem_write)(&snd_ioctl_rwsem); ++ ++ if (card->components) { ++ ptr = strstr(card->components, component); ++ if (ptr) { ++ if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */ ++ return 1; ++ } ++ cur_len = strlen(card->components) + 1; ++ } else { ++ cur_len = 0; + } +- if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) { ++ ++ need_len = cur_len + len + 1; ++ if (need_len > 512) { + snd_BUG(); + return -ENOMEM; + } ++ ++ if (need_len > card->components_alloc_size) { ++ unsigned int new_alloc = roundup(need_len, 32); ++ ++ ptr = krealloc(card->components, new_alloc, GFP_KERNEL); ++ if (!ptr) ++ return -ENOMEM; ++ if (!card->components) ++ ptr[0] = '\0'; ++ card->components = ptr; ++ card->components_alloc_size = new_alloc; ++ } ++ + if (card->components[0] != '\0') + strcat(card->components, " "); + strcat(card->components, component); +diff --git a/sound/core/jack.c b/sound/core/jack.c +--- a/sound/core/jack.c ++++ b/sound/core/jack.c +@@ -303,41 +303,41 @@ static ssize_t jack_type_read(struct file *file, + } + + static const struct file_operations jack_type_fops = { +- .open = simple_open, +- .read = jack_type_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_type_read, ++ .llseek = default_llseek, + }; + #endif + + static const struct file_operations sw_inject_enable_fops = { +- .open = simple_open, +- .read = sw_inject_enable_read, +- .write = sw_inject_enable_write, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = sw_inject_enable_read, ++ .write = sw_inject_enable_write, ++ .llseek = default_llseek, + }; + + static const struct file_operations jackin_inject_fops = { +- .open = simple_open, +- .write = jackin_inject_write, +- .llseek = default_llseek, ++ .open = simple_open, ++ .write = jackin_inject_write, ++ .llseek = default_llseek, + }; + + static const struct file_operations jack_kctl_id_fops = { +- .open = simple_open, +- .read = jack_kctl_id_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_kctl_id_read, ++ .llseek = default_llseek, + }; + + static const struct file_operations jack_kctl_mask_bits_fops = { +- .open = simple_open, +- .read = jack_kctl_mask_bits_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_kctl_mask_bits_read, ++ .llseek = default_llseek, + }; + + static const struct file_operations jack_kctl_status_fops = { +- .open = simple_open, +- .read = jack_kctl_status_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_kctl_status_read, ++ .llseek = default_llseek, + }; + + static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, +diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c +--- a/sound/core/oss/mixer_oss.c ++++ b/sound/core/oss/mixer_oss.c +@@ -30,9 +30,7 @@ static int snd_mixer_oss_open(struct inode *inode, struct file *file) + struct snd_mixer_oss_file *fmixer; + int err; + +- err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ nonseekable_open(inode, file); + + card = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_MIXER); +@@ -60,7 +58,7 @@ static int snd_mixer_oss_open(struct inode *inode, struct file *file) + kfree(fmixer); + snd_card_file_remove(card, file); + snd_card_unref(card); +- return -EFAULT; ++ return -ENODEV; + } + snd_card_unref(card); + return 0; +@@ -407,13 +405,12 @@ static long snd_mixer_oss_ioctl_compat(struct file *file, unsigned int cmd, + * REGISTRATION PART + */ + +-static const struct file_operations snd_mixer_oss_f_ops = +-{ +- .owner = THIS_MODULE, +- .open = snd_mixer_oss_open, +- .release = snd_mixer_oss_release, +- .unlocked_ioctl = snd_mixer_oss_ioctl, +- .compat_ioctl = snd_mixer_oss_ioctl_compat, ++static const struct file_operations snd_mixer_oss_f_ops = { ++ .owner = THIS_MODULE, ++ .open = snd_mixer_oss_open, ++ .release = snd_mixer_oss_release, ++ .unlocked_ioctl = snd_mixer_oss_ioctl, ++ .compat_ioctl = snd_mixer_oss_ioctl_compat, + }; + + /* +diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c +--- a/sound/core/oss/pcm_oss.c ++++ b/sound/core/oss/pcm_oss.c +@@ -879,11 +879,11 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + _snd_pcm_hw_param_min(sparams, SNDRV_PCM_HW_PARAM_PERIODS, 2, 0); + snd_mask_none(&mask); + if (atomic_read(&substream->mmap_count)) +- snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); ++ snd_mask_set(&mask, SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); + else { +- snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED); ++ snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_INTERLEAVED); + if (!direct) +- snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); ++ snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + } + err = snd_pcm_hw_param_mask(substream, sparams, SNDRV_PCM_HW_PARAM_ACCESS, &mask); + if (err < 0) { +@@ -909,7 +909,7 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + else + sformat = snd_pcm_plug_slave_format(format, sformat_mask); + +- if ((__force int)sformat < 0 || ++ if (sformat < 0 || + !snd_mask_test_format(sformat_mask, sformat)) { + pcm_for_each_format(sformat) { + if (snd_mask_test_format(sformat_mask, sformat) && +@@ -921,7 +921,7 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + goto failure; + } + format_found: +- err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, (__force int)sformat, 0); ++ err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, sformat, 0); + if (err < 0) + goto failure; + +@@ -930,9 +930,9 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + } else { + _snd_pcm_hw_params_any(params); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS, +- (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0); ++ SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT, +- (__force int)snd_pcm_oss_format_from(runtime->oss.format), 0); ++ snd_pcm_oss_format_from(runtime->oss.format), 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS, + runtime->oss.channels, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE, +@@ -1875,7 +1875,7 @@ static int snd_pcm_oss_get_formats(struct snd_pcm_oss_file *pcm_oss_file) + format_mask = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + for (fmt = 0; fmt < 32; ++fmt) { + if (snd_mask_test(format_mask, fmt)) { +- int f = snd_pcm_oss_format_to((__force snd_pcm_format_t)fmt); ++ int f = snd_pcm_oss_format_to(fmt); + if (f >= 0) + formats |= f; + } +@@ -2495,9 +2495,7 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) + int nonblock; + wait_queue_entry_t wait; + +- err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ nonseekable_open(inode, file); + + pcm = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_PCM); +@@ -2509,7 +2507,7 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { +- err = -EFAULT; ++ err = -ENODEV; + goto __error2; + } + if (snd_task_name(current, task_name, sizeof(task_name)) < 0) { +@@ -3129,17 +3127,16 @@ static inline void snd_pcm_oss_proc_done(struct snd_pcm *pcm) + * ENTRY functions + */ + +-static const struct file_operations snd_pcm_oss_f_reg = +-{ +- .owner = THIS_MODULE, +- .read = snd_pcm_oss_read, +- .write = snd_pcm_oss_write, +- .open = snd_pcm_oss_open, +- .release = snd_pcm_oss_release, +- .poll = snd_pcm_oss_poll, +- .unlocked_ioctl = snd_pcm_oss_ioctl, +- .compat_ioctl = snd_pcm_oss_ioctl_compat, +- .mmap = snd_pcm_oss_mmap, ++static const struct file_operations snd_pcm_oss_f_reg = { ++ .owner = THIS_MODULE, ++ .read = snd_pcm_oss_read, ++ .write = snd_pcm_oss_write, ++ .open = snd_pcm_oss_open, ++ .release = snd_pcm_oss_release, ++ .poll = snd_pcm_oss_poll, ++ .unlocked_ioctl = snd_pcm_oss_ioctl, ++ .compat_ioctl = snd_pcm_oss_ioctl_compat, ++ .mmap = snd_pcm_oss_mmap, + }; + + static void register_oss_dsp(struct snd_pcm *pcm, int index) +diff --git a/sound/core/oss/pcm_plugin.c b/sound/core/oss/pcm_plugin.c +--- a/sound/core/oss/pcm_plugin.c ++++ b/sound/core/oss/pcm_plugin.c +@@ -272,13 +272,13 @@ static int snd_pcm_plug_formats(const struct snd_mask *mask, + SNDRV_PCM_FMTBIT_U24_3BE | SNDRV_PCM_FMTBIT_S24_3BE | + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE); +- snd_mask_set(&formats, (__force int)SNDRV_PCM_FORMAT_MU_LAW); ++ snd_mask_set(&formats, SNDRV_PCM_FORMAT_MU_LAW); + + if (formats.bits[0] & lower_32_bits(linfmts)) + formats.bits[0] |= lower_32_bits(linfmts); + if (formats.bits[1] & upper_32_bits(linfmts)) + formats.bits[1] |= upper_32_bits(linfmts); +- return snd_mask_test(&formats, (__force int)format); ++ return snd_mask_test(&formats, format); + } + + static const snd_pcm_format_t preferred_formats[] = { +@@ -307,20 +307,20 @@ snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, + { + int i; + +- if (snd_mask_test(format_mask, (__force int)format)) ++ if (snd_mask_test(format_mask, format)) + return format; + if (!snd_pcm_plug_formats(format_mask, format)) +- return (__force snd_pcm_format_t)-EINVAL; ++ return -EINVAL; + if (snd_pcm_format_linear(format)) { + unsigned int width = snd_pcm_format_width(format); + int unsignd = snd_pcm_format_unsigned(format) > 0; + int big = snd_pcm_format_big_endian(format) > 0; + unsigned int badness, best = -1; +- snd_pcm_format_t best_format = (__force snd_pcm_format_t)-1; ++ snd_pcm_format_t best_format = -1; + for (i = 0; i < ARRAY_SIZE(preferred_formats); i++) { + snd_pcm_format_t f = preferred_formats[i]; + unsigned int w; +- if (!snd_mask_test(format_mask, (__force int)f)) ++ if (!snd_mask_test(format_mask, f)) + continue; + w = snd_pcm_format_width(f); + if (w >= width) +@@ -334,21 +334,21 @@ snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, + best = badness; + } + } +- if ((__force int)best_format >= 0) ++ if (best_format >= 0) + return best_format; + else +- return (__force snd_pcm_format_t)-EINVAL; ++ return -EINVAL; + } else { + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: + for (i = 0; i < ARRAY_SIZE(preferred_formats); ++i) { + snd_pcm_format_t format1 = preferred_formats[i]; +- if (snd_mask_test(format_mask, (__force int)format1)) ++ if (snd_mask_test(format_mask, format1)) + return format1; + } + fallthrough; + default: +- return (__force snd_pcm_format_t)-EINVAL; ++ return -EINVAL; + } + } + } +diff --git a/sound/core/pcm.c b/sound/core/pcm.c +--- a/sound/core/pcm.c ++++ b/sound/core/pcm.c +@@ -211,11 +211,9 @@ static const char * const snd_pcm_format_names[] = { + */ + const char *snd_pcm_format_name(snd_pcm_format_t format) + { +- unsigned int format_num = (__force unsigned int)format; +- +- if (format_num >= ARRAY_SIZE(snd_pcm_format_names) || !snd_pcm_format_names[format_num]) ++ if (format >= ARRAY_SIZE(snd_pcm_format_names) || !snd_pcm_format_names[format]) + return "Unknown"; +- return snd_pcm_format_names[format_num]; ++ return snd_pcm_format_names[format]; + } + EXPORT_SYMBOL_GPL(snd_pcm_format_name); + +@@ -275,12 +273,12 @@ static const char *snd_pcm_stream_name(int stream) + + static const char *snd_pcm_access_name(snd_pcm_access_t access) + { +- return snd_pcm_access_names[(__force int)access]; ++ return snd_pcm_access_names[access]; + } + + static const char *snd_pcm_subformat_name(snd_pcm_subformat_t subformat) + { +- return snd_pcm_subformat_names[(__force int)subformat]; ++ return snd_pcm_subformat_names[subformat]; + } + + static const char *snd_pcm_tstamp_mode_name(int mode) +@@ -290,7 +288,7 @@ static const char *snd_pcm_tstamp_mode_name(int mode) + + static const char *snd_pcm_state_name(snd_pcm_state_t state) + { +- return snd_pcm_state_names[(__force int)state]; ++ return snd_pcm_state_names[state]; + } + + #if IS_ENABLED(CONFIG_SND_PCM_OSS) +diff --git a/sound/core/pcm_misc.c b/sound/core/pcm_misc.c +--- a/sound/core/pcm_misc.c ++++ b/sound/core/pcm_misc.c +@@ -24,15 +24,12 @@ struct pcm_format_data { + unsigned char silence[8]; /* silence data to fill */ + }; + +-/* we do lots of calculations on snd_pcm_format_t; shut up sparse */ +-#define INT __force int +- + static bool valid_format(snd_pcm_format_t format) + { +- return (INT)format >= 0 && (INT)format <= (INT)SNDRV_PCM_FORMAT_LAST; ++ return format >= 0 && format <= SNDRV_PCM_FORMAT_LAST; + } + +-static const struct pcm_format_data pcm_formats[(INT)SNDRV_PCM_FORMAT_LAST+1] = { ++static const struct pcm_format_data pcm_formats[SNDRV_PCM_FORMAT_LAST+1] = { + [SNDRV_PCM_FORMAT_S8] = { + .width = 8, .phys = 8, .le = -1, .signd = 1, + .silence = {}, +@@ -251,7 +248,7 @@ int snd_pcm_format_signed(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].signd; ++ val = pcm_formats[format].signd; + if (val < 0) + return -EINVAL; + return val; +@@ -300,7 +297,7 @@ int snd_pcm_format_little_endian(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].le; ++ val = pcm_formats[format].le; + if (val < 0) + return -EINVAL; + return val; +@@ -337,7 +334,7 @@ int snd_pcm_format_width(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].width; ++ val = pcm_formats[format].width; + if (!val) + return -EINVAL; + return val; +@@ -356,7 +353,7 @@ int snd_pcm_format_physical_width(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].phys; ++ val = pcm_formats[format].phys; + if (!val) + return -EINVAL; + return val; +@@ -390,9 +387,9 @@ const unsigned char *snd_pcm_format_silence_64(snd_pcm_format_t format) + { + if (!valid_format(format)) + return NULL; +- if (! pcm_formats[(INT)format].phys) ++ if (! pcm_formats[format].phys) + return NULL; +- return pcm_formats[(INT)format].silence; ++ return pcm_formats[format].silence; + } + EXPORT_SYMBOL(snd_pcm_format_silence_64); + +@@ -416,12 +413,12 @@ int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int + return -EINVAL; + if (samples == 0) + return 0; +- width = pcm_formats[(INT)format].phys; /* physical width */ ++ width = pcm_formats[format].phys; /* physical width */ + if (!width) + return -EINVAL; +- pat = pcm_formats[(INT)format].silence; ++ pat = pcm_formats[format].silence; + /* signed or 1 byte data */ +- if (pcm_formats[(INT)format].signd == 1 || width <= 8) { ++ if (pcm_formats[format].signd == 1 || width <= 8) { + unsigned int bytes = samples * width / 8; + memset(data, *pat, bytes); + return 0; +diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c +--- a/sound/core/pcm_native.c ++++ b/sound/core/pcm_native.c +@@ -257,7 +257,7 @@ int snd_pcm_info_user(struct snd_pcm_substream *substream, + } + + /* macro for simplified cast */ +-#define PARAM_MASK_BIT(b) (1U << (__force int)(b)) ++#define PARAM_MASK_BIT(b) (1U << (b)) + + static bool hw_support_mmap(struct snd_pcm_substream *substream) + { +@@ -489,7 +489,7 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream, + params->msbits = snd_interval_value(i); + m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + if (snd_mask_single(m)) { +- snd_pcm_format_t format = (__force snd_pcm_format_t)snd_mask_min(m); ++ snd_pcm_format_t format = snd_mask_min(m); + params->msbits = snd_pcm_format_width(format); + } + } +@@ -497,13 +497,13 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream, + if (params->msbits) { + m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + if (snd_mask_single(m)) { +- snd_pcm_format_t format = (__force snd_pcm_format_t)snd_mask_min(m); ++ snd_pcm_format_t format = snd_mask_min(m); + + if (snd_pcm_format_linear(format) && + snd_pcm_format_width(format) != params->msbits) { + m_rw = hw_param_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT); + snd_mask_reset(m_rw, +- (__force unsigned)SNDRV_PCM_SUBFORMAT_MSBITS_MAX); ++ SNDRV_PCM_SUBFORMAT_MSBITS_MAX); + if (snd_mask_empty(m_rw)) + return -EINVAL; + } +@@ -1252,7 +1252,7 @@ static void snd_pcm_trigger_tstamp(struct snd_pcm_substream *substream) + runtime->trigger_master = NULL; + } + +-#define ACTION_ARG_IGNORE (__force snd_pcm_state_t)0 ++#define ACTION_ARG_IGNORE 0 + + struct action_ops { + int (*pre_action)(struct snd_pcm_substream *substream, +@@ -1637,7 +1637,7 @@ EXPORT_SYMBOL_GPL(snd_pcm_stop_xrun); + /* + * pause callbacks: pass boolean (to start pause or resume) as state argument + */ +-#define pause_pushed(state) (__force bool)(state) ++#define pause_pushed(state) (bool)(state) + + static int snd_pcm_pre_pause(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +@@ -1709,14 +1709,14 @@ static const struct action_ops snd_pcm_action_pause = { + static int snd_pcm_pause(struct snd_pcm_substream *substream, bool push) + { + return snd_pcm_action(&snd_pcm_action_pause, substream, +- (__force snd_pcm_state_t)push); ++ (snd_pcm_state_t)push); + } + + static int snd_pcm_pause_lock_irq(struct snd_pcm_substream *substream, + bool push) + { + return snd_pcm_action_lock_irq(&snd_pcm_action_pause, substream, +- (__force snd_pcm_state_t)push); ++ (snd_pcm_state_t)push); + } + + #ifdef CONFIG_PM +@@ -1984,7 +1984,7 @@ static int snd_pcm_pre_prepare(struct snd_pcm_substream *substream, + snd_pcm_state_t state) + { + snd_pcm_state_t cur_state = snd_pcm_get_state(substream); +- int f_flags = (__force int)state; ++ int f_flags = state; + + if (cur_state == SNDRV_PCM_STATE_OPEN || + cur_state == SNDRV_PCM_STATE_DISCONNECTED) +@@ -2052,7 +2052,7 @@ static int snd_pcm_prepare(struct snd_pcm_substream *substream, + + return snd_pcm_action_nonatomic(&snd_pcm_action_prepare, + substream, +- (__force snd_pcm_state_t)f_flags); ++ (snd_pcm_state_t)f_flags); + } + + /* +@@ -2463,7 +2463,7 @@ static int snd_pcm_hw_rule_format(struct snd_pcm_hw_params *params, + if (bits <= 0) + continue; /* ignore invalid formats */ + if ((unsigned)bits < i->min || (unsigned)bits > i->max) +- snd_mask_reset(&m, (__force unsigned)k); ++ snd_mask_reset(&m, k); + } + return snd_mask_refine(mask, &m); + } +@@ -2545,16 +2545,16 @@ static int snd_pcm_hw_rule_subformats(struct snd_pcm_hw_params *params, + + snd_mask_none(&m); + /* All PCMs support at least the default STD subformat. */ +- snd_mask_set(&m, (__force unsigned)SNDRV_PCM_SUBFORMAT_STD); ++ snd_mask_set(&m, SNDRV_PCM_SUBFORMAT_STD); + + pcm_for_each_format(f) { +- if (!snd_mask_test(fmask, (__force unsigned)f)) ++ if (!snd_mask_test(fmask, f)) + continue; + + if (f == SNDRV_PCM_FORMAT_S32_LE && *subformats) + m.bits[0] |= *subformats; + else if (snd_pcm_format_linear(f)) +- snd_mask_set(&m, (__force unsigned)SNDRV_PCM_SUBFORMAT_MSBITS_MAX); ++ snd_mask_set(&m, SNDRV_PCM_SUBFORMAT_MSBITS_MAX); + } + + return snd_mask_refine(sfmask, &m); +@@ -2882,9 +2882,10 @@ static int snd_pcm_open_file(struct file *file, + static int snd_pcm_playback_open(struct inode *inode, struct file *file) + { + struct snd_pcm *pcm; +- int err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ int err; ++ ++ nonseekable_open(inode, file); ++ + pcm = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_PLAYBACK); + err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK); +@@ -2896,9 +2897,10 @@ static int snd_pcm_playback_open(struct inode *inode, struct file *file) + static int snd_pcm_capture_open(struct inode *inode, struct file *file) + { + struct snd_pcm *pcm; +- int err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ int err; ++ ++ nonseekable_open(inode, file); ++ + pcm = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_CAPTURE); + err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE); +@@ -2920,7 +2922,7 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream) + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { +- err = -EFAULT; ++ err = -ENODEV; + goto __error2; + } + init_waitqueue_entry(&wait, current); +@@ -3916,6 +3918,8 @@ static vm_fault_t snd_pcm_mmap_data_fault(struct vm_fault *vmf) + if (substream == NULL) + return VM_FAULT_SIGBUS; + runtime = substream->runtime; ++ if (runtime->state == SNDRV_PCM_STATE_DISCONNECTED) ++ return VM_FAULT_SIGBUS; + offset = vmf->pgoff << PAGE_SHIFT; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); + if (offset > dma_bytes - PAGE_SIZE) +@@ -4241,29 +4245,29 @@ static unsigned long snd_pcm_get_unmapped_area(struct file *file, + + const struct file_operations snd_pcm_f_ops[2] = { + { +- .owner = THIS_MODULE, +- .write = snd_pcm_write, +- .write_iter = snd_pcm_writev, +- .open = snd_pcm_playback_open, +- .release = snd_pcm_release, +- .poll = snd_pcm_poll, +- .unlocked_ioctl = snd_pcm_ioctl, +- .compat_ioctl = snd_pcm_ioctl_compat, +- .mmap = snd_pcm_mmap, +- .fasync = snd_pcm_fasync, +- .get_unmapped_area = snd_pcm_get_unmapped_area, ++ .owner = THIS_MODULE, ++ .write = snd_pcm_write, ++ .write_iter = snd_pcm_writev, ++ .open = snd_pcm_playback_open, ++ .release = snd_pcm_release, ++ .poll = snd_pcm_poll, ++ .unlocked_ioctl = snd_pcm_ioctl, ++ .compat_ioctl = snd_pcm_ioctl_compat, ++ .mmap = snd_pcm_mmap, ++ .fasync = snd_pcm_fasync, ++ .get_unmapped_area = snd_pcm_get_unmapped_area, + }, + { +- .owner = THIS_MODULE, +- .read = snd_pcm_read, +- .read_iter = snd_pcm_readv, +- .open = snd_pcm_capture_open, +- .release = snd_pcm_release, +- .poll = snd_pcm_poll, +- .unlocked_ioctl = snd_pcm_ioctl, +- .compat_ioctl = snd_pcm_ioctl_compat, +- .mmap = snd_pcm_mmap, +- .fasync = snd_pcm_fasync, +- .get_unmapped_area = snd_pcm_get_unmapped_area, ++ .owner = THIS_MODULE, ++ .read = snd_pcm_read, ++ .read_iter = snd_pcm_readv, ++ .open = snd_pcm_capture_open, ++ .release = snd_pcm_release, ++ .poll = snd_pcm_poll, ++ .unlocked_ioctl = snd_pcm_ioctl, ++ .compat_ioctl = snd_pcm_ioctl_compat, ++ .mmap = snd_pcm_mmap, ++ .fasync = snd_pcm_fasync, ++ .get_unmapped_area = snd_pcm_get_unmapped_area, + } + }; +diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c +--- a/sound/core/rawmidi.c ++++ b/sound/core/rawmidi.c +@@ -408,9 +408,10 @@ static int rawmidi_open_priv(struct snd_rawmidi *rmidi, int subdevice, int mode, + return 0; + } + +-/* called from sound/core/seq/seq_midi.c */ +-int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, +- int mode, struct snd_rawmidi_file *rfile) ++/* called from sound/core/seq/seq_midi.c and sound/core/ump.c */ ++int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, ++ int mode, struct snd_rawmidi_file *rfile, ++ int depth) + { + int err; + +@@ -419,13 +420,14 @@ int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, + if (!try_module_get(rmidi->card->module)) + return -ENXIO; + +- guard(mutex)(&rmidi->open_mutex); ++ mutex_lock_nested(&rmidi->open_mutex, depth); + err = rawmidi_open_priv(rmidi, subdevice, mode, rfile); + if (err < 0) + module_put(rmidi->card->module); ++ mutex_unlock(&rmidi->open_mutex); + return err; + } +-EXPORT_SYMBOL(snd_rawmidi_kernel_open); ++EXPORT_SYMBOL(snd_rawmidi_kernel_open_nested); + + static int snd_rawmidi_open(struct inode *inode, struct file *file) + { +@@ -441,9 +443,7 @@ static int snd_rawmidi_open(struct inode *inode, struct file *file) + if ((file->f_flags & O_APPEND) && !(file->f_flags & O_NONBLOCK)) + return -EINVAL; /* invalid combination */ + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + if (maj == snd_major) { + rmidi = snd_lookup_minor_data(iminor(inode), +@@ -1784,14 +1784,14 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, + */ + + static const struct file_operations snd_rawmidi_f_ops = { +- .owner = THIS_MODULE, +- .read = snd_rawmidi_read, +- .write = snd_rawmidi_write, +- .open = snd_rawmidi_open, +- .release = snd_rawmidi_release, +- .poll = snd_rawmidi_poll, +- .unlocked_ioctl = snd_rawmidi_ioctl, +- .compat_ioctl = snd_rawmidi_ioctl_compat, ++ .owner = THIS_MODULE, ++ .read = snd_rawmidi_read, ++ .write = snd_rawmidi_write, ++ .open = snd_rawmidi_open, ++ .release = snd_rawmidi_release, ++ .poll = snd_rawmidi_poll, ++ .unlocked_ioctl = snd_rawmidi_ioctl, ++ .compat_ioctl = snd_rawmidi_ioctl_compat, + }; + + static int snd_rawmidi_alloc_substreams(struct snd_rawmidi *rmidi, +diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c +--- a/sound/core/seq/oss/seq_oss.c ++++ b/sound/core/seq/oss/seq_oss.c +@@ -206,17 +206,16 @@ odev_poll(struct file *file, poll_table * wait) + * registration of sequencer minor device + */ + +-static const struct file_operations seq_oss_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = odev_read, +- .write = odev_write, +- .open = odev_open, +- .release = odev_release, +- .poll = odev_poll, +- .unlocked_ioctl = odev_ioctl, +- .compat_ioctl = odev_ioctl_compat, +- .llseek = noop_llseek, ++static const struct file_operations seq_oss_f_ops = { ++ .owner = THIS_MODULE, ++ .read = odev_read, ++ .write = odev_write, ++ .open = odev_open, ++ .release = odev_release, ++ .poll = odev_poll, ++ .unlocked_ioctl = odev_ioctl, ++ .compat_ioctl = odev_ioctl_compat, ++ .llseek = noop_llseek, + }; + + static int __init +diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c +--- a/sound/core/seq/seq_clientmgr.c ++++ b/sound/core/seq/seq_clientmgr.c +@@ -59,7 +59,7 @@ static DEFINE_MUTEX(register_mutex); + * client table + */ + static char clienttablock[SNDRV_SEQ_MAX_CLIENTS]; +-static struct snd_seq_client *clienttab[SNDRV_SEQ_MAX_CLIENTS]; ++static struct snd_seq_client __rcu *clienttab[SNDRV_SEQ_MAX_CLIENTS]; + static struct snd_seq_usage client_usage; + + /* +@@ -95,15 +95,23 @@ static inline int snd_seq_write_pool_allocated(struct snd_seq_client *client) + return snd_seq_total_cells(client->pool) > 0; + } + +-/* return pointer to client structure for specified id */ +-static struct snd_seq_client *clientptr(int clientid) ++/* return pointer to client structure for specified id; call under RCU read-lock */ ++static struct snd_seq_client *__clientptr(int clientid) + { + if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { + pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n", + clientid); + return NULL; + } +- return clienttab[clientid]; ++ return rcu_dereference_check(clienttab[clientid], ++ lockdep_is_held(&clients_lock)); ++} ++ ++/* return pointer to client structure for specified id */ ++static struct snd_seq_client *clientptr(int clientid) ++{ ++ guard(rcu)(); ++ return __clientptr(clientid); + } + + static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) +@@ -115,8 +123,8 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) + clientid); + return NULL; + } +- scoped_guard(spinlock_irqsave, &clients_lock) { +- client = clientptr(clientid); ++ scoped_guard(rcu) { ++ client = __clientptr(clientid); + if (client) + return snd_seq_client_ref(client); + if (clienttablock[clientid]) +@@ -150,8 +158,8 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) + snd_seq_device_load_drivers(); + } + } +- scoped_guard(spinlock_irqsave, &clients_lock) { +- client = clientptr(clientid); ++ scoped_guard(rcu) { ++ client = __clientptr(clientid); + if (client) + return snd_seq_client_ref(client); + } +@@ -212,7 +220,6 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) + } + client->type = NO_CLIENT; + snd_use_lock_init(&client->use_lock); +- rwlock_init(&client->ports_lock); + mutex_init(&client->ports_mutex); + INIT_LIST_HEAD(&client->ports_list_head); + mutex_init(&client->ioctl_mutex); +@@ -224,14 +231,17 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) + for (c = SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN; + c < SNDRV_SEQ_MAX_CLIENTS; + c++) { +- if (clienttab[c] || clienttablock[c]) ++ if (rcu_access_pointer(clienttab[c]) || clienttablock[c]) + continue; +- clienttab[client->number = c] = client; ++ client->number = c; ++ rcu_assign_pointer(clienttab[c], client); + return client; + } + } else { +- if (clienttab[client_index] == NULL && !clienttablock[client_index]) { +- clienttab[client->number = client_index] = client; ++ if (rcu_access_pointer(clienttab[client_index]) == NULL && ++ !clienttablock[client_index]) { ++ client->number = client_index; ++ rcu_assign_pointer(clienttab[client_index], client); + return client; + } + } +@@ -249,10 +259,16 @@ static int seq_free_client1(struct snd_seq_client *client) + return 0; + scoped_guard(spinlock_irq, &clients_lock) { + clienttablock[client->number] = 1; +- clienttab[client->number] = NULL; ++ rcu_assign_pointer(clienttab[client->number], NULL); + } + snd_seq_delete_all_ports(client); + snd_seq_queue_client_leave(client->number); ++ /* the client has been unpublished from the table; wait for a grace ++ * period so that lockless readers (snd_seq_client_use_ptr()) that ++ * observed the old pointer can no longer take a new use_lock ++ * reference, then drain the outstanding references before freeing ++ */ ++ synchronize_rcu(); + snd_use_lock_sync(&client->use_lock); + if (client->pool) + snd_seq_pool_delete(&client->pool); +@@ -296,11 +312,8 @@ static int snd_seq_open(struct inode *inode, struct file *file) + int c, mode; /* client id */ + struct snd_seq_client *client; + struct snd_seq_user_client *user; +- int err; + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + scoped_guard(mutex, ®ister_mutex) { + client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS); +@@ -517,9 +530,8 @@ static struct snd_seq_client *get_event_dest_client(struct snd_seq_event *event) + * Return the error event. + * + * If the receiver client is a user client, the original event is +- * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. If +- * the original event is also variable length, the external data is +- * copied after the event record. ++ * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. The ++ * external data of a variable length event is not copied along. + * If the receiver client is a kernel client, the original event is + * quoted in SNDRV_SEQ_EVENT_KERNEL_ERROR, since this requires no extra + * kmalloc. +@@ -528,7 +540,7 @@ static int bounce_error_event(struct snd_seq_client *client, + struct snd_seq_event *event, + int err, int atomic, int hop) + { +- struct snd_seq_event bounce_ev; ++ struct snd_seq_event bounce_ev, quoted; + int result; + + if (client == NULL || +@@ -548,15 +560,19 @@ static int bounce_error_event(struct snd_seq_client *client, + * For user clients, send SNDRV_SEQ_EVENT_BOUNCE with the + * original event embedded as variable-length data. This + * avoids exposing data.quote.event (a kernel pointer) to +- * userspace. The variable-length path in snd_seq_event_dup() +- * copies the event data from data.ext.ptr into chained cells, +- * and snd_seq_expand_var_event() copies only the data content +- * -- never the pointer -- to userspace. ++ * userspace. Sanitise the embedded copy too - a queued ++ * variable-length event carries the address of its own ++ * extension cell, and the payload goes out verbatim. + */ ++ quoted = *event; ++ if (snd_seq_ev_is_variable("ed)) { ++ quoted.data.ext.len &= ~SNDRV_SEQ_EXT_MASK; ++ quoted.data.ext.ptr = NULL; ++ } + bounce_ev.type = SNDRV_SEQ_EVENT_BOUNCE; + bounce_ev.flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + bounce_ev.data.ext.len = sizeof(struct snd_seq_event); +- bounce_ev.data.ext.ptr = (char *)event; ++ bounce_ev.data.ext.ptr = (char *)"ed; + } else { + /* + * For kernel clients, quote the event pointer directly. +@@ -720,10 +736,11 @@ static int __deliver_to_subscribers(struct snd_seq_client *client, + + /* lock list */ + if (atomic) +- read_lock(&grp->list_lock); ++ rcu_read_lock(); + else + down_read_nested(&grp->list_mutex, hop); +- list_for_each_entry(subs, &grp->list_head, src_list) { ++ hlist_for_each_entry_rcu(subs, &grp->list_head, src_list, ++ lockdep_is_held(&grp->list_mutex)) { + /* both ports ready? */ + if (atomic_read(&subs->ref_count) != 2) + continue; +@@ -744,7 +761,7 @@ static int __deliver_to_subscribers(struct snd_seq_client *client, + memcpy(event, &event_saved, saved_size); + } + if (atomic) +- read_unlock(&grp->list_lock); ++ rcu_read_unlock(); + else + up_read(&grp->list_mutex); + memcpy(event, &event_saved, saved_size); +@@ -1941,7 +1958,7 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) + { + struct snd_seq_query_subs *subs = arg; + struct snd_seq_port_subs_info *group; +- struct list_head *p; ++ struct hlist_node *p; + int i; + + struct snd_seq_client *cptr __free(snd_seq_client) = +@@ -1968,15 +1985,15 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) + /* search for the subscriber */ + subs->num_subs = group->count; + i = 0; +- list_for_each(p, &group->list_head) { ++ hlist_for_each(p, &group->list_head) { + if (i++ == subs->index) { + /* found! */ + struct snd_seq_subscribers *s; + if (subs->type == SNDRV_SEQ_QUERY_SUBS_READ) { +- s = list_entry(p, struct snd_seq_subscribers, src_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, src_list); + subs->addr = s->info.dest; + } else { +- s = list_entry(p, struct snd_seq_subscribers, dest_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, dest_list); + subs->addr = s->info.sender; + } + subs->flags = s->info.flags; +@@ -2532,19 +2549,19 @@ static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer, + struct snd_seq_port_subs_info *group, + int is_src, char *msg) + { +- struct list_head *p; ++ struct hlist_node *p; + struct snd_seq_subscribers *s; + int count = 0; + + guard(rwsem_read)(&group->list_mutex); +- if (list_empty(&group->list_head)) ++ if (hlist_empty(&group->list_head)) + return; + snd_iprintf(buffer, msg); +- list_for_each(p, &group->list_head) { ++ hlist_for_each(p, &group->list_head) { + if (is_src) +- s = list_entry(p, struct snd_seq_subscribers, src_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, src_list); + else +- s = list_entry(p, struct snd_seq_subscribers, dest_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, dest_list); + if (count++) + snd_iprintf(buffer, ", "); + snd_iprintf(buffer, "%d:%d", +@@ -2666,16 +2683,15 @@ void snd_seq_info_clients_read(struct snd_info_entry *entry, + * REGISTRATION PART + */ + +-static const struct file_operations snd_seq_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = snd_seq_read, +- .write = snd_seq_write, +- .open = snd_seq_open, +- .release = snd_seq_release, +- .poll = snd_seq_poll, +- .unlocked_ioctl = snd_seq_ioctl, +- .compat_ioctl = snd_seq_ioctl_compat, ++static const struct file_operations snd_seq_f_ops = { ++ .owner = THIS_MODULE, ++ .read = snd_seq_read, ++ .write = snd_seq_write, ++ .open = snd_seq_open, ++ .release = snd_seq_release, ++ .poll = snd_seq_poll, ++ .unlocked_ioctl = snd_seq_ioctl, ++ .compat_ioctl = snd_seq_ioctl_compat, + }; + + static struct device *seq_dev; +diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h +--- a/sound/core/seq/seq_clientmgr.h ++++ b/sound/core/seq/seq_clientmgr.h +@@ -49,7 +49,6 @@ struct snd_seq_client { + /* ports */ + int num_ports; /* number of ports */ + struct list_head ports_list_head; +- rwlock_t ports_lock; + struct mutex ports_mutex; + struct mutex ioctl_mutex; + int convert32; /* convert 32->64bit */ +diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c +--- a/sound/core/seq/seq_midi.c ++++ b/sound/core/seq/seq_midi.c +@@ -42,9 +42,11 @@ struct seq_midisynth { + struct snd_rawmidi *rmidi; + int device; + int subdevice; ++ struct snd_rawmidi_substream __rcu *input_substream; ++ snd_use_lock_t input_use_lock; /* in-flight event_input users */ + struct snd_rawmidi_file input_rfile; +- spinlock_t output_lock; /* protects output_rfile publication */ + snd_use_lock_t output_use_lock; /* in-flight event_input users */ ++ struct snd_rawmidi_substream __rcu *output_substream; + struct snd_rawmidi_file output_rfile; + int seq_client; + int seq_port; +@@ -76,6 +78,14 @@ static void snd_midi_input_event(struct snd_rawmidi_substream *substream) + msynth = runtime->private_data; + if (msynth == NULL) + return; ++ ++ scoped_guard(rcu) { ++ if (rcu_dereference(msynth->input_substream) != substream) ++ return; ++ ++ snd_use_lock_use(&msynth->input_use_lock); ++ } ++ + memset(&ev, 0, sizeof(ev)); + while (runtime->avail > 0) { + res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf)); +@@ -95,6 +105,8 @@ static void snd_midi_input_event(struct snd_rawmidi_substream *substream) + memset(&ev, 0, sizeof(ev)); + } + } ++ ++ snd_use_lock_free(&msynth->input_use_lock); + } + + static int dump_midi(struct snd_rawmidi_substream *substream, const char *buf, int count) +@@ -134,8 +146,8 @@ static int event_process_midi(struct snd_seq_event *ev, int direct, + if (snd_BUG_ON(!msynth)) + return -EINVAL; + +- scoped_guard(spinlock_irqsave, &msynth->output_lock) { +- substream = msynth->output_rfile.output; ++ scoped_guard(rcu) { ++ substream = rcu_dereference(msynth->output_substream); + if (!substream) + return -ENODEV; + snd_use_lock_use(&msynth->output_use_lock); +@@ -177,7 +189,7 @@ static int snd_seq_midisynth_new(struct seq_midisynth *msynth, + msynth->card = card; + msynth->device = device; + msynth->subdevice = subdevice; +- spin_lock_init(&msynth->output_lock); ++ snd_use_lock_init(&msynth->input_use_lock); + snd_use_lock_init(&msynth->output_use_lock); + return 0; + } +@@ -188,28 +200,31 @@ static int midisynth_subscribe(void *private_data, struct snd_seq_port_subscribe + int err; + struct seq_midisynth *msynth = private_data; + struct snd_rawmidi_runtime *runtime; ++ struct snd_rawmidi_file rfile = {}; + struct snd_rawmidi_params params; + + /* open midi port */ + err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice, + SNDRV_RAWMIDI_LFLG_INPUT, +- &msynth->input_rfile); ++ &rfile); + if (err < 0) { + pr_debug("ALSA: seq_midi: midi input open failed!!!\n"); + return err; + } +- runtime = msynth->input_rfile.input->runtime; ++ runtime = rfile.input->runtime; + memset(¶ms, 0, sizeof(params)); + params.avail_min = 1; + params.buffer_size = input_buffer_size; +- err = snd_rawmidi_input_params(msynth->input_rfile.input, ¶ms); ++ err = snd_rawmidi_input_params(rfile.input, ¶ms); + if (err < 0) { +- snd_rawmidi_kernel_release(&msynth->input_rfile); ++ snd_rawmidi_kernel_release(&rfile); + return err; + } + snd_midi_event_reset_encode(msynth->parser); + runtime->event = snd_midi_input_event; + runtime->private_data = msynth; ++ msynth->input_rfile = rfile; ++ rcu_assign_pointer(msynth->input_substream, rfile.input); + snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0); + return 0; + } +@@ -219,10 +234,19 @@ static int midisynth_unsubscribe(void *private_data, struct snd_seq_port_subscri + { + int err; + struct seq_midisynth *msynth = private_data; ++ struct snd_rawmidi_file rfile; + +- if (snd_BUG_ON(!msynth->input_rfile.input)) ++ rcu_assign_pointer(msynth->input_substream, NULL); ++ synchronize_rcu(); ++ snd_use_lock_sync(&msynth->input_use_lock); ++ ++ rfile = msynth->input_rfile; ++ msynth->input_rfile = (struct snd_rawmidi_file){}; ++ ++ if (snd_BUG_ON(!rfile.input)) + return -EINVAL; +- err = snd_rawmidi_kernel_release(&msynth->input_rfile); ++ ++ err = snd_rawmidi_kernel_release(&rfile); + return err; + } + +@@ -252,8 +276,8 @@ static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info + return err; + } + snd_midi_event_reset_decode(msynth->parser); +- scoped_guard(spinlock_irqsave, &msynth->output_lock) +- msynth->output_rfile = rfile; ++ msynth->output_rfile = rfile; ++ rcu_assign_pointer(msynth->output_substream, rfile.output); + return 0; + } + +@@ -261,17 +285,16 @@ static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info + static int midisynth_unuse(void *private_data, struct snd_seq_port_subscribe *info) + { + struct seq_midisynth *msynth = private_data; +- struct snd_rawmidi_file rfile = {}; ++ struct snd_rawmidi_file rfile; + +- scoped_guard(spinlock_irqsave, &msynth->output_lock) { +- rfile = msynth->output_rfile; +- msynth->output_rfile = (struct snd_rawmidi_file){}; +- } ++ rcu_assign_pointer(msynth->output_substream, NULL); ++ synchronize_rcu(); ++ snd_use_lock_sync(&msynth->output_use_lock); ++ rfile = msynth->output_rfile; ++ msynth->output_rfile = (struct snd_rawmidi_file){}; + + if (snd_BUG_ON(!rfile.output)) + return -EINVAL; +- +- snd_use_lock_sync(&msynth->output_use_lock); + snd_rawmidi_drain_output(rfile.output); + return snd_rawmidi_kernel_release(&rfile); + } +diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c +--- a/sound/core/seq/seq_ports.c ++++ b/sound/core/seq/seq_ports.c +@@ -48,8 +48,8 @@ struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, + + if (client == NULL) + return NULL; +- guard(read_lock)(&client->ports_lock); +- list_for_each_entry(port, &client->ports_list_head, list) { ++ guard(rcu)(); ++ list_for_each_entry_rcu(port, &client->ports_list_head, list) { + if (port->addr.port == num) { + if (port->closing) + break; /* deleting now */ +@@ -71,8 +71,8 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl + + num = pinfo->addr.port; + found = NULL; +- guard(read_lock)(&client->ports_lock); +- list_for_each_entry(port, &client->ports_list_head, list) { ++ guard(rcu)(); ++ list_for_each_entry_rcu(port, &client->ports_list_head, list) { + if ((port->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) && + !check_inactive) + continue; /* skip inactive ports */ +@@ -98,10 +98,9 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl + /* initialize snd_seq_port_subs_info */ + static void port_subs_info_init(struct snd_seq_port_subs_info *grp) + { +- INIT_LIST_HEAD(&grp->list_head); ++ INIT_HLIST_HEAD(&grp->list_head); + grp->count = 0; + grp->exclusive = 0; +- rwlock_init(&grp->list_lock); + init_rwsem(&grp->list_mutex); + grp->open = NULL; + grp->close = NULL; +@@ -154,7 +153,6 @@ int snd_seq_insert_port(struct snd_seq_client *client, int port, + + num = max(port, 0); + guard(mutex)(&client->ports_mutex); +- guard(write_lock_irq)(&client->ports_lock); + struct list_head *insert_before = &client->ports_list_head; + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) +@@ -166,12 +164,13 @@ int snd_seq_insert_port(struct snd_seq_client *client, int port, + if (port < 0) /* auto-probe mode */ + num = p->addr.port + 1; + } +- /* insert the new port */ +- list_add_tail(&new_port->list, insert_before); +- client->num_ports++; ++ /* finish initializing the port before publishing it to RCU readers */ + new_port->addr.port = num; /* store the port number in the port */ + if (!new_port->name[0]) + sprintf(new_port->name, "port-%d", num); ++ /* insert the new port */ ++ list_add_tail_rcu(&new_port->list, insert_before); ++ client->num_ports++; + + return num; + } +@@ -202,12 +201,12 @@ static void delete_and_unsubscribe_port(struct snd_seq_client *client, + bool is_src, bool ack); + + static inline struct snd_seq_subscribers * +-get_subscriber(struct list_head *p, bool is_src) ++get_subscriber(struct hlist_node *p, bool is_src) + { + if (is_src) +- return list_entry(p, struct snd_seq_subscribers, src_list); ++ return hlist_entry(p, struct snd_seq_subscribers, src_list); + else +- return list_entry(p, struct snd_seq_subscribers, dest_list); ++ return hlist_entry(p, struct snd_seq_subscribers, dest_list); + } + + /* +@@ -219,9 +218,9 @@ static void clear_subscriber_list(struct snd_seq_client *client, + struct snd_seq_port_subs_info *grp, + int is_src) + { +- struct list_head *p, *n; ++ struct hlist_node *p, *n; + +- list_for_each_safe(p, n, &grp->list_head) { ++ hlist_for_each_safe(p, n, &grp->list_head) { + struct snd_seq_subscribers *subs; + + subs = get_subscriber(p, is_src); +@@ -238,13 +237,13 @@ static void clear_subscriber_list(struct snd_seq_client *client, + * remove the subscriber info + */ + if (atomic_dec_and_test(&subs->ref_count)) +- kfree(subs); ++ kfree_rcu(subs, rcu); + continue; + } + + /* ok we got the connected port */ + delete_and_unsubscribe_port(c, aport, subs, !is_src, true); +- kfree(subs); ++ kfree_rcu(subs, rcu); + } + } + +@@ -254,7 +253,13 @@ static int port_delete(struct snd_seq_client *client, + { + /* set closing flag and wait for all port access are gone */ + port->closing = 1; +- snd_use_lock_sync(&port->use_lock); ++ /* the port has already been unlinked from the client's port list; ++ * wait for a grace period so that RCU readers still traversing the ++ * list can no longer take a new use_lock reference, then drain the ++ * outstanding references before freeing ++ */ ++ synchronize_rcu(); ++ snd_use_lock_sync(&port->use_lock); + + /* clear subscribers info */ + clear_subscriber_list(client, port, &port->c_src, true); +@@ -277,11 +282,10 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) + struct snd_seq_client_port *found = NULL, *p; + + scoped_guard(mutex, &client->ports_mutex) { +- guard(write_lock_irq)(&client->ports_lock); + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) { + /* ok found. delete from the list at first */ +- list_del(&p->list); ++ list_del_rcu(&p->list); + client->num_ports--; + found = p; + break; +@@ -297,26 +301,16 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) + /* delete the all ports belonging to the given client */ + int snd_seq_delete_all_ports(struct snd_seq_client *client) + { +- struct list_head deleted_list; + struct snd_seq_client_port *port, *tmp; +- +- /* move the port list to deleted_list, and +- * clear the port list in the client data. ++ ++ /* unlink and delete each port; port_delete() waits for an RCU grace ++ * period before draining the port, so concurrent lockless readers can ++ * no longer take a new use_lock reference on it + */ + guard(mutex)(&client->ports_mutex); +- scoped_guard(write_lock_irq, &client->ports_lock) { +- if (!list_empty(&client->ports_list_head)) { +- list_add(&deleted_list, &client->ports_list_head); +- list_del_init(&client->ports_list_head); +- } else { +- INIT_LIST_HEAD(&deleted_list); +- } +- client->num_ports = 0; +- } +- +- /* remove each port in deleted_list */ +- list_for_each_entry_safe(port, tmp, &deleted_list, list) { +- list_del(&port->list); ++ list_for_each_entry_safe(port, tmp, &client->ports_list_head, list) { ++ list_del_rcu(&port->list); ++ client->num_ports--; + snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port); + port_delete(client, port); + } +@@ -499,20 +493,20 @@ static int check_and_subscribe_port(struct snd_seq_client *client, + bool is_src, bool exclusive, bool ack) + { + struct snd_seq_port_subs_info *grp; +- struct list_head *p; ++ struct hlist_node *p; + struct snd_seq_subscribers *s; + int err; + + grp = is_src ? &port->c_src : &port->c_dest; + guard(rwsem_write)(&grp->list_mutex); + if (exclusive) { +- if (!list_empty(&grp->list_head)) ++ if (!hlist_empty(&grp->list_head)) + return -EBUSY; + } else { + if (grp->exclusive) + return -EBUSY; + /* check whether already exists */ +- list_for_each(p, &grp->list_head) { ++ hlist_for_each(p, &grp->list_head) { + s = get_subscriber(p, is_src); + if (match_subs_info(&subs->info, &s->info)) + return -EBUSY; +@@ -526,11 +520,10 @@ static int check_and_subscribe_port(struct snd_seq_client *client, + } + + /* add to list */ +- guard(write_lock_irq)(&grp->list_lock); + if (is_src) +- list_add_tail(&subs->src_list, &grp->list_head); ++ hlist_add_tail_rcu(&subs->src_list, &grp->list_head); + else +- list_add_tail(&subs->dest_list, &grp->list_head); ++ hlist_add_tail_rcu(&subs->dest_list, &grp->list_head); + grp->exclusive = exclusive; + atomic_inc(&subs->ref_count); + +@@ -544,17 +537,15 @@ static void __delete_and_unsubscribe_port(struct snd_seq_client *client, + bool is_src, bool ack) + { + struct snd_seq_port_subs_info *grp; +- struct list_head *list; ++ struct hlist_node *list; + bool empty; + + grp = is_src ? &port->c_src : &port->c_dest; + list = is_src ? &subs->src_list : &subs->dest_list; +- scoped_guard(write_lock_irq, &grp->list_lock) { +- empty = list_empty(list); +- if (!empty) +- list_del_init(list); +- grp->exclusive = 0; +- } ++ empty = hlist_unhashed(list); ++ if (!empty) ++ hlist_del_init_rcu(list); ++ grp->exclusive = 0; + + if (!empty) + unsubscribe_port(client, port, grp, &subs->info, ack); +@@ -590,8 +581,8 @@ int snd_seq_port_connect(struct snd_seq_client *connector, + + subs->info = *info; + atomic_set(&subs->ref_count, 0); +- INIT_LIST_HEAD(&subs->src_list); +- INIT_LIST_HEAD(&subs->dest_list); ++ INIT_HLIST_NODE(&subs->src_list); ++ INIT_HLIST_NODE(&subs->dest_list); + + exclusive = !!(info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE); + +@@ -612,7 +603,7 @@ int snd_seq_port_connect(struct snd_seq_client *connector, + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); + error: +- kfree(subs); ++ kfree_rcu(subs, rcu); + return err; + } + +@@ -633,7 +624,7 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector, + */ + scoped_guard(rwsem_write, &dest->list_mutex) { + /* look for the connection */ +- list_for_each_entry(subs, &dest->list_head, dest_list) { ++ hlist_for_each_entry(subs, &dest->list_head, dest_list) { + if (match_subs_info(info, &subs->info)) { + __delete_and_unsubscribe_port(dest_client, dest_port, + subs, false, +@@ -648,7 +639,7 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector, + + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); +- kfree(subs); ++ kfree_rcu(subs, rcu); + return 0; + } + +@@ -662,7 +653,7 @@ int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp, + int err = -ENOENT; + + guard(rwsem_read)(&src_grp->list_mutex); +- list_for_each_entry(s, &src_grp->list_head, src_list) { ++ hlist_for_each_entry(s, &src_grp->list_head, src_list) { + if (addr_match(dest_addr, &s->info.dest)) { + *subs = s->info; + err = 0; +diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h +--- a/sound/core/seq/seq_ports.h ++++ b/sound/core/seq/seq_ports.h +@@ -28,17 +28,17 @@ + + struct snd_seq_subscribers { + struct snd_seq_port_subscribe info; /* additional info */ +- struct list_head src_list; /* link of sources */ +- struct list_head dest_list; /* link of destinations */ ++ struct hlist_node src_list; /* link of sources */ ++ struct hlist_node dest_list; /* link of destinations */ + atomic_t ref_count; ++ struct rcu_head rcu; /* for deferred free */ + }; + + struct snd_seq_port_subs_info { +- struct list_head list_head; /* list of subscribed ports */ ++ struct hlist_head list_head; /* list of subscribed ports */ + unsigned int count; /* count of subscribers */ + unsigned int exclusive: 1; /* exclusive mode */ + struct rw_semaphore list_mutex; +- rwlock_t list_lock; + int (*open)(void *private_data, struct snd_seq_port_subscribe *info); + int (*close)(void *private_data, struct snd_seq_port_subscribe *info); + }; +diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c +--- a/sound/core/seq/seq_ump_client.c ++++ b/sound/core/seq/seq_ump_client.c +@@ -37,8 +37,11 @@ struct seq_ump_client { + struct snd_ump_endpoint *ump; /* assigned endpoint */ + int seq_client; /* sequencer client id */ + int opened[2]; /* current opens for each direction */ +- rwlock_t output_lock; /* protects out_rfile output access */ + struct snd_rawmidi_file out_rfile; /* rawmidi for output */ ++ /* RCU-protected shadow of out_rfile.output for the delivery hot path; ++ * out_rfile itself is only touched by open/close under open_mutex ++ */ ++ struct snd_rawmidi_substream __rcu *out_substream; + struct seq_ump_input_buffer input; /* input parser context */ + void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */ + struct work_struct group_notify_work; /* FB change notification */ +@@ -89,8 +92,8 @@ static int seq_ump_process_event(struct snd_seq_event *ev, int direct, + unsigned char type; + int len; + +- guard(read_lock_irqsave)(&client->output_lock); +- substream = client->out_rfile.output; ++ guard(rcu)(); ++ substream = rcu_dereference(client->out_substream); + if (!substream) + return -ENODEV; + if (!snd_seq_ev_is_ump(ev)) +@@ -108,19 +111,22 @@ static int seq_ump_process_event(struct snd_seq_event *ev, int direct, + static int seq_ump_client_open(struct seq_ump_client *client, int dir) + { + struct snd_ump_endpoint *ump = client->ump; +- struct snd_rawmidi_file rfile = {}; + int err; + + guard(mutex)(&ump->open_mutex); + if (dir == STR_OUT && !client->opened[dir]) { ++ /* out_rfile is only accessed under open_mutex; the delivery ++ * path reads out_substream via RCU, so open into out_rfile ++ * directly and publish the substream afterwards ++ */ + err = snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT | + SNDRV_RAWMIDI_LFLG_APPEND, +- &rfile); ++ &client->out_rfile); + if (err < 0) + return err; +- scoped_guard(write_lock_irqsave, &client->output_lock) +- client->out_rfile = rfile; ++ rcu_assign_pointer(client->out_substream, ++ client->out_rfile.output); + } + client->opened[dir]++; + return 0; +@@ -130,17 +136,18 @@ static int seq_ump_client_open(struct seq_ump_client *client, int dir) + static int seq_ump_client_close(struct seq_ump_client *client, int dir) + { + struct snd_ump_endpoint *ump = client->ump; +- struct snd_rawmidi_file rfile = {}; + + guard(mutex)(&ump->open_mutex); + if (!--client->opened[dir]) { +- if (dir == STR_OUT) { +- scoped_guard(write_lock_irqsave, &client->output_lock) { +- rfile = client->out_rfile; +- client->out_rfile = (struct snd_rawmidi_file){}; +- } +- if (rfile.rmidi) +- snd_rawmidi_kernel_release(&rfile); ++ if (dir == STR_OUT && client->out_rfile.rmidi) { ++ rcu_assign_pointer(client->out_substream, NULL); ++ /* wait for a grace period so that no reader in the ++ * delivery path is still writing to the substream ++ * before it is released ++ */ ++ synchronize_rcu(); ++ snd_rawmidi_kernel_release(&client->out_rfile); ++ client->out_rfile = (struct snd_rawmidi_file){}; + } + } + return 0; +@@ -480,7 +487,6 @@ static int snd_seq_ump_probe(struct snd_seq_device *dev) + + INIT_WORK(&client->group_notify_work, handle_group_notify); + client->ump = ump; +- rwlock_init(&client->output_lock); + + client->seq_client = + snd_seq_create_kernel_client(card, ump->core.device, +diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c +--- a/sound/core/seq/seq_virmidi.c ++++ b/sound/core/seq/seq_virmidi.c +@@ -78,10 +78,11 @@ static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, + int len; + + if (atomic) +- read_lock(&rdev->filelist_lock); ++ rcu_read_lock(); + else + down_read(&rdev->filelist_sem); +- list_for_each_entry(vmidi, &rdev->filelist, list) { ++ list_for_each_entry_rcu(vmidi, &rdev->filelist, list, ++ lockdep_is_held(&rdev->filelist_sem)) { + if (!READ_ONCE(vmidi->trigger)) + continue; + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { +@@ -96,7 +97,7 @@ static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, + } + } + if (atomic) +- read_unlock(&rdev->filelist_lock); ++ rcu_read_unlock(); + else + up_read(&rdev->filelist_sem); + +@@ -200,8 +201,7 @@ static int snd_virmidi_input_open(struct snd_rawmidi_substream *substream) + vmidi->port = rdev->port; + runtime->private_data = vmidi; + scoped_guard(rwsem_write, &rdev->filelist_sem) { +- guard(write_lock_irq)(&rdev->filelist_lock); +- list_add_tail(&vmidi->list, &rdev->filelist); ++ list_add_tail_rcu(&vmidi->list, &rdev->filelist); + } + vmidi->rdev = rdev; + return 0; +@@ -243,9 +243,13 @@ static int snd_virmidi_input_close(struct snd_rawmidi_substream *substream) + struct snd_virmidi *vmidi = substream->runtime->private_data; + + scoped_guard(rwsem_write, &rdev->filelist_sem) { +- guard(write_lock_irq)(&rdev->filelist_lock); +- list_del(&vmidi->list); ++ list_del_rcu(&vmidi->list); + } ++ /* wait for a grace period so that lockless readers in the atomic ++ * delivery path (snd_virmidi_dev_receive_event()) are no longer ++ * traversing this entry before its parser and memory are freed ++ */ ++ synchronize_rcu(); + snd_midi_event_free(vmidi->parser); + substream->runtime->private_data = NULL; + kfree(vmidi); +@@ -508,7 +512,6 @@ int snd_virmidi_new(struct snd_card *card, int device, struct snd_rawmidi **rrmi + rdev->device = device; + rdev->client = -1; + init_rwsem(&rdev->filelist_sem); +- rwlock_init(&rdev->filelist_lock); + INIT_LIST_HEAD(&rdev->filelist); + rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH; + rmidi->private_data = rdev; +diff --git a/sound/core/sound.c b/sound/core/sound.c +--- a/sound/core/sound.c ++++ b/sound/core/sound.c +@@ -167,11 +167,10 @@ static int snd_open(struct inode *inode, struct file *file) + return err; + } + +-static const struct file_operations snd_fops = +-{ +- .owner = THIS_MODULE, +- .open = snd_open, +- .llseek = noop_llseek, ++static const struct file_operations snd_fops = { ++ .owner = THIS_MODULE, ++ .open = snd_open, ++ .llseek = noop_llseek, + }; + + #ifdef CONFIG_SND_DYNAMIC_MINORS +@@ -358,7 +357,7 @@ static void snd_minor_info_read(struct snd_info_entry *entry, struct snd_info_bu + struct snd_minor *mptr; + + guard(mutex)(&sound_mutex); +- for (minor = 0; minor < SNDRV_OS_MINORS; ++minor) { ++ for (minor = 0; minor < ARRAY_SIZE(snd_minors); ++minor) { + mptr = snd_minors[minor]; + if (!mptr) + continue; +@@ -395,15 +394,21 @@ int __init snd_minor_info_init(void) + + static int __init alsa_sound_init(void) + { ++ int err; ++ + snd_major = major; + snd_ecards_limit = cards_limit; +- if (register_chrdev(major, "alsa", &snd_fops)) { ++ ++ err = register_chrdev(major, "alsa", &snd_fops); ++ if (err < 0) { + pr_err("ALSA core: unable to register native major device number %d\n", major); +- return -EIO; ++ return err; + } +- if (snd_info_init() < 0) { ++ ++ err = snd_info_init(); ++ if (err < 0) { + unregister_chrdev(major, "alsa"); +- return -ENOMEM; ++ return err; + } + + #ifdef CONFIG_SND_DEBUG +diff --git a/sound/core/sound_kunit.c b/sound/core/sound_kunit.c +--- a/sound/core/sound_kunit.c ++++ b/sound/core/sound_kunit.c +@@ -17,8 +17,8 @@ + .name = #fmt, \ + } + +-#define WRONG_FORMAT_1 (__force snd_pcm_format_t)((__force int)SNDRV_PCM_FORMAT_LAST + 1) +-#define WRONG_FORMAT_2 (__force snd_pcm_format_t)-1 ++#define WRONG_FORMAT_1 (SNDRV_PCM_FORMAT_LAST + 1) ++#define WRONG_FORMAT_2 -1 + + #define VALID_NAME "ValidName" + #define NAME_W_SPEC_CHARS "In%v@1id name" +diff --git a/sound/core/timer.c b/sound/core/timer.c +--- a/sound/core/timer.c ++++ b/sound/core/timer.c +@@ -1546,11 +1546,8 @@ static int realloc_user_queue(struct snd_timer_user *tu, int size) + static int snd_timer_user_open(struct inode *inode, struct file *file) + { + struct snd_timer_user *tu; +- int err; + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + tu = kzalloc_obj(*tu); + if (tu == NULL) +@@ -2176,9 +2173,9 @@ static long snd_utimer_ioctl(struct file *file, unsigned int ioctl, unsigned lon + } + + static const struct file_operations snd_utimer_fops = { +- .llseek = noop_llseek, +- .release = snd_utimer_release, +- .unlocked_ioctl = snd_utimer_ioctl, ++ .llseek = noop_llseek, ++ .release = snd_utimer_release, ++ .unlocked_ioctl = snd_utimer_ioctl, + }; + + static int snd_utimer_start(struct snd_timer *t) +@@ -2530,16 +2527,15 @@ static __poll_t snd_timer_user_poll(struct file *file, poll_table * wait) + #define snd_timer_user_ioctl_compat NULL + #endif + +-static const struct file_operations snd_timer_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = snd_timer_user_read, +- .open = snd_timer_user_open, +- .release = snd_timer_user_release, +- .poll = snd_timer_user_poll, +- .unlocked_ioctl = snd_timer_user_ioctl, +- .compat_ioctl = snd_timer_user_ioctl_compat, +- .fasync = snd_timer_user_fasync, ++static const struct file_operations snd_timer_f_ops = { ++ .owner = THIS_MODULE, ++ .read = snd_timer_user_read, ++ .open = snd_timer_user_open, ++ .release = snd_timer_user_release, ++ .poll = snd_timer_user_poll, ++ .unlocked_ioctl = snd_timer_user_ioctl, ++ .compat_ioctl = snd_timer_user_ioctl_compat, ++ .fasync = snd_timer_user_fasync, + }; + + /* unregister the system timer */ +diff --git a/sound/core/ump.c b/sound/core/ump.c +--- a/sound/core/ump.c ++++ b/sound/core/ump.c +@@ -1157,10 +1157,11 @@ static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream) + return -ENODEV; + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!ump->legacy_out_opens) { +- err = snd_rawmidi_kernel_open(&ump->core, 0, +- SNDRV_RAWMIDI_LFLG_OUTPUT | +- SNDRV_RAWMIDI_LFLG_APPEND, +- &ump->legacy_out_rfile); ++ err = snd_rawmidi_kernel_open_nested(&ump->core, 0, ++ SNDRV_RAWMIDI_LFLG_OUTPUT | ++ SNDRV_RAWMIDI_LFLG_APPEND, ++ &ump->legacy_out_rfile, ++ SINGLE_DEPTH_NESTING); + if (err < 0) + return err; + } +diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c +--- a/sound/drivers/aloop.c ++++ b/sound/drivers/aloop.c +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -55,7 +56,7 @@ MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver."); + module_param_array(pcm_notify, int, NULL, 0444); + MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels changes."); + module_param_array(timer_source, charp, NULL, 0444); +-MODULE_PARM_DESC(timer_source, "Sound card name or number and device/subdevice number of timer to be used. Empty string for jiffies timer [default]."); ++MODULE_PARM_DESC(timer_source, "Sound card name or number and device/subdevice number of timer to be used. Empty string for jiffies timer [default], 'hrtimer' for high-resolution timer."); + + #define NO_PITCH 100000 + +@@ -161,8 +162,11 @@ struct loopback_pcm { + unsigned int period_size_frac; /* period size in jiffies ticks */ + unsigned int last_drift; + unsigned long last_jiffies; +- /* If jiffies timer is used */ ++ /* If jiffies / hrtimer is used */ + struct timer_list timer; ++#ifdef CONFIG_HIGH_RES_TIMERS ++ struct hrtimer hrtimer; ++#endif + + /* size of per channel buffer in case of non-interleaved access */ + unsigned int channel_buf_n; +@@ -232,6 +236,31 @@ static int loopback_jiffies_timer_start(struct loopback_pcm *dpcm) + return 0; + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++/* call in cable->lock */ ++static int loopback_hrtimer_start(struct loopback_pcm *dpcm) ++{ ++ unsigned long tick; ++ unsigned int rate_shift = get_rate_shift(dpcm); ++ ++ if (rate_shift != dpcm->pcm_rate_shift) { ++ dpcm->pcm_rate_shift = rate_shift; ++ dpcm->period_size_frac = frac_pos(dpcm, dpcm->pcm_period_size); ++ } ++ if (dpcm->period_size_frac <= dpcm->irq_pos) { ++ dpcm->irq_pos %= dpcm->period_size_frac; ++ dpcm->period_update_pending = 1; ++ } ++ tick = dpcm->period_size_frac - dpcm->irq_pos; ++ tick = DIV_ROUND_UP(tick, dpcm->pcm_bps); ++ hrtimer_start(&dpcm->hrtimer, ++ ns_to_ktime(div_u64((u64)tick * NSEC_PER_SEC, HZ)), ++ HRTIMER_MODE_REL_SOFT); ++ ++ return 0; ++} ++#endif ++ + /* call in cable->lock */ + static int loopback_snd_timer_start(struct loopback_pcm *dpcm) + { +@@ -270,6 +299,16 @@ static inline int loopback_jiffies_timer_stop(struct loopback_pcm *dpcm) + return 0; + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++/* call in cable->lock */ ++static inline int loopback_hrtimer_stop(struct loopback_pcm *dpcm) ++{ ++ hrtimer_try_to_cancel(&dpcm->hrtimer); ++ ++ return 0; ++} ++#endif ++ + /* call in cable->lock */ + static int loopback_snd_timer_stop(struct loopback_pcm *dpcm) + { +@@ -300,6 +339,15 @@ static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm) + return 0; + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static inline int loopback_hrtimer_stop_sync(struct loopback_pcm *dpcm) ++{ ++ hrtimer_cancel(&dpcm->hrtimer); ++ ++ return 0; ++} ++#endif ++ + /* call in loopback->cable_lock */ + static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm) + { +@@ -734,6 +782,30 @@ static void loopback_jiffies_timer_function(struct timer_list *t) + snd_pcm_period_elapsed(dpcm->substream); + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static enum hrtimer_restart loopback_hrtimer_function(struct hrtimer *t) ++{ ++ struct loopback_pcm *dpcm = container_of(t, struct loopback_pcm, hrtimer); ++ bool period_elapsed = false; ++ ++ scoped_guard(spinlock_irqsave, &dpcm->cable->lock) { ++ if (loopback_jiffies_timer_pos_update(dpcm->cable) & ++ (1 << dpcm->substream->stream)) { ++ loopback_hrtimer_start(dpcm); ++ if (dpcm->period_update_pending) { ++ dpcm->period_update_pending = 0; ++ period_elapsed = true; ++ } ++ } ++ } ++ ++ if (period_elapsed) ++ snd_pcm_period_elapsed(dpcm->substream); ++ ++ return HRTIMER_NORESTART; ++} ++#endif ++ + /* call in cable->lock */ + static int loopback_snd_timer_check_resolution(struct snd_pcm_runtime *runtime, + unsigned long resolution) +@@ -907,6 +979,21 @@ static void loopback_jiffies_timer_dpcm_info(struct loopback_pcm *dpcm, + snd_iprintf(buffer, " timer_expires:\t%lu\n", dpcm->timer.expires); + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static void loopback_hrtimer_dpcm_info(struct loopback_pcm *dpcm, ++ struct snd_info_buffer *buffer) ++{ ++ snd_iprintf(buffer, " update_pending:\t%u\n", ++ dpcm->period_update_pending); ++ snd_iprintf(buffer, " irq_pos:\t\t%u\n", dpcm->irq_pos); ++ snd_iprintf(buffer, " period_frac:\t%u\n", dpcm->period_size_frac); ++ snd_iprintf(buffer, " last_jiffies:\t%lu (%lu)\n", ++ dpcm->last_jiffies, jiffies); ++ snd_iprintf(buffer, " timer_expires:\t%llu\n", ++ ktime_to_ns(hrtimer_get_expires(&dpcm->hrtimer))); ++} ++#endif ++ + static void loopback_snd_timer_dpcm_info(struct loopback_pcm *dpcm, + struct snd_info_buffer *buffer) + { +@@ -1097,6 +1184,26 @@ static const struct loopback_ops loopback_jiffies_timer_ops = { + .dpcm_info = loopback_jiffies_timer_dpcm_info, + }; + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static int loopback_hrtimer_open(struct loopback_pcm *dpcm) ++{ ++ hrtimer_setup(&dpcm->hrtimer, loopback_hrtimer_function, ++ CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); ++ ++ return 0; ++} ++ ++static const struct loopback_ops loopback_hrtimer_ops = { ++ .open = loopback_hrtimer_open, ++ .start = loopback_hrtimer_start, ++ .stop = loopback_hrtimer_stop, ++ .stop_sync = loopback_hrtimer_stop_sync, ++ .close_substream = loopback_hrtimer_stop_sync, ++ .pos_update = loopback_jiffies_timer_pos_update, ++ .dpcm_info = loopback_hrtimer_dpcm_info, ++}; ++#endif ++ + static int loopback_parse_timer_id(const char *str, + struct snd_timer_id *tid) + { +@@ -1274,7 +1381,12 @@ static int loopback_open(struct snd_pcm_substream *substream) + spin_lock_init(&cable->lock); + snd_refcount_init(&cable->stop_count); + cable->hw = loopback_pcm_hardware; +- if (loopback->timer_source) ++#ifdef CONFIG_HIGH_RES_TIMERS ++ if (loopback->timer_source && !strcmp(loopback->timer_source, "hrtimer")) ++ cable->ops = &loopback_hrtimer_ops; ++ else ++#endif ++ if (loopback->timer_source && loopback->timer_source[0]) + cable->ops = &loopback_snd_timer_ops; + else + cable->ops = &loopback_jiffies_timer_ops; +@@ -1491,7 +1603,7 @@ static int loopback_format_info(struct snd_kcontrol *kcontrol, + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; +- uinfo->value.integer.max = (__force int)SNDRV_PCM_FORMAT_LAST; ++ uinfo->value.integer.max = SNDRV_PCM_FORMAT_LAST; + uinfo->value.integer.step = 1; + return 0; + } +@@ -1502,7 +1614,7 @@ static int loopback_format_get(struct snd_kcontrol *kcontrol, + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = +- (__force int)loopback->setup[kcontrol->id.subdevice] ++ loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].format; + return 0; + } +@@ -1556,7 +1668,7 @@ static int loopback_channels_get(struct snd_kcontrol *kcontrol, + static int loopback_access_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) + { +- const char * const texts[] = {"Interleaved", "Non-interleaved"}; ++ static const char * const texts[] = {"Interleaved", "Non-interleaved"}; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); + } +diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c +--- a/sound/drivers/dummy.c ++++ b/sound/drivers/dummy.c +@@ -100,6 +100,7 @@ struct dummy_timer_ops { + int (*prepare)(struct snd_pcm_substream *); + int (*start)(struct snd_pcm_substream *); + int (*stop)(struct snd_pcm_substream *); ++ int (*sync_stop)(struct snd_pcm_substream *); + snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *); + }; + +@@ -285,6 +286,14 @@ static int dummy_systimer_stop(struct snd_pcm_substream *substream) + return 0; + } + ++static int dummy_systimer_sync_stop(struct snd_pcm_substream *substream) ++{ ++ struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; ++ ++ timer_delete_sync(&dpcm->timer); ++ return 0; ++} ++ + static int dummy_systimer_prepare(struct snd_pcm_substream *substream) + { + struct snd_pcm_runtime *runtime = substream->runtime; +@@ -341,7 +350,10 @@ static int dummy_systimer_create(struct snd_pcm_substream *substream) + + static void dummy_systimer_free(struct snd_pcm_substream *substream) + { +- kfree(substream->runtime->private_data); ++ struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; ++ ++ timer_shutdown_sync(&dpcm->timer); ++ kfree(dpcm); + } + + static const struct dummy_timer_ops dummy_systimer_ops = { +@@ -350,6 +362,7 @@ static const struct dummy_timer_ops dummy_systimer_ops = { + .prepare = dummy_systimer_prepare, + .start = dummy_systimer_start, + .stop = dummy_systimer_stop, ++ .sync_stop = dummy_systimer_sync_stop, + .pointer = dummy_systimer_pointer, + }; + +@@ -407,9 +420,12 @@ static int dummy_hrtimer_stop(struct snd_pcm_substream *substream) + return 0; + } + +-static inline void dummy_hrtimer_sync(struct dummy_hrtimer_pcm *dpcm) ++static int dummy_hrtimer_sync_stop(struct snd_pcm_substream *substream) + { ++ struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; ++ + hrtimer_cancel(&dpcm->timer); ++ return 0; + } + + static snd_pcm_uframes_t +@@ -435,7 +451,6 @@ static int dummy_hrtimer_prepare(struct snd_pcm_substream *substream) + long sec; + unsigned long nsecs; + +- dummy_hrtimer_sync(dpcm); + period = runtime->period_size; + rate = runtime->rate; + sec = period / rate; +@@ -463,7 +478,7 @@ static int dummy_hrtimer_create(struct snd_pcm_substream *substream) + static void dummy_hrtimer_free(struct snd_pcm_substream *substream) + { + struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; +- dummy_hrtimer_sync(dpcm); ++ + kfree(dpcm); + } + +@@ -473,6 +488,7 @@ static const struct dummy_timer_ops dummy_hrtimer_ops = { + .prepare = dummy_hrtimer_prepare, + .start = dummy_hrtimer_start, + .stop = dummy_hrtimer_stop, ++ .sync_stop = dummy_hrtimer_sync_stop, + .pointer = dummy_hrtimer_pointer, + }; + +@@ -495,6 +511,13 @@ static int dummy_pcm_trigger(struct snd_pcm_substream *substream, int cmd) + return -EINVAL; + } + ++static int dummy_pcm_sync_stop(struct snd_pcm_substream *substream) ++{ ++ if (get_dummy_ops(substream)->sync_stop) ++ return get_dummy_ops(substream)->sync_stop(substream); ++ return 0; ++} ++ + static int dummy_pcm_prepare(struct snd_pcm_substream *substream) + { + return get_dummy_ops(substream)->prepare(substream); +@@ -646,6 +669,7 @@ static const struct snd_pcm_ops dummy_pcm_ops = { + .hw_params = dummy_pcm_hw_params, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, ++ .sync_stop = dummy_pcm_sync_stop, + .pointer = dummy_pcm_pointer, + }; + +@@ -655,6 +679,7 @@ static const struct snd_pcm_ops dummy_pcm_ops_no_buf = { + .hw_params = dummy_pcm_hw_params, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, ++ .sync_stop = dummy_pcm_sync_stop, + .pointer = dummy_pcm_pointer, + .copy = dummy_pcm_copy, + .fill_silence = dummy_pcm_silence, +diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c +--- a/sound/hda/codecs/ca0132.c ++++ b/sound/hda/codecs/ca0132.c +@@ -5788,7 +5788,8 @@ static int ca0132_alt_mic_boost_info(struct snd_kcontrol *kcontrol, + uinfo->value.enumerated.items = MIC_BOOST_NUM_OF_STEPS; + if (uinfo->value.enumerated.item >= MIC_BOOST_NUM_OF_STEPS) + uinfo->value.enumerated.item = MIC_BOOST_NUM_OF_STEPS - 1; +- sprintf(namestr, "%d %s", (uinfo->value.enumerated.item * 10), sfx); ++ snprintf(namestr, sizeof(namestr), "%d %s", ++ (uinfo->value.enumerated.item * 10), sfx); + strscpy(uinfo->value.enumerated.name, namestr); + return 0; + } +@@ -5840,9 +5841,9 @@ static int ae5_headphone_gain_info(struct snd_kcontrol *kcontrol, + uinfo->value.enumerated.items = AE5_HEADPHONE_GAIN_MAX; + if (uinfo->value.enumerated.item >= AE5_HEADPHONE_GAIN_MAX) + uinfo->value.enumerated.item = AE5_HEADPHONE_GAIN_MAX - 1; +- sprintf(namestr, "%s %s", +- ae5_headphone_gain_presets[uinfo->value.enumerated.item].name, +- sfx); ++ snprintf(namestr, sizeof(namestr), "%s %s", ++ ae5_headphone_gain_presets[uinfo->value.enumerated.item].name, ++ sfx); + strscpy(uinfo->value.enumerated.name, namestr); + return 0; + } +@@ -5894,8 +5895,8 @@ static int ae5_sound_filter_info(struct snd_kcontrol *kcontrol, + uinfo->value.enumerated.items = AE5_SOUND_FILTER_MAX; + if (uinfo->value.enumerated.item >= AE5_SOUND_FILTER_MAX) + uinfo->value.enumerated.item = AE5_SOUND_FILTER_MAX - 1; +- sprintf(namestr, "%s", +- ae5_filter_presets[uinfo->value.enumerated.item].name); ++ snprintf(namestr, sizeof(namestr), "%s", ++ ae5_filter_presets[uinfo->value.enumerated.item].name); + strscpy(uinfo->value.enumerated.name, namestr); + return 0; + } +@@ -6632,7 +6633,7 @@ static int ca0132_alt_add_effect_slider(struct hda_codec *codec, hda_nid_t nid, + struct snd_kcontrol_new knew = + HDA_CODEC_VOLUME_MONO(namestr, nid, 1, 0, type); + +- sprintf(namestr, "FX: %s %s Volume", pfx, dirstr[dir]); ++ snprintf(namestr, sizeof(namestr), "FX: %s %s Volume", pfx, dirstr[dir]); + + knew.tlv.c = NULL; + +@@ -6671,9 +6672,9 @@ static int add_fx_switch(struct hda_codec *codec, hda_nid_t nid, + * prefix to OutFX or InFX enable controls. + */ + if (ca0132_use_alt_controls(spec) && (nid <= IN_EFFECT_END_NID)) +- sprintf(namestr, "FX: %s %s Switch", pfx, dirstr[dir]); ++ snprintf(namestr, sizeof(namestr), "FX: %s %s Switch", pfx, dirstr[dir]); + else +- sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]); ++ snprintf(namestr, sizeof(namestr), "%s %s Switch", pfx, dirstr[dir]); + + return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); + } +@@ -8530,10 +8531,9 @@ static void ca0132_set_dsp_msr(struct hda_codec *codec, bool is96k) + + static bool ca0132_download_dsp_images(struct hda_codec *codec) + { +- bool dsp_loaded = false; + struct ca0132_spec *spec = codec->spec; + const struct dsp_image_seg *dsp_os_image; +- const struct firmware *fw_entry = NULL; ++ const struct firmware *fw_entry __free(firmware) = NULL; + /* + * Alternate firmwares for different variants. The Recon3Di apparently + * can use the default firmware, but I'll leave the option in case +@@ -8573,15 +8573,10 @@ static bool ca0132_download_dsp_images(struct hda_codec *codec) + dsp_os_image = (struct dsp_image_seg *)(fw_entry->data); + if (dspload_image(codec, dsp_os_image, 0, 0, true, 0)) { + codec_err(codec, "ca0132 DSP load image failed\n"); +- goto exit_download; ++ return false; + } + +- dsp_loaded = dspload_wait_loaded(codec); +- +-exit_download: +- release_firmware(fw_entry); +- +- return dsp_loaded; ++ return dspload_wait_loaded(codec); + } + + static void ca0132_download_dsp(struct hda_codec *codec) +@@ -9634,6 +9629,7 @@ static void ca0132_free(struct hda_codec *codec) + #endif + kfree(spec->spec_init_verbs); + kfree(codec->spec); ++ codec->spec = NULL; + } + + static void dbpro_free(struct hda_codec *codec) +@@ -9644,6 +9640,7 @@ static void dbpro_free(struct hda_codec *codec) + + kfree(spec->spec_init_verbs); + kfree(codec->spec); ++ codec->spec = NULL; + } + + static void ca0132_config(struct hda_codec *codec) +diff --git a/sound/hda/codecs/cm9825.c b/sound/hda/codecs/cm9825.c +--- a/sound/hda/codecs/cm9825.c ++++ b/sound/hda/codecs/cm9825.c +@@ -15,7 +15,8 @@ + + enum { + QUIRK_CM_STD = 0x0, +- QUIRK_GENE_TWL7_SSID = 0x160dc000 ++ QUIRK_GENE_TWL7_SSID = 0x160dc000, ++ QUIRK_IBP_SSID = 0x15bd3275 + }; + + /* CM9825 Offset Definitions */ +@@ -40,13 +41,19 @@ enum { + #define CM9825_VERB_SET_GAD 0x7b4 + #define CM9825_VERB_SET_TMOD 0x7b5 + #define CM9825_VERB_SET_SNR 0x7b6 ++#define CM9825_VERB_SET_OMTP 0x7ef ++#define CM9825_VERB_READ_OMTP 0xfec + + struct cmi_spec { + struct hda_gen_spec gen; + const struct hda_verb *chip_d0_verbs; + const struct hda_verb *chip_d3_verbs; ++ const struct hda_verb *chip_playback_start_verbs; ++ const struct hda_verb *chip_playback_stop_verbs; + const struct hda_verb *chip_hp_present_verbs; + const struct hda_verb *chip_hp_remove_verbs; ++ const struct hda_verb *chip_lineout_present_verbs; ++ const struct hda_verb *chip_lineout_remove_verbs; + struct hda_codec *codec; + struct delayed_work unsol_inputs_work; + struct delayed_work unsol_lineout_work; +@@ -193,6 +200,94 @@ static const struct hda_verb cm9825_gene_twl7_playback_stop_verbs[] = { + {} + }; + ++/* ++ * To save power, AD/CLK is turned off. ++ */ ++static const struct hda_verb cm9825_ibp_d3_verbs[] = { ++ {0x43, CM9825_VERB_SET_D2S, 0x62}, ++ {0x43, CM9825_VERB_SET_PLL, 0x01}, ++ {0x43, CM9825_VERB_SET_NEG, 0xc2}, ++ {0x43, CM9825_VERB_SET_ADCL, 0x00}, ++ {0x43, CM9825_VERB_SET_DACL, 0x02}, ++ {0x43, CM9825_VERB_SET_VNEG, 0x50}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x00}, ++ {0x43, CM9825_VERB_SET_PDNEG, 0x04}, ++ {0x43, CM9825_VERB_SET_CDALR, 0xf6}, ++ {0x43, CM9825_VERB_SET_OTP, 0xcd}, ++ {} ++}; ++ ++/* ++ * D0 configuration: enable PLL/CLK/ADC/DAC and optimize performance ++ */ ++static const struct hda_verb cm9825_ibp_d0_verbs[] = { ++ {0x34, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, ++ {0x43, CM9825_VERB_SET_SNR, 0x38}, ++ {0x43, CM9825_VERB_SET_PLL, 0x00}, ++ {0x43, CM9825_VERB_SET_ADCL, 0x00}, ++ {0x43, CM9825_VERB_SET_DACL, 0x02}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x00}, ++ {0x43, CM9825_VERB_SET_VNEG, 0x56}, ++ {0x43, CM9825_VERB_SET_D2S, 0x62}, ++ {0x43, CM9825_VERB_SET_DACTRL, 0x00}, ++ {0x43, CM9825_VERB_SET_PDNEG, 0x0c}, ++ {0x43, CM9825_VERB_SET_CDALR, 0xf4}, ++ {0x43, CM9825_VERB_SET_OTP, 0xcd}, ++ {0x43, CM9825_VERB_SET_MTCBA, 0x61}, ++ {0x43, CM9825_VERB_SET_OCP, 0x33}, ++ {0x43, CM9825_VERB_SET_GAD, 0x07}, ++ {0x43, CM9825_VERB_SET_TMOD, 0x26}, ++ {0x3c, AC_VERB_SET_AMP_GAIN_MUTE | 0xa0, 0x2d}, ++ {0x3c, AC_VERB_SET_AMP_GAIN_MUTE | 0x90, 0x2d}, ++ {0x43, CM9825_VERB_SET_HPF_1, 0x40}, ++ {0x43, CM9825_VERB_SET_HPF_2, 0x40}, ++ {} ++}; ++ ++/* ++ * Enable mbias, ADC. ++ */ ++static const struct hda_verb cm9825_ibp_lineout_present_verbs[] = { ++ {0x43, CM9825_VERB_SET_ADCL, 0x8c}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x10}, ++ {} ++}; ++ ++/* ++ * Disable mbias, ADC. ++ */ ++static const struct hda_verb cm9825_ibp_lineout_remove_verbs[] = { ++ {0x43, CM9825_VERB_SET_ADCL, 0x00}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x00}, ++ {} ++}; ++ ++/* ++ * Turn on the DAC (widget NID 0x43) in the playback path by writing ++ * a sequence of vendor-specific verbs. ++ */ ++static const struct hda_verb cm9825_ibp_playback_start_verbs[] = { ++ {0x43, CM9825_VERB_SET_DACL, 0xaa}, ++ {0x43, CM9825_VERB_SET_D2S, 0xf2}, ++ {0x43, CM9825_VERB_SET_VDO, 0xc4}, ++ {0x43, CM9825_VERB_SET_SNR, 0x30}, ++ {} ++}; ++ ++/* ++ * Shut down the playback path. The order differs slightly from the ++ * enable sequence, likely to avoid audible pop noise when powering ++ * down the output stage. ++ */ ++static const struct hda_verb cm9825_ibp_playback_stop_verbs[] = { ++ {0x43, CM9825_VERB_SET_VDO, 0xc0}, ++ {0x43, CM9825_VERB_SET_DACL, 0x02}, ++ {0x43, CM9825_VERB_SET_D2S, 0x62}, ++ {0x43, CM9825_VERB_SET_VDO, 0x80}, ++ {0x43, CM9825_VERB_SET_SNR, 0x38}, ++ {} ++}; ++ + static void cm9825_update_jk_plug_status(struct hda_codec *codec, hda_nid_t nid) + { + struct cmi_spec *spec = codec->spec; +@@ -232,6 +327,23 @@ static void cm9825_unsol_lineout_delayed(struct work_struct *work) + struct cmi_spec *spec = + container_of(to_delayed_work(work), struct cmi_spec, + unsol_lineout_work); ++ hda_nid_t line_out_pin = spec->gen.autocfg.line_out_pins[0]; ++ bool line_out_jack_plugin = false; ++ ++ line_out_jack_plugin = snd_hda_jack_detect(spec->codec, line_out_pin); ++ ++ codec_dbg(spec->codec, "lineout_jack_plugin %d, lineout_pin 0x%X\n", ++ (int)line_out_jack_plugin, line_out_pin); ++ ++ if (!line_out_jack_plugin) { ++ /* Jack plugout */ ++ snd_hda_sequence_write(spec->codec, ++ spec->chip_lineout_remove_verbs); ++ } else { ++ /* Jack plugin */ ++ snd_hda_sequence_write(spec->codec, ++ spec->chip_lineout_present_verbs); ++ } + + cm9825_update_jk_plug_status(spec->codec, + spec->gen.autocfg.line_out_pins[0]); +@@ -362,12 +474,10 @@ static void cm9825_playback_pcm_hook(struct hda_pcm_stream *hinfo, + + switch (action) { + case HDA_GEN_PCM_ACT_PREPARE: +- snd_hda_sequence_write(spec->codec, +- cm9825_gene_twl7_playback_start_verbs); ++ snd_hda_sequence_write(codec, spec->chip_playback_start_verbs); + break; + case HDA_GEN_PCM_ACT_CLEANUP: +- snd_hda_sequence_write(spec->codec, +- cm9825_gene_twl7_playback_stop_verbs); ++ snd_hda_sequence_write(codec, spec->chip_playback_stop_verbs); + break; + default: + return; +@@ -471,7 +581,8 @@ static int cm9825_resume(struct hda_codec *codec) + + if (codec->core.subsystem_id == QUIRK_CM_STD) + cm9825_cm_std_resume(codec); +- else if (codec->core.subsystem_id == QUIRK_GENE_TWL7_SSID) { ++ else if (codec->core.subsystem_id == QUIRK_GENE_TWL7_SSID || ++ codec->core.subsystem_id == QUIRK_IBP_SSID) { + snd_hda_codec_init(codec); + snd_hda_sequence_write(codec, spec->chip_d0_verbs); + } +@@ -487,6 +598,7 @@ static int cm9825_probe(struct hda_codec *codec, const struct hda_device_id *id) + struct cmi_spec *spec; + struct auto_pin_cfg *cfg; + int err = 0; ++ unsigned int val; + + spec = kzalloc_obj(*spec); + if (spec == NULL) +@@ -523,9 +635,33 @@ static int cm9825_probe(struct hda_codec *codec, const struct hda_device_id *id) + spec->chip_d0_verbs = cm9825_gene_twl7_d0_verbs; + spec->chip_d3_verbs = cm9825_gene_twl7_d3_verbs; + spec->gen.pcm_playback_hook = cm9825_playback_pcm_hook; ++ spec->chip_playback_start_verbs = ++ cm9825_gene_twl7_playback_start_verbs; ++ spec->chip_playback_stop_verbs = ++ cm9825_gene_twl7_playback_stop_verbs; + /* Internal fixed device, Rear, Mic-in, 3.5mm */ + snd_hda_codec_set_pincfg(codec, 0x37, 0x24A70100); + break; ++ case QUIRK_IBP_SSID: ++ snd_hda_codec_set_name(codec, "CM9825 IBP"); ++ spec->chip_d0_verbs = cm9825_ibp_d0_verbs; ++ spec->chip_d3_verbs = cm9825_ibp_d3_verbs; ++ spec->gen.pcm_playback_hook = cm9825_playback_pcm_hook; ++ spec->chip_lineout_present_verbs = ++ cm9825_ibp_lineout_present_verbs; ++ spec->chip_lineout_remove_verbs = ++ cm9825_ibp_lineout_remove_verbs; ++ spec->chip_playback_start_verbs = ++ cm9825_ibp_playback_start_verbs; ++ spec->chip_playback_stop_verbs = cm9825_ibp_playback_stop_verbs; ++ ++ /* OMTP */ ++ val = ++ snd_hda_codec_read(codec, 0x46, 0, CM9825_VERB_READ_OMTP, ++ 0x0); ++ snd_hda_codec_write(codec, 0x46, 0, CM9825_VERB_SET_OMTP, ++ (val >> 24) & 0x7f); ++ break; + default: + err = -ENXIO; + break; +diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c +--- a/sound/hda/codecs/conexant.c ++++ b/sound/hda/codecs/conexant.c +@@ -292,6 +292,7 @@ enum { + CXT_PINCFG_TOP_SPEAKER, + CXT_FIXUP_HP_A_U, + CXT_FIXUP_ACER_SWIFT_HP, ++ CXT_FIXUP_HUAWEI_MATEBOOK_HP, + }; + + /* for hda_fixup_thinkpad_acpi() */ +@@ -451,7 +452,8 @@ static void olpc_xo_update_mic_pins(struct hda_codec *codec) + if (!spec->dc_enable) { + /* disable DC bias path and pin for port F */ + update_mic_pin(codec, 0x1e, 0); +- snd_hda_activate_path(codec, spec->dc_mode_path, false, false); ++ if (spec->dc_mode_path) ++ snd_hda_activate_path(codec, spec->dc_mode_path, false, false); + + /* update port B (ext mic) and C (int mic) */ + /* OLPC defers mic widget control until when capture is +@@ -487,7 +489,8 @@ static void olpc_xo_update_mic_pins(struct hda_codec *codec) + update_mic_pin(codec, 0x1b, 0); + /* enable DC bias path and pin */ + update_mic_pin(codec, 0x1e, spec->recording ? PIN_IN : 0); +- snd_hda_activate_path(codec, spec->dc_mode_path, true, false); ++ if (spec->dc_mode_path) ++ snd_hda_activate_path(codec, spec->dc_mode_path, true, false); + } + } + +@@ -1033,6 +1036,13 @@ static const struct hda_fixup cxt_fixups[] = { + { } + }, + }, ++ [CXT_FIXUP_HUAWEI_MATEBOOK_HP] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x18, 0x03211020 }, /* Headphone */ ++ { } ++ }, ++ }, + }; + + static const struct hda_quirk cxt5045_fixups[] = { +@@ -1097,6 +1107,7 @@ static const struct hda_quirk cxt5066_fixups[] = { + SND_PCI_QUIRK(0x103c, 0x829a, "HP 800 G3 DM", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x82b4, "HP ProDesk 600 G3", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x836e, "HP ProBook 455 G5", CXT_FIXUP_MUTE_LED_GPIO), ++ SND_PCI_QUIRK(0x103c, 0x837b, "HP ProBook 440 G5", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x837f, "HP ProBook 470 G5", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x83b2, "HP EliteBook 840 G5", CXT_FIXUP_HP_DOCK), + SND_PCI_QUIRK(0x103c, 0x83b3, "HP EliteBook 830 G5", CXT_FIXUP_HP_DOCK), +@@ -1133,6 +1144,7 @@ static const struct hda_quirk cxt5066_fixups[] = { + SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo G50-70", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad/Ideapad", CXT_FIXUP_LENOVO_XPAD_ACPI), ++ SND_PCI_QUIRK(0x19e5, 0x3289, "Huawei Matebook", CXT_FIXUP_HUAWEI_MATEBOOK_HP), + SND_PCI_QUIRK(0x1c06, 0x2011, "Lemote A1004", CXT_PINCFG_LEMOTE_A1004), + SND_PCI_QUIRK(0x1c06, 0x2012, "Lemote A1205", CXT_PINCFG_LEMOTE_A1205), + SND_PCI_QUIRK(0x1d05, 0x3012, "MECHREVO Wujie 15X Pro", CXT_FIXUP_HEADSET_MIC), +diff --git a/sound/hda/codecs/generic.c b/sound/hda/codecs/generic.c +--- a/sound/hda/codecs/generic.c ++++ b/sound/hda/codecs/generic.c +@@ -2695,30 +2695,13 @@ static const struct snd_kcontrol_new out_jack_mode_enum = { + .put = out_jack_mode_put, + }; + +-static bool find_kctl_name(struct hda_codec *codec, const char *name, int idx) +-{ +- struct hda_gen_spec *spec = codec->spec; +- const struct snd_kcontrol_new *kctl; +- int i; +- +- snd_array_for_each(&spec->kctls, i, kctl) { +- if (!strcmp(kctl->name, name) && kctl->index == idx) +- return true; +- } +- return false; +-} +- + static void get_jack_mode_name(struct hda_codec *codec, hda_nid_t pin, + char *name, size_t name_len) + { + struct hda_gen_spec *spec = codec->spec; +- int idx = 0; + +- snd_hda_get_pin_label(codec, pin, &spec->autocfg, name, name_len, &idx); +- strlcat(name, " Jack Mode", name_len); +- +- for (; find_kctl_name(codec, name, idx); idx++) +- ; ++ snd_hda_get_pin_label(codec, pin, &spec->autocfg, name, name_len); ++ hda_append_suffix(name, " Jack Mode", name_len); + } + + static int get_out_jack_num_items(struct hda_codec *codec, hda_nid_t pin) +@@ -5695,7 +5678,7 @@ static void fill_pcm_stream_name(char *str, size_t len, const char *sfx, + break; + } + } +- strlcat(str, sfx, len); ++ hda_append_suffix(str, sfx, len); + } + + /* copy PCM stream info from @default_str, and override non-NULL entries +diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c +--- a/sound/hda/codecs/hdmi/hdmi.c ++++ b/sound/hda/codecs/hdmi/hdmi.c +@@ -2236,8 +2236,10 @@ void snd_hda_hdmi_acomp_pin_eld_notify(void *audio_ptr, int port, int dev_id) + /* skip notification during system suspend (but not in runtime PM); + * the state will be updated at resume + */ +- if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND) ++ if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND) { ++ codec->acomp_requested_resume = 1; + return; ++ } + + snd_hda_hdmi_check_presence_and_report(codec, pin_nid, dev_id); + } +@@ -2346,6 +2348,7 @@ static const struct hda_device_id snd_hda_id_generichdmi[] = { + HDA_CODEC_ID_MODEL(0x1d179f8e, "KX-7000 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x1d179f8f, "KX-7000 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x1d179f90, "KX-7000 HDMI/DP", MODEL_GF), ++ HDA_CODEC_ID_MODEL(0x4c545020, "Lisuan HDMI/DP", MODEL_GENERIC), + HDA_CODEC_ID_MODEL(0x67663d82, "Arise 82 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x67663d83, "Arise 83 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x67663d84, "Arise 84 HDMI/DP", MODEL_GF), +diff --git a/sound/hda/codecs/helpers/razer_blade16_2025.c b/sound/hda/codecs/helpers/razer_blade16_2025.c +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/helpers/razer_blade16_2025.c +@@ -0,0 +1,820 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Razer Blade 16 (2025) external smart-amplifier programming and fixup, ++ * to be included from the codec driver. ++ * ++ * The internal speakers are driven by an external smart amplifier whose ++ * crossover, voicing and protection live in a DSP reached over a vendor ++ * coef mailbox on NID 0x20. These tables hold the programming captured ++ * from the Windows driver, reduced to the NID-0x20 traffic this machine ++ * needs (everything else the HDA parser rebuilds). ++ * ++ * Each entry is either a plain codec coef write or a mailbox write: ++ * strobe == 0: coef[addr] = lo ++ * strobe != 0: amp[addr] = (hi << 16) | lo, with the bank selected on ++ * port 0x89 and the write committed by the strobe opcode. ++ */ ++ ++struct alc298_razer_blade16_2025_op { ++ u16 bank; /* mailbox bank (port 0x89); unused for a plain coef */ ++ u16 addr; /* mailbox register, or plain coef index */ ++ u16 hi; /* mailbox high word; unused for a plain coef */ ++ u16 lo; /* mailbox low word, or plain coef value */ ++ u16 strobe; /* mailbox commit opcode; 0 marks a plain coef write */ ++}; ++ ++/* program the external DSP at init; the amp is woken/parked separately */ ++static const struct alc298_razer_blade16_2025_op alc298_razer_blade16_2025_amp_init[] = { ++ { 0x0000, 0xf102, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xf103, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc000, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xea00, 0x0000, 0x0047, 0xb031 }, ++ { 0x0000, 0xf20d, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xf212, 0x0000, 0x003e, 0xb031 }, ++ { 0x0000, 0xc001, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc003, 0x0000, 0x0022, 0xb031 }, ++ { 0x0000, 0xc004, 0x0000, 0x0044, 0xb031 }, ++ { 0x0000, 0xc005, 0x0000, 0x0044, 0xb031 }, ++ { 0x0000, 0xc007, 0x0000, 0x0064, 0xb031 }, ++ { 0x0000, 0xc00e, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xf223, 0x0000, 0x007f, 0xb031 }, ++ { 0x0000, 0xf224, 0x0000, 0x00db, 0xb031 }, ++ { 0x0000, 0xf225, 0x0000, 0x00ee, 0xb031 }, ++ { 0x0000, 0xf226, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xf227, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xf21a, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xf242, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xc120, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xc125, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xc321, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xc200, 0x0000, 0x00d8, 0xb031 }, ++ { 0x0000, 0xc201, 0x0000, 0x0027, 0xb031 }, ++ { 0x0000, 0xc202, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xc400, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc401, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xc402, 0x0000, 0x00e0, 0xb031 }, ++ { 0x0000, 0xc403, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc404, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xc406, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xc407, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc408, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xc300, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc125, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xdf00, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xdf5f, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xdf60, 0x0000, 0x00a7, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x0084, 0xb031 }, ++ { 0x0000, 0xc206, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xf10a, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f4, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x00e0, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f4, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x0065, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f7, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f7, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0031, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xe706, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xe707, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xe806, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xe807, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xce04, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xce05, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xce06, 0x0000, 0x0031, 0xb031 }, ++ { 0x0000, 0xce07, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xcf04, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xcf05, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xcf06, 0x0000, 0x0031, 0xb031 }, ++ { 0x0000, 0xcf07, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xc130, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xe000, 0x0000, 0x00a8, 0xb031 }, ++ { 0x4100, 0x1888, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc121, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xea00, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xf800, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xca00, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xca10, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca02, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xca12, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xed00, 0x0000, 0x0090, 0xb031 }, ++ { 0x0000, 0xcc2c, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc2d, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xcc2e, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xcc2f, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcd2c, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd2d, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xcd2e, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xcd2f, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcc24, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc25, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xcc26, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xcc27, 0x0000, 0x0085, 0xb031 }, ++ { 0x0000, 0xcd24, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd25, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xcd26, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xcd27, 0x0000, 0x0085, 0xb031 }, ++ { 0x0000, 0xcc20, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc21, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc22, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcd20, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd21, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd22, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcc16, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xcc17, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd16, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xcd17, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc29, 0x0000, 0x005d, 0xb031 }, ++ { 0x0000, 0xcc2a, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xcd29, 0x0000, 0x005d, 0xb031 }, ++ { 0x0000, 0xcd2a, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xcc31, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xcc32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc33, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc34, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd31, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xcd32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd33, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd34, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc36, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xcc37, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcc38, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcc39, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcd36, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xcd37, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcd38, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcd39, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcc09, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc0a, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xcc0b, 0x0000, 0x007c, 0xb031 }, ++ { 0x0000, 0xcc0c, 0x0000, 0x005b, 0xb031 }, ++ { 0x0000, 0xcd09, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd0a, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xcd0b, 0x0000, 0x007c, 0xb031 }, ++ { 0x0000, 0xcd0c, 0x0000, 0x005b, 0xb031 }, ++ { 0x0000, 0xcc0e, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc0f, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xcc10, 0x0000, 0x003e, 0xb031 }, ++ { 0x0000, 0xcc11, 0x0000, 0x002d, 0xb031 }, ++ { 0x0000, 0xcd0e, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd0f, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xcd10, 0x0000, 0x003e, 0xb031 }, ++ { 0x0000, 0xcd11, 0x0000, 0x002d, 0xb031 }, ++ { 0x0000, 0xccd6, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xccd7, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xcdd6, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcdd7, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xccd8, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xccd9, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xcdd8, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcdd9, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xccda, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xccdb, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xcdda, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcddb, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xc320, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xc321, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xe604, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xdd00, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xdc19, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdc1a, 0x0000, 0x006a, 0xb031 }, ++ { 0x0000, 0xdc1b, 0x0000, 0x00aa, 0xb031 }, ++ { 0x0000, 0xdc1c, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xdc1d, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdc1e, 0x0000, 0x0027, 0xb031 }, ++ { 0x0000, 0xdc1f, 0x0000, 0x0062, 0xb031 }, ++ { 0x0000, 0xdc20, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xde19, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xde1a, 0x0000, 0x006a, 0xb031 }, ++ { 0x0000, 0xde1b, 0x0000, 0x00aa, 0xb031 }, ++ { 0x0000, 0xde1c, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xde1d, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xde1e, 0x0000, 0x0027, 0xb031 }, ++ { 0x0000, 0xde1f, 0x0000, 0x0062, 0xb031 }, ++ { 0x0000, 0xde20, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xdb32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdd32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdb33, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xdd33, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xdb34, 0x0000, 0x001a, 0xb031 }, ++ { 0x0000, 0xdd34, 0x0000, 0x001a, 0xb031 }, ++ { 0x0000, 0xdb15, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xdd15, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xdb17, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xdd17, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xdb94, 0x0000, 0x0070, 0xb031 }, ++ { 0x0000, 0xdd94, 0x0000, 0x0070, 0xb031 }, ++ { 0x0000, 0xdb19, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xdd19, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x001c, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xdb04, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xdb05, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xdd04, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xdd05, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xdbbb, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xdbbc, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xdbbd, 0x0000, 0x00f3, 0xb031 }, ++ { 0x0000, 0xdbbe, 0x0000, 0x00cf, 0xb031 }, ++ { 0x0000, 0xddbb, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xddbc, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xddbd, 0x0000, 0x00f3, 0xb031 }, ++ { 0x0000, 0xddbe, 0x0000, 0x00cf, 0xb031 }, ++ { 0x0000, 0xdb01, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xdd01, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xdb08, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xdd08, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xdc52, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xde52, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0xdf0a, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdf0b, 0x0000, 0x007f, 0xb031 }, ++ { 0x0000, 0xc851, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xc951, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xdf01, 0x0000, 0x0073, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0xf800, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xebcb, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeb10, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb11, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xeb12, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xeb13, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xeb14, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb15, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xeb16, 0x0000, 0x0069, 0xb031 }, ++ { 0x0000, 0xeb21, 0x0000, 0x0011, 0xb031 }, ++ { 0x0000, 0xeb22, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xeb23, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeb24, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb25, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xeb26, 0x0000, 0x0055, 0xb031 }, ++ { 0x0000, 0xeb27, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xeb28, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb29, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xeb2a, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xeb2b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xeb30, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb31, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xeb32, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xeb33, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb34, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb35, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xeb36, 0x0000, 0x0024, 0xb031 }, ++ { 0x0000, 0xeb37, 0x0000, 0x0068, 0xb031 }, ++ { 0x0000, 0xeb40, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb41, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xeb42, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xeb43, 0x0000, 0x00e8, 0xb031 }, ++ { 0x0000, 0xeb44, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb45, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xeb46, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xeb47, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb48, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb49, 0x0000, 0x00c5, 0xb031 }, ++ { 0x0000, 0xeb4a, 0x0000, 0x0032, 0xb031 }, ++ { 0x0000, 0xeb4b, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeb50, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb51, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xeb52, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xeb53, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb54, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb55, 0x0000, 0x007d, 0xb031 }, ++ { 0x0000, 0xeb56, 0x0000, 0x00d9, 0xb031 }, ++ { 0x0000, 0xeb57, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xeb60, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xeb61, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xeb62, 0x0000, 0x00b5, 0xb031 }, ++ { 0x0000, 0xeb63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb64, 0x0000, 0x00fa, 0xb031 }, ++ { 0x0000, 0xeb65, 0x0000, 0x0086, 0xb031 }, ++ { 0x0000, 0xeb66, 0x0000, 0x00a8, 0xb031 }, ++ { 0x0000, 0xeb67, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb68, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xeb69, 0x0000, 0x009a, 0xb031 }, ++ { 0x0000, 0xeb6a, 0x0000, 0x002f, 0xb031 }, ++ { 0x0000, 0xeb6b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xebb6, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeb70, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xeb71, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xeb72, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xeb73, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xeb74, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xeb75, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xeb76, 0x0000, 0x002c, 0xb031 }, ++ { 0x0000, 0xeb77, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xeb80, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb81, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xeb82, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xeb83, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xeb84, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xeb85, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xeb86, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xeb87, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xeb88, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb89, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xeb8a, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xeb8b, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xebb8, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeb90, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xeb91, 0x0000, 0x008b, 0xb031 }, ++ { 0x0000, 0xeb92, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xeb93, 0x0000, 0x0064, 0xb031 }, ++ { 0x0000, 0xeb94, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xeb95, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xeb96, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xeb97, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xeba0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeba1, 0x0000, 0x0091, 0xb031 }, ++ { 0x0000, 0xeba2, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xeba3, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeba4, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xeba5, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeba6, 0x0000, 0x0093, 0xb031 }, ++ { 0x0000, 0xeba7, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xeba8, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xeba9, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xebaa, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xebab, 0x0000, 0x006c, 0xb031 }, ++ { 0x0000, 0xebc3, 0x0000, 0x00ff, 0xb031 }, ++ { 0x0000, 0xebc4, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xeb00, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xeb01, 0x0000, 0x00a9, 0xb031 }, ++ { 0x0000, 0xeb02, 0x0000, 0x00df, 0xb031 }, ++ { 0x0000, 0xeb03, 0x0000, 0x007b, 0xb031 }, ++ { 0x0000, 0xeb05, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xeb06, 0x0000, 0x005a, 0xb031 }, ++ { 0x0000, 0xeb07, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xebc0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xd0cb, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xd0b0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd010, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd011, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd012, 0x0000, 0x0092, 0xb031 }, ++ { 0x0000, 0xd013, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd014, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd015, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xd016, 0x0000, 0x0072, 0xb031 }, ++ { 0x0000, 0xd017, 0x0000, 0x00b0, 0xb031 }, ++ { 0x0000, 0xd020, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd021, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd022, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd023, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd024, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd025, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd026, 0x0000, 0x0090, 0xb031 }, ++ { 0x0000, 0xd027, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xd028, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd029, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd02a, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd02b, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c3, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c4, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd0c0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xce6a, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xce63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xce64, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xdb04, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xdb05, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xcc00, 0x0000, 0x00c1, 0xb031 }, ++ { 0x0000, 0xca02, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xca00, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca62, 0x0000, 0x0077, 0xb031 }, ++ { 0x0000, 0xca65, 0x0000, 0x0082, 0xb031 }, ++ { 0x0000, 0xca68, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xca75, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xca7b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xca00, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xca02, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc860, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc861, 0x0000, 0x00e5, 0xb031 }, ++ { 0x0000, 0xc862, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xc863, 0x0000, 0x00c6, 0xb031 }, ++ { 0x0000, 0xc864, 0x0000, 0x00ba, 0xb031 }, ++ { 0x0000, 0xc865, 0x0000, 0x0061, 0xb031 }, ++ { 0x0000, 0xc867, 0x0000, 0x0017, 0xb031 }, ++ { 0x0000, 0xc868, 0x0000, 0x00ed, 0xb031 }, ++ { 0x0000, 0xc869, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xc86a, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xc86b, 0x0000, 0x00e2, 0xb031 }, ++ { 0x0000, 0xc875, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xc87b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xc836, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc8a0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc833, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xc834, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc83f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc83e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc83a, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xc83b, 0x0000, 0x0089, 0xb031 }, ++ { 0x0000, 0xc830, 0x0000, 0x0013, 0xb031 }, ++ { 0x0000, 0xc826, 0x0000, 0x00d1, 0xb031 }, ++ { 0x0000, 0xc890, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc823, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc824, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xc82f, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xc82e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc820, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc816, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc880, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xc813, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc814, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc81f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc81e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc81a, 0x0000, 0x0066, 0xb031 }, ++ { 0x0000, 0xc81b, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc810, 0x0000, 0x000d, 0xb031 }, ++ { 0x0000, 0xc846, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc8d0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc844, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc84f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc84e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc84a, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc84b, 0x0000, 0x0056, 0xb031 }, ++ { 0x0000, 0xc800, 0x0000, 0x00b0, 0xb031 }, ++ { 0x0000, 0xc851, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xc500, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc501, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc502, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc503, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc504, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc505, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc506, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc507, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc510, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc511, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc512, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc513, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc514, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc515, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc516, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc517, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc518, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc541, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xd250, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xd251, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xd264, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0xf800, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xecb0, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec10, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec11, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xec12, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xec13, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xec14, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec15, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xec16, 0x0000, 0x0069, 0xb031 }, ++ { 0x0000, 0xec21, 0x0000, 0x0011, 0xb031 }, ++ { 0x0000, 0xec22, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xec23, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xec24, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec25, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xec26, 0x0000, 0x0055, 0xb031 }, ++ { 0x0000, 0xec27, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xec28, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec29, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xec2a, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xec2b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xecb2, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec30, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec31, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xec32, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xec33, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec34, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec35, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xec36, 0x0000, 0x0024, 0xb031 }, ++ { 0x0000, 0xec37, 0x0000, 0x0068, 0xb031 }, ++ { 0x0000, 0xec40, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec41, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xec42, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xec43, 0x0000, 0x00e8, 0xb031 }, ++ { 0x0000, 0xec44, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec45, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xec46, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xec47, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec48, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec49, 0x0000, 0x00c5, 0xb031 }, ++ { 0x0000, 0xec4a, 0x0000, 0x0032, 0xb031 }, ++ { 0x0000, 0xec4b, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xecb4, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec50, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec51, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xec52, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xec53, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec54, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec55, 0x0000, 0x007d, 0xb031 }, ++ { 0x0000, 0xec56, 0x0000, 0x00d9, 0xb031 }, ++ { 0x0000, 0xec57, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec60, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xec61, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xec62, 0x0000, 0x00b5, 0xb031 }, ++ { 0x0000, 0xec63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec64, 0x0000, 0x00fa, 0xb031 }, ++ { 0x0000, 0xec65, 0x0000, 0x0086, 0xb031 }, ++ { 0x0000, 0xec66, 0x0000, 0x00a8, 0xb031 }, ++ { 0x0000, 0xec67, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec68, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xec69, 0x0000, 0x009a, 0xb031 }, ++ { 0x0000, 0xec6a, 0x0000, 0x002f, 0xb031 }, ++ { 0x0000, 0xec6b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xecb6, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xec70, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xec71, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xec72, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xec73, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xec74, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xec75, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xec76, 0x0000, 0x002c, 0xb031 }, ++ { 0x0000, 0xec77, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xec80, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec81, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xec82, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xec83, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xec84, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xec85, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xec86, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xec87, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xec88, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec89, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xec8a, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xec8b, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xecb8, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xec90, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xec91, 0x0000, 0x008b, 0xb031 }, ++ { 0x0000, 0xec92, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xec93, 0x0000, 0x0064, 0xb031 }, ++ { 0x0000, 0xec94, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xec95, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xec96, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xec97, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xeca0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeca1, 0x0000, 0x0091, 0xb031 }, ++ { 0x0000, 0xeca2, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xeca3, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeca4, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xeca5, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeca6, 0x0000, 0x0093, 0xb031 }, ++ { 0x0000, 0xeca7, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xeca8, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xeca9, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xecaa, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xecab, 0x0000, 0x006c, 0xb031 }, ++ { 0x0000, 0xebc3, 0x0000, 0x00ff, 0xb031 }, ++ { 0x0000, 0xebc4, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xec00, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xec01, 0x0000, 0x00a9, 0xb031 }, ++ { 0x0000, 0xec02, 0x0000, 0x00df, 0xb031 }, ++ { 0x0000, 0xec03, 0x0000, 0x007b, 0xb031 }, ++ { 0x0000, 0xec05, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xec06, 0x0000, 0x005a, 0xb031 }, ++ { 0x0000, 0xec07, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xebc0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xd1b0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd110, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd111, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd112, 0x0000, 0x0092, 0xb031 }, ++ { 0x0000, 0xd113, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd114, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd115, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xd116, 0x0000, 0x0072, 0xb031 }, ++ { 0x0000, 0xd117, 0x0000, 0x00b0, 0xb031 }, ++ { 0x0000, 0xd120, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd121, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd122, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd123, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd124, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd125, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd126, 0x0000, 0x0090, 0xb031 }, ++ { 0x0000, 0xd127, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xd128, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd129, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd12a, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd12b, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c3, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c4, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd0c0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xce63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xce64, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xdd04, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xdd05, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xca12, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xca10, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca82, 0x0000, 0x0077, 0xb031 }, ++ { 0x0000, 0xca85, 0x0000, 0x0082, 0xb031 }, ++ { 0x0000, 0xca88, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xca95, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xca9b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xca10, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca12, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc960, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc961, 0x0000, 0x00e5, 0xb031 }, ++ { 0x0000, 0xc962, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xc963, 0x0000, 0x00c6, 0xb031 }, ++ { 0x0000, 0xc964, 0x0000, 0x00ba, 0xb031 }, ++ { 0x0000, 0xc965, 0x0000, 0x0061, 0xb031 }, ++ { 0x0000, 0xc967, 0x0000, 0x0017, 0xb031 }, ++ { 0x0000, 0xc968, 0x0000, 0x00ed, 0xb031 }, ++ { 0x0000, 0xc969, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xc96a, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xc96b, 0x0000, 0x00e2, 0xb031 }, ++ { 0x0000, 0xc975, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xc97b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xc936, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc9a0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc933, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xc934, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc93f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc93e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc93a, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xc93b, 0x0000, 0x0089, 0xb031 }, ++ { 0x0000, 0xc930, 0x0000, 0x0013, 0xb031 }, ++ { 0x0000, 0xc926, 0x0000, 0x00d1, 0xb031 }, ++ { 0x0000, 0xc990, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc923, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc924, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xc92f, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xc92e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc920, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc916, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc980, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xc913, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc914, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc91f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc91e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc91a, 0x0000, 0x0066, 0xb031 }, ++ { 0x0000, 0xc91b, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc910, 0x0000, 0x000d, 0xb031 }, ++ { 0x0000, 0xc946, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc9d0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc944, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc94f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc94e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc94a, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc94b, 0x0000, 0x0056, 0xb031 }, ++ { 0x0000, 0xc951, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xc600, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc601, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc602, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc603, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc604, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc605, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc606, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc607, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc610, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc611, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc612, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc613, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc614, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc615, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc616, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc617, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc618, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc541, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xc802, 0x0000, 0x00d2, 0xb031 }, ++ { 0x0000, 0xc902, 0x0000, 0x00d2, 0xb031 }, ++ { 0x0000, 0xc800, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd250, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xd252, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xd264, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xdf00, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xdbb5, 0x0041, 0xd27f, 0xb037 }, ++ { 0x0000, 0xddb5, 0x0040, 0x6e3b, 0xb037 }, ++ { 0x0000, 0xdb93, 0x0000, 0x009e, 0xb033 }, ++ { 0x0000, 0xdd93, 0x0000, 0x009e, 0xb033 }, ++ { 0x0000, 0xdb12, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xdd12, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xdb08, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xdd08, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0x0010, 0x0000, 0x0f21, 0x0000 }, ++}; ++ ++/* wake the amp (ea00=0x43, c121=0x0b, f109=0xe0) */ ++static const struct alc298_razer_blade16_2025_op alc298_razer_blade16_2025_amp_wake[] = { ++ { 0x0000, 0xea00, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xc121, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x00e0, 0xb031 }, ++}; ++ ++/* park the amp (ea00=0x47, c121=0x0a, f109=0xa0) */ ++static const struct alc298_razer_blade16_2025_op alc298_razer_blade16_2025_amp_sleep[] = { ++ { 0x0000, 0xea00, 0x0000, 0x0047, 0xb031 }, ++ { 0x0000, 0xc121, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x00a0, 0xb031 }, ++}; ++ ++/* ++ * Razer Blade 16 (2025): Realtek ALC298 driving an external smart amp. ++ * Crossover, voicing and speaker protection live in an external DSP ++ * reached over a vendor-coef mailbox on NID 0x20; the captured DSP ++ * programming is in the tables above. NID 0x14, the BIOS-disabled ++ * tweeter pin, is re-exposed via a pin-config override so the parser ++ * drives it as a normal internal speaker (shared volume/mute, auto-mute ++ * on headphone insertion). ++ * ++ * alc298_razer_blade16_2025_apply() runs one op-table: a plain coef write ++ * (strobe == 0) goes through alc_write_coef_idx(), while a mailbox op ++ * selects a bank and streams four PROC_COEF words (addr/hi/lo/commit) ++ * into a single coef index (0x23) on NID 0x20 to reach the external DSP ++ * -- that one-index/many-words form is why it cannot be expressed as an ++ * alc_process_coef_fw() table (one value per index). ++ */ ++static void alc298_razer_blade16_2025_apply(struct hda_codec *codec, ++ const struct alc298_razer_blade16_2025_op *seq, ++ int num) ++{ ++ int i; ++ ++ for (i = 0; i < num; i++) { ++ if (!seq[i].strobe) { ++ alc_write_coef_idx(codec, seq[i].addr, seq[i].lo); ++ continue; ++ } ++ alc_write_coef_idx(codec, 0x89, seq[i].bank); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 0x23); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].addr); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].hi); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].lo); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].strobe); ++ } ++} ++ ++static void alc298_razer_blade16_2025_pcm_hook(struct hda_pcm_stream *hinfo, ++ struct hda_codec *codec, ++ struct snd_pcm_substream *substream, ++ int action) ++{ ++ /* power the external amp only while a stream is running */ ++ switch (action) { ++ case HDA_GEN_PCM_ACT_PREPARE: ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_wake, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_wake)); ++ break; ++ case HDA_GEN_PCM_ACT_CLEANUP: ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_sleep, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_sleep)); ++ break; ++ } ++} ++ ++static void alc298_fixup_razer_blade16_2025(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ /* ++ * Pin every output to a fixed converter so the routing does not ++ * rely on the generic parser's DAC-allocation heuristics: without ++ * a fixed assignment the parser brings up only one speaker pin and ++ * leaves the other silent, and the allocation could change between ++ * kernel versions. Both speaker pins share DAC 0x03 through mixer ++ * 0x0d: that is the only converter that drives the woofer (NID ++ * 0x17) on this machine -- feeding it from DAC 0x02 or the digital ++ * converter 0x06 leaves the woofer silent. Both pins carry the ++ * same stereo stream and the external amp does the crossover. ++ * Headphone NID 0x21 keeps DAC 0x02. ++ */ ++ static const hda_nid_t preferred_pairs[] = { ++ 0x21, 0x02, ++ 0x14, 0x03, ++ 0x17, 0x03, ++ 0 ++ }; ++ struct alc_spec *spec = codec->spec; ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->gen.preferred_dacs = preferred_pairs; ++ break; ++ case HDA_FIXUP_ACT_PROBE: ++ spec->gen.pcm_playback_hook = alc298_razer_blade16_2025_pcm_hook; ++ break; ++ case HDA_FIXUP_ACT_INIT: ++ /* ++ * Wake the amp, program its DSP at boot and on resume, then ++ * park it. It is woken again for playback and parked ++ * afterwards from the pcm_playback_hook. ++ */ ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_wake, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_wake)); ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_init, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_init)); ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_sleep, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_sleep)); ++ break; ++ } ++} +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -296,7 +296,8 @@ static void alc282_init(struct hda_codec *codec) + msleep(100); + + /* Headphone capless set to normal mode */ +- alc_write_coef_idx(codec, 0x78, coef78); ++ if (coef78 != -1) ++ alc_write_coef_idx(codec, 0x78, coef78); + } + + static void alc282_shutup(struct hda_codec *codec) +@@ -333,7 +334,8 @@ static void alc282_shutup(struct hda_codec *codec) + + alc_auto_setup_eapd(codec, false); + alc_shutup_pins(codec); +- alc_write_coef_idx(codec, 0x78, coef78); ++ if (coef78 != -1) ++ alc_write_coef_idx(codec, 0x78, coef78); + } + + static const struct coef_fw alc283_coefs[] = { +@@ -585,15 +587,19 @@ static void alc285_hp_init(struct hda_codec *codec) + + alc_write_coefex_idx(codec, 0x58, 0x00, 0xf888); /* HP depop procedure start */ + val = alc_read_coefex_idx(codec, 0x58, 0x00); +- for (i = 0; i < 20 && val & 0x8000; i++) { ++ for (i = 0; i < 20 && val != -1 && val & 0x8000; i++) { + msleep(50); + val = alc_read_coefex_idx(codec, 0x58, 0x00); + } /* Wait for depop procedure finish */ + +- alc_write_coefex_idx(codec, 0x58, 0x00, val); /* write back the result */ +- alc_update_coef_idx(codec, 0x38, 1<<4, coef38); +- alc_update_coef_idx(codec, 0x0d, 0x110, coef0d); +- alc_update_coef_idx(codec, 0x36, 3<<13, coef36); ++ if (val != -1) ++ alc_write_coefex_idx(codec, 0x58, 0x00, val); /* write back the result */ ++ if (coef38 != -1) ++ alc_update_coef_idx(codec, 0x38, 1<<4, coef38); ++ if (coef0d != -1) ++ alc_update_coef_idx(codec, 0x0d, 0x110, coef0d); ++ if (coef36 != -1) ++ alc_update_coef_idx(codec, 0x36, 3<<13, coef36); + + msleep(50); + alc_update_coef_idx(codec, 0x4a, 1<<15, 0); +@@ -858,7 +864,7 @@ static void alc294_hp_init(struct hda_codec *codec) + + /* Wait for depop procedure finish */ + val = alc_read_coefex_idx(codec, 0x58, 0x01); +- for (i = 0; i < 20 && val & 0x0080; i++) { ++ for (i = 0; i < 20 && val != -1 && val & 0x0080; i++) { + msleep(50); + val = alc_read_coefex_idx(codec, 0x58, 0x01); + } +@@ -1392,6 +1398,15 @@ static void alc245_fixup_hp_gpio_led(struct hda_codec *codec, + alc_fixup_hp_gpio_led(codec, action, 0, 0x04); + } + ++static void alc245_fixup_minisforum_jack_detect(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ if (action == HDA_FIXUP_ACT_INIT) ++ /* Clear Reset HP JD while preserving the other coefficient bits. */ ++ alc_update_coef_idx(codec, 0x4a, BIT(15), 0); ++} ++ + /* turn on/off mic-mute LED per capture hook via VREF change */ + static int vref_micmute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +@@ -2557,6 +2572,13 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec, + } + } + ++static void alc285_lenovo_dac_rename(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ if (action == HDA_FIXUP_ACT_BUILD) ++ rename_ctl(codec, "Line Out Playback Volume", ++ "Headphone Playback Volume"); ++} + static void alc290_fixup_mono_speakers(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { +@@ -3262,6 +3284,36 @@ static void find_cirrus_companion_amps(struct hda_codec *cdc) + comp_generic_fixup(cdc, HDA_FIXUP_ACT_PRE_PROBE, bus, acpi_ids[i].hid, match, count); + } + ++static void aw88399_fixup_i2c_two(struct hda_codec *cdc, const struct hda_fixup *fix, int action) ++{ ++ comp_generic_fixup(cdc, action, "i2c", "AWDZ8399", "-%s:00-aw88399-hda.%d", 2); ++} ++ ++static void alc287_fixup_legion_16iax10h_aw88399(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ /* ++ * Force DAC 0x02 for the bass speaker 0x17, as the default 0x06 lacks volume controls. ++ */ ++ static const hda_nid_t conn[] = { 0x02 }; ++ struct alc_spec *spec = codec->spec; ++ ++ alc269_fixup_limit_int_mic_boost(codec, fix, action); ++ alc_fixup_headset_mode_no_hp_mic(codec, fix, action); ++ alc_fixup_headset_jack(codec, fix, action); ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->gen.suppress_auto_mic = 1; ++ snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn); ++ break; ++ case HDA_FIXUP_ACT_BUILD: ++ spec->gen.multiout.max_channels = 2; ++ spec->gen.pcm_rec[0]->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = 2; ++ break; ++ } ++} ++ + static void cs35l41_fixup_i2c_two(struct hda_codec *cdc, const struct hda_fixup *fix, int action) + { + comp_generic_fixup(cdc, action, "i2c", "CSC3551", "-%s:00-cs35l41-hda.%d", 2); +@@ -3357,6 +3409,9 @@ static void alc287_fixup_acer_micmute_led(struct hda_codec *codec, + /* for alc285_fixup_ideapad_s740_coef() */ + #include "../helpers/ideapad_s740.c" + ++/* for alc298_fixup_razer_blade16_2025() */ ++#include "../helpers/razer_blade16_2025.c" ++ + static const struct coef_fw alc256_fixup_set_coef_defaults_coefs[] = { + WRITE_COEF(0x10, 0x0020), WRITE_COEF(0x24, 0x0000), + WRITE_COEF(0x26, 0x0000), WRITE_COEF(0x29, 0x3000), +@@ -3524,6 +3579,16 @@ static void alc287_fixup_yoga9_14iap7_bass_spk_pin(struct hda_codec *codec, + } + } + ++static void alc_fixup_yoga_pro7_audio(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ /* Reuse the DAC routing selected for ThinkPad X1 Gen7 */ ++ alc285_fixup_thinkpad_x1_gen7(codec, fix, action); ++ ++ /* Limit the internal mic boost to 0 or 1 to avoid noise */ ++ alc269_fixup_limit_int_mic_boost(codec, fix, action); ++} ++ + static void alc295_fixup_dell_inspiron_top_speakers(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { +@@ -3929,6 +3994,7 @@ enum { + ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572, + ALC269_FIXUP_ACER_AC700, + ALC269_FIXUP_LIMIT_INT_MIC_BOOST, ++ ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST, + ALC269VB_FIXUP_ASUS_ZENBOOK, + ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A, + ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE, +@@ -3997,6 +4063,7 @@ enum { + ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC295_FIXUP_DISABLE_DAC3, + ALC285_FIXUP_SPEAKER2_TO_DAC1, ++ ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1, + ALC285_FIXUP_ASUS_SPEAKER2_TO_DAC1, + ALC285_FIXUP_ASUS_HEADSET_MIC, + ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS, +@@ -4066,7 +4133,6 @@ enum { + ALC294_FIXUP_ASUS_ALLY, + ALC294_FIXUP_ASUS_ALLY_PINS, + ALC294_FIXUP_ASUS_ALLY_VERBS, +- ALC294_FIXUP_ASUS_ALLY_SPEAKER, + ALC294_FIXUP_ASUS_HPE, + ALC294_FIXUP_ASUS_COEF_1B, + ALC294_FIXUP_ASUS_GX502_HP, +@@ -4091,6 +4157,8 @@ enum { + ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, + ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS, + ALC298_FIXUP_LG_GRAM_STYLE_14, ++ ALC298_FIXUP_RAZER_BLADE16_2025_PINS, ++ ALC298_FIXUP_RAZER_BLADE16_2025, + ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, + ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, + ALC295_FIXUP_ASUS_MIC_NO_PRESENCE, +@@ -4108,7 +4176,6 @@ enum { + ALC269_FIXUP_LEMOTE_A190X, + ALC256_FIXUP_INTEL_NUC8_RUGGED, + ALC233_FIXUP_INTEL_NUC8_DMIC, +- ALC233_FIXUP_INTEL_NUC8_BOOST, + ALC256_FIXUP_INTEL_NUC10, + ALC255_FIXUP_XIAOMI_HEADSET_MIC, + ALC274_FIXUP_HP_MIC, +@@ -4139,6 +4206,7 @@ enum { + ALC298_FIXUP_LENOVO_C940_DUET7, + ALC287_FIXUP_LENOVO_YOGA_BOOK_9I, + ALC287_FIXUP_LENOVO_YOGA_PRO7, ++ ALC287_FIXUP_LENOVO_XPAD_HEADSET_JACK, + ALC287_FIXUP_13S_GEN2_SPEAKERS, + ALC256_FIXUP_SET_COEF_DEFAULTS, + ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE, +@@ -4183,7 +4251,6 @@ enum { + ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD, + ALC2XX_FIXUP_HEADSET_MIC, + ALC289_FIXUP_DELL_CS35L41_SPI_2, +- ALC294_FIXUP_CS35L41_I2C_2, + ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, + ALC256_FIXUP_HEADPHONE_AMP_VOL, + ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX, +@@ -4197,6 +4264,7 @@ enum { + ALC287_FIXUP_LENOVO_THKPAD_WH_ALC1318, + ALC256_FIXUP_CHROME_BOOK, + ALC245_FIXUP_CLEVO_NOISY_MIC, ++ ALC245_FIXUP_MINISFORUM_JACK_DETECT, + ALC269_FIXUP_VAIO_VJFH52_MIC_NO_PRESENCE, + ALC233_FIXUP_MEDION_MTL_SPK, + ALC233_FIXUP_STARLABS_STARFIGHTER, +@@ -4206,7 +4274,6 @@ enum { + ALC274_FIXUP_HP_AIO_BIND_DACS, + ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2, + ALC285_FIXUP_ASUS_GA605K_HEADSET_MIC, +- ALC285_FIXUP_ASUS_GA605K_I2C_SPEAKER2_TO_DAC1, + ALC269_FIXUP_POSITIVO_P15X_HEADSET_MIC, + ALC289_FIXUP_ASUS_ZEPHYRUS_DUAL_SPK, + ALC256_FIXUP_VAIO_RPL_MIC_NO_PRESENCE, +@@ -4226,6 +4293,10 @@ enum { + ALC236_FIXUP_DELL_HP_POP_NOISE, + ALC274_FIXUP_HP_89E9_GPIO, + ALC274_FIXUP_HP_VERBS, ++ ALC287_FIXUP_AW88399_I2C_2, ++ ALC287_FIXUP_LENOVO_LEGION_AW88399, ++ ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, ++ ALC285_LENOVO_DAC_RENAME, + }; + + /* A special fixup for Lenovo C940 and Yoga Duet 7; +@@ -4263,6 +4334,20 @@ static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, + } + + static const struct hda_fixup alc269_fixups[] = { ++ [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x14, 0x90170121 }, /* tweeter as internal speaker, seq 1 */ ++ { 0x17, 0x90170120 }, /* woofer as internal speaker, seq 0 */ ++ { } ++ }, ++ .chained = true, ++ .chain_id = ALC298_FIXUP_RAZER_BLADE16_2025, ++ }, ++ [ALC298_FIXUP_RAZER_BLADE16_2025] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc298_fixup_razer_blade16_2025, ++ }, + [ALC233_FIXUP_WUJIE_SPEAKERS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { +@@ -4703,6 +4788,10 @@ static const struct hda_fixup alc269_fixups[] = { + .chain_id = ALC271_FIXUP_DMIC, + }, + [ALC269_FIXUP_LIMIT_INT_MIC_BOOST] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc269_fixup_limit_int_mic_boost ++ }, ++ [ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_limit_int_mic_boost, + .chained = true, +@@ -5078,11 +5167,7 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, + .chained = true, +- .chain_id = ALC233_FIXUP_INTEL_NUC8_BOOST, +- }, +- [ALC233_FIXUP_INTEL_NUC8_BOOST] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = alc269_fixup_limit_int_mic_boost ++ .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST, + }, + [ALC255_FIXUP_DELL_SPK_NOISE] = { + .type = HDA_FIXUP_FUNC, +@@ -5143,6 +5228,10 @@ static const struct hda_fixup alc269_fixups[] = { + [ALC285_FIXUP_SPEAKER2_TO_DAC1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, ++ }, ++ [ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_fixup_speaker2_to_dac1, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI + }, +@@ -5662,11 +5751,7 @@ static const struct hda_fixup alc269_fixups[] = { + { } + }, + .chained = true, +- .chain_id = ALC294_FIXUP_ASUS_ALLY_SPEAKER +- }, +- [ALC294_FIXUP_ASUS_ALLY_SPEAKER] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = alc285_fixup_speaker2_to_dac1, ++ .chain_id = ALC285_FIXUP_SPEAKER2_TO_DAC1 + }, + [ALC285_FIXUP_THINKPAD_X1_GEN7] = { + .type = HDA_FIXUP_FUNC, +@@ -6209,11 +6294,20 @@ static const struct hda_fixup alc269_fixups[] = { + }, + [ALC287_FIXUP_LENOVO_YOGA_PRO7] = { + .type = HDA_FIXUP_FUNC, +- /* Reuse the DAC routing selected for ThinkPad X1 Gen7 */ +- .v.func = alc285_fixup_thinkpad_x1_gen7, ++ .v.func = alc_fixup_yoga_pro7_audio, + .chained = true, + .chain_id = ALC269_FIXUP_LENOVO_XPAD_ACPI, + }, ++ [ALC287_FIXUP_LENOVO_XPAD_HEADSET_JACK] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_headset_jack, ++ .chained = true, ++ /* Same as ALC285_FIXUP_THINKPAD_HEADSET_JACK, except that the ++ * mute LEDs are driven through ideapad_laptop rather than ++ * thinkpad_acpi. ++ */ ++ .chain_id = ALC287_FIXUP_LENOVO_YOGA_PRO7, ++ }, + [ALC623_FIXUP_LENOVO_THINKSTATION_P340] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_no_shutup, +@@ -6653,10 +6747,6 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC289_FIXUP_DUAL_SPK + }, +- [ALC294_FIXUP_CS35L41_I2C_2] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = cs35l41_fixup_i2c_two, +- }, + [ALC256_FIXUP_ACER_SFG16_MICMUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_acer_sfg16_micmute_led, +@@ -6729,6 +6819,10 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE, + }, ++ [ALC245_FIXUP_MINISFORUM_JACK_DETECT] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc245_fixup_minisforum_jack_detect, ++ }, + [ALC269_FIXUP_VAIO_VJFH52_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { +@@ -6749,6 +6843,8 @@ static const struct hda_fixup alc269_fixups[] = { + [ALC233_FIXUP_STARLABS_STARFIGHTER] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_fixup_starlabs_starfighter, ++ .chained = true, ++ .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST, + }, + [ALC294_FIXUP_BASS_SPEAKER_15] = { + .type = HDA_FIXUP_FUNC, +@@ -6776,11 +6872,7 @@ static const struct hda_fixup alc269_fixups[] = { + { } + }, + .chained = true, +- .chain_id = ALC285_FIXUP_ASUS_GA605K_I2C_SPEAKER2_TO_DAC1 +- }, +- [ALC285_FIXUP_ASUS_GA605K_I2C_SPEAKER2_TO_DAC1] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = alc285_fixup_speaker2_to_dac1, ++ .chain_id = ALC285_FIXUP_SPEAKER2_TO_DAC1 + }, + [ALC269_FIXUP_POSITIVO_P15X_HEADSET_MIC] = { + .type = HDA_FIXUP_FUNC, +@@ -6898,6 +6990,26 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC274_FIXUP_HP_89E9_GPIO, + }, ++ [ALC287_FIXUP_AW88399_I2C_2] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = aw88399_fixup_i2c_two, ++ }, ++ [ALC287_FIXUP_LENOVO_LEGION_AW88399] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc287_fixup_legion_16iax10h_aw88399, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_AW88399_I2C_2, ++ }, ++ [ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_headset_jack, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, ++ }, ++ [ALC285_LENOVO_DAC_RENAME] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_lenovo_dac_rename, ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -6951,8 +7063,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1597, "Acer Nitro 5 AN517-55", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x159e, "Acer Nitro 5 AN515-46", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), ++ SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), +@@ -6961,6 +7075,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1025, 0x182c, "Acer Helios ZPD", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1025, 0x1844, "Acer Helios ZPS", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1025, 0x1909, "Acer Nitro ANV16-42", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1028, 0x0470, "Dell M101z", ALC269_FIXUP_DELL_M101Z), + SND_PCI_QUIRK(0x1028, 0x053c, "Dell Latitude E5430", ALC292_FIXUP_DELL_E7X), + SND_PCI_QUIRK(0x1028, 0x054b, "Dell XPS one 2710", ALC275_FIXUP_DELL_XPS), +@@ -7145,10 +7260,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), + SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), + SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), ++ SND_PCI_QUIRK(0x103c, 0x85f3, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8603, "HP Omen 17-cb0xxx", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x860c, "HP ZBook 17 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x860f, "HP ZBook 15 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x861f, "HP Elite Dragonfly G1", ALC285_FIXUP_HP_GPIO_AMP_INIT), ++ SND_PCI_QUIRK(0x103c, 0x864f, "HP Laptop 15-dy0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x869d, "HP", ALC236_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x86c1, "HP Laptop 15-da3001TU", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x86c7, "HP Envy AiO 32", ALC274_FIXUP_HP_ENVY_GPIO), +@@ -7259,6 +7376,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x89e7, "HP Elite x2 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a05, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a06, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), ++ SND_PCI_QUIRK(0x103c, 0x8a0e, "HP Pavilion Laptop 15-eh2xxx", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a0f, "HP Pavilion 14-ec1xxx", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a1b, "HP 255 15.6 inch G9 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8a1f, "HP Laptop 14s-dr5xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), +@@ -7350,10 +7468,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8bf0, "HP", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c15, "HP Spectre x360 2-in-1 Laptop 14-eu0xxx", ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX), + SND_PCI_QUIRK(0x103c, 0x8c16, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), +- SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre 16", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), + SND_PCI_QUIRK(0x103c, 0x8c21, "HP Pavilion Plus Laptop 14-ey0XXX", ALC245_FIXUP_HP_X360_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8c2d, "HP Victus 15-fa1xxx (MB 8C2D)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c30, "HP Victus 15-fb1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8c3f, "HP Victus 15-fa1xxx (MB 8C3F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c46, "HP EliteBook 830 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c47, "HP EliteBook 840 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c48, "HP EliteBook 860 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), +@@ -7502,6 +7621,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8f42, "HP ZBook 8 G2a 14W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8f57, "HP Trekker G7JC", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8f62, "HP ZBook 8 G2a 16W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), ++ SND_PCI_QUIRK(0x103c, 0x8f7e, "HP Messi", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8f94, "HP ZBook 8 G2a 14", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), + SND_PCI_QUIRK(0x103c, 0x8f95, "HP ZBook 8 G2a 16", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), + SND_PCI_QUIRK(0x1043, 0x1024, "ASUS Zephyrus G14 2025", ALC285_FIXUP_ASUS_GA403U_HEADSET_MIC), +@@ -7509,6 +7629,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1034, "ASUS GU605C", ALC285_FIXUP_ASUS_GU605_SPI_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300), ++ SND_PCI_QUIRK(0x1043, 0x1044, "ASUS GA403UM", ALC285_FIXUP_ASUS_GA403U_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1054, "ASUS G614FH/FM/FP", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x106d, "Asus K53BE", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x106f, "ASUS VivoBook X515UA", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), +@@ -7516,16 +7637,15 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x10a1, "ASUS UX391UA", ALC294_FIXUP_ASUS_SPK), + SND_PCI_QUIRK(0x1043, 0x10a4, "ASUS TP3407SA", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), ++ SND_PCI_QUIRK(0x1043, 0x10c4, "ASUS Vivobook S14 S5406SA", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x10d3, "ASUS K6500ZC", ALC294_FIXUP_ASUS_SPK), + SND_PCI_QUIRK(0x1043, 0x1154, "ASUS TP3607SH", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x1043, 0x115d, "Asus 1015E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x1194, "ASUS UM3406KA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x11c0, "ASUS X556UR", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), +- HDA_CODEC_QUIRK(0x1043, 0x1204, "ASUS Strix G16 G615JMR", ALC287_FIXUP_TXNW2781_I2C_ASUS), +- SND_PCI_QUIRK(0x1043, 0x1204, "ASUS Strix G615JHR_JMR_JPR", ALC287_FIXUP_TAS2781_I2C), +- HDA_CODEC_QUIRK(0x1043, 0x1214, "ASUS ROG Strix G615LP", ALC287_FIXUP_TXNW2781_I2C_ASUS), +- SND_PCI_QUIRK(0x1043, 0x1214, "ASUS Strix G615LH_LM_LP", ALC287_FIXUP_TAS2781_I2C), ++ SND_PCI_QUIRK(0x1043, 0x1204, "ASUS Strix G615JHR_JMR_JPR", ALC287_FIXUP_TXNW2781_I2C_ASUS), ++ SND_PCI_QUIRK(0x1043, 0x1214, "ASUS Strix G615LH_LM_LP", ALC287_FIXUP_TXNW2781_I2C_ASUS), + SND_PCI_QUIRK(0x1043, 0x125e, "ASUS Q524UQK", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1271, "ASUS X430UN", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1290, "ASUS X441SA", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), +@@ -7574,7 +7694,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1863, "ASUS UX6404VI/VV", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1881, "ASUS Zephyrus S/M", ALC294_FIXUP_ASUS_GX502_PINS), + SND_PCI_QUIRK(0x1043, 0x18b1, "Asus MJ401TA", ALC256_FIXUP_ASUS_HEADSET_MIC), +- SND_PCI_QUIRK(0x1043, 0x18d3, "ASUS UM3504DA", ALC294_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1043, 0x18d3, "ASUS UM3504DA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x18f1, "Asus FX505DT", ALC256_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x194e, "ASUS UX563FD", ALC294_FIXUP_ASUS_HPE), + SND_PCI_QUIRK(0x1043, 0x1970, "ASUS UX550VE", ALC289_FIXUP_ASUS_GA401), +@@ -7585,7 +7705,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x19f4, "ASUS UM3405GA", ALC294_FIXUP_ASUS_I2C_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1a13, "Asus G73Jw", ALC269_FIXUP_ASUS_G73JW), + SND_PCI_QUIRK(0x1043, 0x1a63, "ASUS UX3405MA", ALC294_FIXUP_ASUS_SPI_HEADSET_MIC), +- SND_PCI_QUIRK(0x1043, 0x1a83, "ASUS UM5302LA", ALC294_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1043, 0x1a83, "ASUS UM5302LA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1a8e, "ASUS G712LWS", ALC294_FIXUP_LENOVO_MIC_LOCATION), + SND_PCI_QUIRK(0x1043, 0x1a8f, "ASUS UX582ZS", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1b11, "ASUS UX431DA", ALC294_FIXUP_ASUS_COEF_1B), +@@ -7614,6 +7734,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1264, "ASUS UM5606KA", ALC294_FIXUP_BASS_SPEAKER_15), + SND_PCI_QUIRK(0x1043, 0x1e02, "ASUS UX3402ZA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1e10, "ASUS VivoBook X507UAR", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), ++ SND_PCI_QUIRK(0x1043, 0x1e3e, "ASUS VivoBook M515DA/X515DAP", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1e11, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA502), + SND_PCI_QUIRK(0x1043, 0x1e12, "ASUS UM3402", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1e1f, "ASUS Vivobook 15 X1504VAP", ALC2XX_FIXUP_HEADSET_MIC), +@@ -7625,8 +7746,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1e93, "ASUS ExpertBook B9403CVAR", ALC294_FIXUP_ASUS_HPE), + SND_PCI_QUIRK(0x1043, 0x1eb3, "ASUS Ally RC72LA", ALC287_FIXUP_ASUS_ALLY_X), + SND_PCI_QUIRK(0x1043, 0x1ed3, "ASUS HN7306W", ALC287_FIXUP_CS35L41_I2C_2), +- HDA_CODEC_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1), +- SND_PCI_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x1043, 0x1c52, "ASUS Zephyrus G15 2022", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1f11, "ASUS Zephyrus G14", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1f12, "ASUS UM5302", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7837,7 +7957,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x2211, "Thinkpad W541", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2212, "Thinkpad T440", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2214, "Thinkpad X240", ALC292_FIXUP_TPT440_DOCK), +- SND_PCI_QUIRK(0x17aa, 0x2215, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x2215, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x2218, "Thinkpad X1 Carbon 2nd", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2223, "ThinkPad T550", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2226, "ThinkPad X250", ALC292_FIXUP_TPT440_DOCK), +@@ -7853,7 +7973,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x224b, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x224c, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x224d, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), +- SND_PCI_QUIRK(0x17aa, 0x225d, "Thinkpad T480", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x225c, "Lenovo ThinkPad X1 Carbon 6th Gen", ALC285_LENOVO_DAC_RENAME), ++ SND_PCI_QUIRK(0x17aa, 0x225d, "Thinkpad T480", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x2288, "Thinkpad X390", ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK), + SND_PCI_QUIRK(0x17aa, 0x2292, "Thinkpad X1 Carbon 7th", ALC285_FIXUP_THINKPAD_HEADSET_JACK), + SND_PCI_QUIRK(0x17aa, 0x22be, "Thinkpad X1 Carbon 8th", ALC285_FIXUP_THINKPAD_HEADSET_JACK), +@@ -7928,7 +8049,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), +- SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_SPEAKER2_TO_DAC1), ++ SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), + HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7963,6 +8084,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38b8, "Yoga S780-14.5 proX AMD YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38b9, "Yoga S780-14.5 proX AMD LX Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38ba, "Yoga S780-14.5 Air AMD quad YC", ALC287_FIXUP_TAS2781_I2C), ++ HDA_CODEC_QUIRK(0x17aa, 0x3926, "Legion Pro 5 16ADR10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), ++ /* Legion R9000P ADR10 shares PCI SSID 17aa:38bb with Yoga S780-14.5 Air AMD quad AAC; ++ * use codec SSID to distinguish them ++ */ ++ HDA_CODEC_QUIRK(0x17aa, 0x3927, "Legion R9000P ADR10", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3928, "Legion R9000P ADR10", ALC287_FIXUP_LENOVO_LEGION_AW88399), + SND_PCI_QUIRK(0x17aa, 0x38bb, "Yoga S780-14.5 Air AMD quad AAC", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38be, "Yoga S980-14.5 proX YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38bf, "Yoga S980-14.5 proX LX Dual", ALC287_FIXUP_TAS2781_I2C), +@@ -7980,7 +8107,6 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38df, "Yoga Y990 Intel YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38e0, "Yoga Y990 Intel VECO Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38f8, "Yoga Book 9i", ALC287_FIXUP_TAS2781_I2C), +- SND_PCI_QUIRK(0x17aa, 0x38df, "Y990 YG DUAL", ALC287_FIXUP_TAS2781_I2C), + /* Legion 7 15ASH11 shares PCI SSID 17aa:38f9 with Thinkbook 16P Gen5; + * use codec SSID to distinguish them + */ +@@ -7990,7 +8116,9 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38fc, "Lenovo Yoga Pro 7 15ASH11", ALC287_FIXUP_LENOVO_YOGA_PRO7), + SND_PCI_QUIRK(0x17aa, 0x38fd, "ThinkBook plus Gen5 Hybrid", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x3902, "Lenovo E50-80", ALC269_FIXUP_DMIC_THINKPAD_ACPI), +- SND_PCI_QUIRK(0x17aa, 0x390d, "Lenovo Yoga Pro 7 14ASP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), ++ HDA_CODEC_QUIRK(0x17aa, 0x3906, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3907, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ SND_PCI_QUIRK(0x17aa, 0x390d, "Lenovo Yoga Pro 7 14ASP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET), + SND_PCI_QUIRK(0x17aa, 0x3911, "Lenovo Yoga Pro 7 14IAH10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), +@@ -7998,14 +8126,19 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x392b, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), ++ HDA_CODEC_QUIRK(0x17aa, 0x3936, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3937, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3938, "Legion Pro 7 16AFR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3939, "Legion Pro 7 16AFR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ SND_PCI_QUIRK(0x17aa, 0x393e, "Lenovo ThinkBook 14 G8+ IPH", ALC287_FIXUP_LENOVO_XPAD_HEADSET_JACK), + HDA_CODEC_QUIRK(0x17aa, 0x394c, "Lenovo Yoga Slim 7 14AGP11", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3977, "IdeaPad S210", ALC283_FIXUP_INT_MIC), + SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo B50-70", ALC269_FIXUP_DMIC_THINKPAD_ACPI), + SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K), +- SND_PCI_QUIRK(0x17aa, 0x5013, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x5013, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x501a, "Thinkpad", ALC283_FIXUP_INT_MIC), + SND_PCI_QUIRK(0x17aa, 0x501e, "Thinkpad L440", ALC292_FIXUP_TPT440_DOCK), +- SND_PCI_QUIRK(0x17aa, 0x5026, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x5026, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x5034, "Thinkpad T450", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x5036, "Thinkpad T450s", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x503c, "Thinkpad L450", ALC292_FIXUP_TPT440_DOCK), +@@ -8018,7 +8151,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x505f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x5062, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x508b, "Thinkpad X12 Gen 1", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), +- SND_PCI_QUIRK(0x17aa, 0x5109, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x5109, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x511e, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), +@@ -8032,9 +8165,14 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1854, 0x0489, "LG gram 16 (16Z90R-A)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), + SND_PCI_QUIRK(0x1854, 0x048a, "LG gram 17 (17ZD90R)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), + SND_PCI_QUIRK(0x1854, 0x0490, "LG Gram Style 14 (14Z90RS)", ALC298_FIXUP_LG_GRAM_STYLE_14), ++ SND_PCI_QUIRK(0x1854, 0x0554, "LG gram 16 (16Z90TR)", ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS), + SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS), + SND_PCI_QUIRK(0x19e5, 0x320f, "Huawei WRT-WX9 ", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x19e5, 0x3212, "Huawei KLV-WX9 ", ALC256_FIXUP_ACER_HEADSET_MIC), ++ SND_PCI_QUIRK(0x1a58, 0x2023, "Razer Blade 16 (2025)", ++ ALC298_FIXUP_RAZER_BLADE16_2025_PINS), ++ SND_PCI_QUIRK(0x1a58, 0x300e, "Razer Blade 16 (2025)", ++ ALC298_FIXUP_RAZER_BLADE16_2025_PINS), + SND_PCI_QUIRK(0x1b35, 0x1235, "CZC B20", ALC269_FIXUP_CZC_B20), + SND_PCI_QUIRK(0x1b35, 0x1236, "CZC TMI", ALC269_FIXUP_CZC_TMI), + SND_PCI_QUIRK(0x1b35, 0x1237, "CZC L101", ALC269_FIXUP_CZC_L101), +@@ -8069,13 +8207,17 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1d72, 0x1947, "RedmiBook Air", ALC255_FIXUP_XIAOMI_HEADSET_MIC), + SND_PCI_QUIRK(0x1e39, 0xca14, "MEDION NM14LNL", ALC233_FIXUP_MEDION_MTL_SPK), + SND_PCI_QUIRK(0x1e50, 0x7007, "Positivo DN50E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x1e50, 0x7036, "Acer Gadget E10 ETBook", ALC233_FIXUP_WUJIE_SPEAKERS), + SND_PCI_QUIRK(0x1e50, 0x7038, "Positivo DN140", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1ee7, 0x2078, "HONOR BRB-X M1010", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1ee7, 0x2081, "HONOR MRB-XXX M1020", ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO), ++ SND_PCI_QUIRK(0x1f4c, 0xb020, "Minisforum AI X1 Pro", ++ ALC245_FIXUP_MINISFORUM_JACK_DETECT), + SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", ALC245_FIXUP_BASS_HP_DAC), + SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), ++ SND_PCI_QUIRK(0x2145, 0x0001, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), + SND_PCI_QUIRK(0x2782, 0x0214, "VAIO VJFE-CL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0x0228, "Infinix ZERO BOOK 13", ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13), + SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO), +@@ -8086,6 +8228,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x2782, 0x1705, "MEDION E15433", ALC269VC_FIXUP_INFINIX_Y4_MAX), + SND_PCI_QUIRK(0x2782, 0x1707, "Vaio VJFE-ADL", ALC298_FIXUP_SPK_VOLUME), + SND_PCI_QUIRK(0x2782, 0x4900, "MEDION E15443", ALC233_FIXUP_MEDION_MTL_SPK), ++ SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), + SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), + SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC), +@@ -8159,6 +8302,7 @@ static const struct hda_quirk alc269_fixup_vendor_tbl[] = { + SND_PCI_QUIRK_VENDOR(0x104d, "Sony VAIO", ALC269_FIXUP_SONY_VAIO), + SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo XPAD", ALC269_FIXUP_LENOVO_XPAD_ACPI), + SND_PCI_QUIRK_VENDOR(0x19e5, "Huawei Matebook", ALC255_FIXUP_MIC_MUTE_LED), ++ SND_PCI_QUIRK_VENDOR(0x2145, "Star Labs", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + {} + }; + +@@ -8298,6 +8442,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = { + {.id = ALC2XX_FIXUP_HEADSET_MIC, .name = "alc2xx-fixup-headset-mic"}, + {.id = ALC245_FIXUP_BASS_HP_DAC, .name = "alc245-fixup-bass-hp-dac"}, + {.id = ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, .name = "alc256-honor-mrb-xxx-m1020-audio"}, ++ {.id = ALC287_FIXUP_LENOVO_LEGION_AW88399, .name = "alc287-lenovo-legion-aw88399"}, + {} + }; + #define ALC225_STANDARD_PINS \ +@@ -8903,6 +9048,7 @@ static int alc269_probe(struct hda_codec *codec, const struct hda_device_id *id) + spec->init_hook = alc256_init; + spec->gen.mixer_nid = 0; /* ALC256 does not have any loopback mixer path */ + if (codec->core.vendor_id == 0x10ec0236 && ++ codec->bus->pci && + codec->bus->pci->vendor != PCI_VENDOR_ID_AMD) + spec->en_3kpull_low = false; + break; +diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c +--- a/sound/hda/codecs/realtek/alc662.c ++++ b/sound/hda/codecs/realtek/alc662.c +@@ -325,6 +325,7 @@ enum { + ALC897_FIXUP_UNIS_H3C_X500S, + ALC897_FIXUP_HEADSET_MIC_PIN3, + ALC662_FIXUP_CSL_GPIO, ++ ALC897_FIXUP_BEELINK_SER6_AMP, + }; + + static const struct hda_fixup alc662_fixups[] = { +@@ -782,6 +783,10 @@ static const struct hda_fixup alc662_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc662_fixup_csl_amp, + }, ++ [ALC897_FIXUP_BEELINK_SER6_AMP] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_gpio4, ++ }, + }; + + static const struct hda_quirk alc662_fixup_tbl[] = { +@@ -853,6 +858,7 @@ static const struct hda_quirk alc662_fixup_tbl[] = { + SND_PCI_QUIRK(0x1b35, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T), + SND_PCI_QUIRK(0x1c6c, 0x1239, "Compaq N14JP6-V2", ALC897_FIXUP_HP_HSMIC_VERB), + SND_PCI_QUIRK(0x1e63, 0x6d9a, "F+ FLAPTOP r", ALC897_FIXUP_HP_HSMIC_VERB), ++ SND_PCI_QUIRK(0x1f66, 0x0202, "Beelink SER6 Max 6900", ALC897_FIXUP_BEELINK_SER6_AMP), + + #if 0 + /* Below is a quirk table taken from the old code. +diff --git a/sound/hda/codecs/side-codecs/Kconfig b/sound/hda/codecs/side-codecs/Kconfig +--- a/sound/hda/codecs/side-codecs/Kconfig ++++ b/sound/hda/codecs/side-codecs/Kconfig +@@ -13,6 +13,24 @@ config SND_HDA_CIRRUS_SCODEC_KUNIT_TEST + Documentation/dev-tools/kunit/. + If in doubt, say "N". + ++config SND_HDA_SCODEC_AW88399 ++ tristate ++ select SND_HDA_GENERIC ++ ++config SND_HDA_SCODEC_AW88399_I2C ++ tristate "Build AW88399 HD-audio side codec support for I2C Bus" ++ depends on I2C ++ depends on ACPI ++ depends on SND_SOC ++ select SND_HDA_SCODEC_AW88399 ++ select SND_SOC_AW88399_LIB ++ help ++ Say Y or M here to include AW88399 I2C HD-audio side codec support ++ in snd-hda-intel driver, such as ALC287. ++ ++comment "Set to Y if you want auto-loading the side codec driver" ++ depends on SND_HDA=y && SND_HDA_SCODEC_AW88399_I2C=m ++ + config SND_HDA_SCODEC_CS35L41 + tristate + select SND_HDA_GENERIC +diff --git a/sound/hda/codecs/side-codecs/Makefile b/sound/hda/codecs/side-codecs/Makefile +--- a/sound/hda/codecs/side-codecs/Makefile ++++ b/sound/hda/codecs/side-codecs/Makefile +@@ -3,6 +3,8 @@ subdir-ccflags-y += -I$(src)/../../common + + snd-hda-cirrus-scodec-y := cirrus_scodec.o + snd-hda-cirrus-scodec-test-y := cirrus_scodec_test.o ++snd-hda-scodec-aw88399-y := aw88399_hda.o ++snd-hda-scodec-aw88399-i2c-y := aw88399_hda_i2c.o + snd-hda-scodec-cs35l41-y := cs35l41_hda.o cs35l41_hda_property.o + snd-hda-scodec-cs35l41-i2c-y := cs35l41_hda_i2c.o + snd-hda-scodec-cs35l41-spi-y := cs35l41_hda_spi.o +@@ -16,6 +18,8 @@ snd-hda-scodec-tas2781-spi-y := tas2781_hda_spi.o + + obj-$(CONFIG_SND_HDA_CIRRUS_SCODEC) += snd-hda-cirrus-scodec.o + obj-$(CONFIG_SND_HDA_CIRRUS_SCODEC_KUNIT_TEST) += snd-hda-cirrus-scodec-test.o ++obj-$(CONFIG_SND_HDA_SCODEC_AW88399) += snd-hda-scodec-aw88399.o ++obj-$(CONFIG_SND_HDA_SCODEC_AW88399_I2C) += snd-hda-scodec-aw88399-i2c.o + obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) += snd-hda-scodec-cs35l41.o + obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) += snd-hda-scodec-cs35l41-i2c.o + obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) += snd-hda-scodec-cs35l41-spi.o +diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/side-codecs/aw88399_hda.c +@@ -0,0 +1,373 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// AW88399 HDA side codec driver ++// ++// Based on cs35l41_hda.c and aw88399.c ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../generic.h" ++#include "hda_component.h" ++#include "aw88399_hda.h" ++ ++#define AW88399_HDA_I2C_BASE_ADDR 0x34 ++ ++static void aw88399_hda_playback_hook(struct device *dev, int action) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ struct aw88399 *core = aw88399->core; ++ int ret = 0; ++ ++ dev_dbg(aw88399->dev, "Playback action: %d\n", action); ++ ++ switch (action) { ++ case HDA_GEN_PCM_ACT_OPEN: ++ pm_runtime_get_sync(dev); ++ aw88399->playing = true; ++ break; ++ case HDA_GEN_PCM_ACT_PREPARE: ++ if (core) ++ aw88399_start(core, AW88399_SYNC_START); ++ break; ++ case HDA_GEN_PCM_ACT_CLEANUP: ++ if (aw88399->aw_dev) ++ ret = aw88399_stop(aw88399->aw_dev); ++ if (ret) ++ dev_err(aw88399->dev, "Failed to stop amplifier: %d\n", ret); ++ break; ++ case HDA_GEN_PCM_ACT_CLOSE: ++ if (aw88399->aw_dev) ++ aw88399_stop(aw88399->aw_dev); ++ aw88399->playing = false; ++ pm_runtime_mark_last_busy(dev); ++ pm_runtime_put_autosuspend(dev); ++ break; ++ default: ++ dev_warn(aw88399->dev, "Unsupported action: %d\n", action); ++ break; ++ } ++} ++ ++static int aw88399_hda_bind(struct device *dev, struct device *master, void *master_data) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ struct hda_component_parent *parent = master_data; ++ struct hda_component *comp; ++ ++ comp = hda_component_from_index(parent, aw88399->index); ++ if (!comp) ++ return -EINVAL; ++ ++ if (comp->dev) ++ return -EBUSY; ++ ++ comp->dev = dev; ++ ++ strscpy(comp->name, dev_name(dev), sizeof(comp->name)); ++ ++ comp->playback_hook = aw88399_hda_playback_hook; ++ ++ dev_info(aw88399->dev, ++ "AW88399 Bound - SSID: %s, channel: %d\n", ++ aw88399->acpi_subsystem_id, aw88399->channel); ++ ++ return 0; ++} ++ ++static void aw88399_hda_unbind(struct device *dev, struct device *master, void *master_data) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ struct hda_component_parent *parent = master_data; ++ struct hda_component *comp; ++ ++ comp = hda_component_from_index(parent, aw88399->index); ++ if (comp && (comp->dev == dev)) ++ memset(comp, 0, sizeof(*comp)); ++ ++ dev_dbg(aw88399->dev, "Unbound from HDA codec\n"); ++} ++ ++static const struct component_ops aw88399_hda_comp_ops = { ++ .bind = aw88399_hda_bind, ++ .unbind = aw88399_hda_unbind, ++}; ++ ++static int aw88399_hda_index_from_i2c(struct aw88399_hda *aw88399) ++{ ++ return to_i2c_client(aw88399->dev)->addr - AW88399_HDA_I2C_BASE_ADDR; ++} ++ ++static int aw88399_hda_init(struct aw88399_hda *aw88399) ++{ ++ struct device *dev = aw88399->dev; ++ struct i2c_client *i2c = to_i2c_client(dev); ++ struct aw88399 *core; ++ int ret; ++ ++ core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); ++ if (!core) ++ return -ENOMEM; ++ ++ mutex_init(&core->lock); ++ core->reset_gpio = aw88399->reset_gpio; ++ core->regmap = aw88399->regmap; ++ core->bsts_unreliable = aw88399->bsts_unreliable; ++ ++ aw88399_hw_reset(core); ++ ++ ret = aw88399_init(core, i2c, aw88399->regmap); ++ if (ret) ++ return ret; ++ ++ /* Set channel BEFORE loading firmware so ACF parser sees correct value */ ++ if (core->aw_pa) ++ aw88399_dev_set_channel(core, aw88399->channel); ++ ++ ret = aw88399_request_firmware_file(core); ++ if (ret) ++ return ret; ++ ++ aw88399->core = core; ++ aw88399->aw_dev = core->aw_pa; ++ ++ return 0; ++} ++ ++static int aw88399_swap_channels(struct aw88399_hda *aw88399) ++{ ++ /* ++ * Certain Lenovo Legion laptops have their ++ * I2C wiring reversed: 0x34 is physically the right speaker, ++ * 0x35 is the left. Swap channels to correct L/R assignment. ++ * This is a model-specific hardware wiring issue, not a driver bug. ++ */ ++ aw88399->channel = 1 - aw88399->channel; ++ dev_dbg(aw88399->dev, ++ "Channel swap applied: index %d -> channel %d\n", ++ aw88399->index, aw88399->channel); ++ return 0; ++} ++ ++static int aw88399_skip_bsts_check(struct aw88399_hda *aw88399) ++{ ++ /* ++ * BSTS (boost-finished) status bit does not reliably report on ++ * some hardware. On certain Lenovo Legion laptops, both amps ++ * report BSTS=0 (boost not finished) during normal playback ++ * despite clean audio output. Skip BSTS in the startup status ++ * check to avoid false init failures. ++ */ ++ aw88399->bsts_unreliable = true; ++ dev_dbg(aw88399->dev, "BSTS status check disabled\n"); ++ return 0; ++} ++ ++static int aw88399_apply_legion_quirks(struct aw88399_hda *aw88399) ++{ ++ aw88399_swap_channels(aw88399); ++ aw88399_skip_bsts_check(aw88399); ++ return 0; ++} ++ ++struct aw88399_prop_model { ++ const char *ssid; ++ int (*apply_prop)(struct aw88399_hda *aw88399); ++}; ++ ++static const struct aw88399_prop_model aw88399_prop_model_table[] = { ++ { "17AA3906", aw88399_apply_legion_quirks }, ++ { "17AA3907", aw88399_apply_legion_quirks }, ++ { "17AA3927", aw88399_apply_legion_quirks }, ++ { "17AA3928", aw88399_apply_legion_quirks }, ++ { "17AA3936", aw88399_apply_legion_quirks }, ++ { "17AA3937", aw88399_apply_legion_quirks }, ++ { "17AA3938", aw88399_apply_legion_quirks }, ++ { "17AA3939", aw88399_apply_legion_quirks }, ++ { } ++}; ++ ++static int aw88399_hda_acpi_probe(struct aw88399_hda *aw88399) ++{ ++ struct acpi_device *adev; ++ const char *sub; ++ const struct aw88399_prop_model *model; ++ ++ aw88399->index = aw88399_hda_index_from_i2c(aw88399); ++ aw88399->channel = aw88399->index; ++ aw88399->acpi_subsystem_id = NULL; ++ ++ adev = acpi_dev_get_first_match_dev("AWDZ8399", NULL, -1); ++ if (!adev) { ++ dev_err(aw88399->dev, "Failed to find an ACPI device for AWDZ8399\n"); ++ return -ENODEV; ++ } ++ ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); ++ acpi_dev_put(adev); ++ if (!physdev) ++ return -ENODEV; ++ ++ sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); ++ if (IS_ERR_OR_NULL(sub)) ++ return 0; ++ ++ aw88399->acpi_subsystem_id = devm_kstrdup(aw88399->dev, sub, GFP_KERNEL); ++ kfree(sub); ++ if (!aw88399->acpi_subsystem_id) ++ return -ENOMEM; ++ ++ for (model = aw88399_prop_model_table; model->ssid; model++) { ++ if (!strcasecmp(model->ssid, aw88399->acpi_subsystem_id)) { ++ dev_info(aw88399->dev, ++ "Applying properties for SSID %s\n", ++ aw88399->acpi_subsystem_id); ++ return model->apply_prop(aw88399); ++ } ++ } ++ ++ return 0; ++} ++ ++int aw88399_hda_probe(struct device *dev, struct regmap *regmap) ++{ ++ struct aw88399_hda *aw88399; ++ int ret; ++ ++ aw88399 = devm_kzalloc(dev, sizeof(*aw88399), GFP_KERNEL); ++ if (!aw88399) ++ return -ENOMEM; ++ ++ if (IS_ERR(regmap)) ++ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to obtain regmap\n"); ++ ++ aw88399->dev = dev; ++ aw88399->regmap = regmap; ++ dev_set_drvdata(dev, aw88399); ++ ++ aw88399->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); ++ if (IS_ERR(aw88399->reset_gpio)) ++ return dev_err_probe(dev, PTR_ERR(aw88399->reset_gpio), ++ "Failed to get reset GPIO\n"); ++ ++ ret = aw88399_hda_acpi_probe(aw88399); ++ if (ret) ++ return dev_err_probe(dev, ret, "ACPI probe failed\n"); ++ ++ ret = aw88399_hda_init(aw88399); ++ if (ret) ++ return dev_err_probe(dev, ret, "Chip initialization failed\n"); ++ ++ /* Enable runtime PM */ ++ pm_runtime_set_autosuspend_delay(dev, 3000); ++ pm_runtime_use_autosuspend(dev); ++ pm_runtime_mark_last_busy(dev); ++ pm_runtime_set_active(dev); ++ pm_runtime_enable(dev); ++ ++ ret = component_add(dev, &aw88399_hda_comp_ops); ++ if (ret) { ++ pm_runtime_disable(dev); ++ return dev_err_probe(dev, ret, "Failed to register component\n"); ++ } ++ ++ dev_info(dev, "AW88399 HDA side codec registered successfully\n"); ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(aw88399_hda_probe, "SND_HDA_SCODEC_AW88399"); ++ ++void aw88399_hda_remove(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ ++ pm_runtime_disable(dev); ++ ++ if (aw88399->aw_dev) ++ aw88399_stop(aw88399->aw_dev); ++ ++ component_del(dev, &aw88399_hda_comp_ops); ++ ++ dev_dbg(aw88399->dev, "AW88399 HDA side codec removed\n"); ++} ++EXPORT_SYMBOL_NS_GPL(aw88399_hda_remove, "SND_HDA_SCODEC_AW88399"); ++ ++static int aw88399_hda_runtime_suspend(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ ++ dev_dbg(aw88399->dev, "Runtime suspend\n"); ++ ++ if (aw88399->aw_dev && aw88399->playing) ++ aw88399_stop(aw88399->aw_dev); ++ ++ return 0; ++} ++ ++static int aw88399_hda_runtime_resume(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ ++ dev_dbg(aw88399->dev, "Runtime resume\n"); ++ ++ if (aw88399->core && aw88399->aw_dev && aw88399->playing) ++ aw88399_start(aw88399->core, AW88399_SYNC_START); ++ ++ return 0; ++} ++ ++static int aw88399_hda_system_suspend(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ int ret; ++ ++ dev_dbg(aw88399->dev, "System suspend\n"); ++ ++ if (aw88399->aw_dev && aw88399->playing) ++ aw88399_stop(aw88399->aw_dev); ++ ++ if (aw88399->core) ++ aw88399->core->fw_needs_reload = true; ++ ++ ret = pm_runtime_force_suspend(dev); ++ if (ret) ++ dev_err(aw88399->dev, "Runtime force suspend failed: %d\n", ret); ++ ++ return ret; ++} ++ ++static int aw88399_hda_system_resume(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ int ret; ++ ++ dev_dbg(aw88399->dev, "System resume\n"); ++ ++ if (aw88399->aw_dev) ++ aw88399_hw_reset(aw88399->core); ++ ++ ret = pm_runtime_force_resume(dev); ++ if (ret) ++ dev_err(aw88399->dev, "Runtime force resume failed: %d\n", ret); ++ ++ return ret; ++} ++ ++const struct dev_pm_ops aw88399_hda_pm_ops = { ++ RUNTIME_PM_OPS(aw88399_hda_runtime_suspend, aw88399_hda_runtime_resume, NULL) ++ SYSTEM_SLEEP_PM_OPS(aw88399_hda_system_suspend, aw88399_hda_system_resume) ++}; ++EXPORT_SYMBOL_NS_GPL(aw88399_hda_pm_ops, "SND_HDA_SCODEC_AW88399"); ++ ++MODULE_DESCRIPTION("AW88399 HDA driver"); ++MODULE_AUTHOR("Yakov Till "); ++MODULE_AUTHOR("Marco Giunta "); ++MODULE_LICENSE("GPL"); ++MODULE_FIRMWARE("aw88399_acf.bin"); +diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.h b/sound/hda/codecs/side-codecs/aw88399_hda.h +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/side-codecs/aw88399_hda.h +@@ -0,0 +1,36 @@ ++/* SPDX-License-Identifier: GPL-2.0-only ++ * ++ * AW88399 HDA side codec driver ++ */ ++ ++#ifndef __AW88399_HDA_H__ ++#define __AW88399_HDA_H__ ++ ++#include ++#include ++#include ++ ++struct aw88399; ++struct aw_device; ++ ++struct aw88399_hda { ++ struct device *dev; ++ struct regmap *regmap; ++ struct gpio_desc *reset_gpio; ++ struct aw_device *aw_dev; ++ struct aw88399 *core; ++ bool bsts_unreliable; ++ ++ const char *acpi_subsystem_id; ++ int index; ++ int channel; ++ ++ bool playing; ++}; ++ ++int aw88399_hda_probe(struct device *dev, struct regmap *regmap); ++void aw88399_hda_remove(struct device *dev); ++ ++extern const struct dev_pm_ops aw88399_hda_pm_ops; ++ ++#endif /* __AW88399_HDA_H__ */ +diff --git a/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c +@@ -0,0 +1,54 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// AW88399 HDA I2C driver ++// ++// Based on cs35l41_hda_i2c.c ++// ++ ++#include ++#include ++#include ++ ++#include "aw88399_hda.h" ++ ++static int aw88399_hda_i2c_probe(struct i2c_client *clt) ++{ ++ if (!strstr(dev_name(&clt->dev), "AWDZ8399")) ++ return -ENODEV; ++ ++ return aw88399_hda_probe(&clt->dev, ++ devm_regmap_init_i2c(clt, &aw88399_remap_config)); ++} ++ ++static void aw88399_hda_i2c_remove(struct i2c_client *clt) ++{ ++ aw88399_hda_remove(&clt->dev); ++} ++ ++static const struct i2c_device_id aw88399_hda_i2c_id[] = { ++ { .name = "aw88399-hda" }, ++ { } ++}; ++ ++static const struct acpi_device_id aw88399_acpi_hda_match[] = { ++ { "AWDZ8399", 0 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, aw88399_acpi_hda_match); ++ ++static struct i2c_driver aw88399_hda_i2c_driver = { ++ .driver = { ++ .name = "aw88399-hda", ++ .acpi_match_table = aw88399_acpi_hda_match, ++ .pm = &aw88399_hda_pm_ops, ++ }, ++ .probe = aw88399_hda_i2c_probe, ++ .remove = aw88399_hda_i2c_remove, ++ .id_table = aw88399_hda_i2c_id, ++}; ++module_i2c_driver(aw88399_hda_i2c_driver); ++ ++MODULE_DESCRIPTION("HDA AW88399 I2C driver"); ++MODULE_IMPORT_NS("SND_HDA_SCODEC_AW88399"); ++MODULE_AUTHOR("Yakov Till "); ++MODULE_LICENSE("GPL"); +diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda.c b/sound/hda/codecs/side-codecs/cs35l41_hda.c +--- a/sound/hda/codecs/side-codecs/cs35l41_hda.c ++++ b/sound/hda/codecs/side-codecs/cs35l41_hda.c +@@ -170,6 +170,7 @@ static int cs35l41_request_firmware_file(struct cs35l41_hda *cs35l41, + char *s, c; + int ret = 0; + ++ *firmware = NULL; + if (spkid > -1 && ssid && amp_name) + *filename = kasprintf(GFP_KERNEL, "cirrus/%s-%s-%s-%s-spkid%d-%s.%s", CS35L41_PART, + dsp_name, cs35l41_hda_fw_ids[cs35l41->firmware_type], +@@ -528,8 +529,8 @@ static int cs35l41_read_tuning_params(struct cs35l41_hda *cs35l41, const struct + + static int cs35l41_load_tuning_params(struct cs35l41_hda *cs35l41, char *tuning_filename) + { +- const struct firmware *tuning_param_file = NULL; +- char *tuning_param_filename = NULL; ++ const struct firmware *tuning_param_file __free(firmware) = NULL; ++ char *tuning_param_filename __free(kfree) = NULL; + int ret; + + ret = cs35l41_request_tuning_param_file(cs35l41, tuning_filename, &tuning_param_file, +@@ -548,19 +549,16 @@ static int cs35l41_load_tuning_params(struct cs35l41_hda *cs35l41, char *tuning_ + cs35l41_set_default_tuning_params(cs35l41); + } + +- release_firmware(tuning_param_file); +- kfree(tuning_param_filename); +- + return ret; + } + + static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41) + { +- const struct firmware *coeff_firmware = NULL; +- const struct firmware *wmfw_firmware = NULL; ++ const struct firmware *coeff_firmware __free(firmware) = NULL; ++ const struct firmware *wmfw_firmware __free(firmware) = NULL; + struct cs_dsp *dsp = &cs35l41->cs_dsp; +- char *coeff_filename = NULL; +- char *wmfw_filename = NULL; ++ char *coeff_filename __free(kfree) = NULL; ++ char *wmfw_filename __free(kfree) = NULL; + int ret; + + if (!cs35l41->halo_initialized) { +@@ -592,20 +590,13 @@ static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41) + + ret = cs_dsp_power_up(dsp, wmfw_firmware, wmfw_filename, coeff_firmware, coeff_filename, + cs35l41_hda_fw_ids[cs35l41->firmware_type]); +- if (ret) +- goto err; ++ if (ret) { ++ cs35l41_set_default_tuning_params(cs35l41); ++ return ret; ++ } + + cs35l41_hda_apply_calibration(cs35l41); +- +-err: +- if (ret) +- cs35l41_set_default_tuning_params(cs35l41); +- release_firmware(wmfw_firmware); +- release_firmware(coeff_firmware); +- kfree(wmfw_filename); +- kfree(coeff_filename); +- +- return ret; ++ return 0; + } + + static void cs35l41_shutdown_dsp(struct cs35l41_hda *cs35l41) +@@ -1924,7 +1915,6 @@ int cs35l41_hda_parse_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, + static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, int id) + { + struct acpi_device *adev; +- struct device *physdev; + struct spi_device *spi; + const char *sub; + int ret; +@@ -1936,7 +1926,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i + } + + cs35l41->dacpi = adev; +- physdev = get_device(acpi_get_first_physical_node(adev)); ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); + if (!physdev) { + acpi_dev_put(adev); + return -ENODEV; +@@ -1954,13 +1945,9 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i + } + + ret = cs35l41_hda_parse_acpi(cs35l41, physdev, id); +- if (ret) { +- put_device(physdev); ++ if (ret) + return ret; +- } + out: +- put_device(physdev); +- + cs35l41->bypass_fw = false; + if (cs35l41->control_bus == SPI) { + spi = to_spi_device(cs35l41->dev); +diff --git a/sound/hda/codecs/side-codecs/cs35l56_hda.c b/sound/hda/codecs/side-codecs/cs35l56_hda.c +--- a/sound/hda/codecs/side-codecs/cs35l56_hda.c ++++ b/sound/hda/codecs/side-codecs/cs35l56_hda.c +@@ -527,18 +527,6 @@ static void cs35l56_hda_request_firmware_files(struct cs35l56_hda *cs35l56, + base_name, NULL, NULL, "bin"); + } + +-static void cs35l56_hda_release_firmware_files(const struct firmware *wmfw_firmware, +- char *wmfw_filename, +- const struct firmware *coeff_firmware, +- char *coeff_filename) +-{ +- release_firmware(wmfw_firmware); +- kfree(wmfw_filename); +- +- release_firmware(coeff_firmware); +- kfree(coeff_filename); +-} +- + static int cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) + { + int ret; +@@ -561,10 +549,10 @@ static int cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) + + static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + { +- const struct firmware *coeff_firmware = NULL; +- const struct firmware *wmfw_firmware = NULL; +- char *coeff_filename = NULL; +- char *wmfw_filename = NULL; ++ const struct firmware *coeff_firmware __free(firmware) = NULL; ++ const struct firmware *wmfw_firmware __free(firmware) = NULL; ++ char *coeff_filename __free(kfree) = NULL; ++ char *wmfw_filename __free(kfree) = NULL; + unsigned int preloaded_fw_ver; + bool firmware_missing; + int ret; +@@ -606,14 +594,14 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + if (firmware_missing) { + if (!wmfw_firmware) { + dev_err(cs35l56->base.dev, ".%s file required but not found\n", "wmfw"); +- goto err_fw_release; ++ return; + } else if (!coeff_firmware) { + dev_err(cs35l56->base.dev, ".%s file required but not found\n", "bin"); +- goto err_fw_release; ++ return; + } + } + +- mutex_lock(&cs35l56->base.irq_lock); ++ guard(mutex)(&cs35l56->base.irq_lock); + + /* + * If the firmware hasn't been patched it must be shutdown before +@@ -624,14 +612,14 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + if (firmware_missing && (wmfw_firmware || coeff_firmware)) { + ret = cs35l56_firmware_shutdown(&cs35l56->base); + if (ret) +- goto err; ++ return; + } + + ret = cs_dsp_power_up(&cs35l56->cs_dsp, wmfw_firmware, wmfw_filename, + coeff_firmware, coeff_filename, "misc"); + if (ret) { + dev_dbg(cs35l56->base.dev, "%s: cs_dsp_power_up ret %d\n", __func__, ret); +- goto err; ++ return; + } + + if (wmfw_filename) +@@ -679,11 +667,6 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + err_powered_up: + if (!cs35l56->base.fw_patched) + cs_dsp_power_down(&cs35l56->cs_dsp); +-err: +- mutex_unlock(&cs35l56->base.irq_lock); +-err_fw_release: +- cs35l56_hda_release_firmware_files(wmfw_firmware, wmfw_filename, +- coeff_firmware, coeff_filename); + } + + static void cs35l56_hda_dsp_work(struct work_struct *work) +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +@@ -88,7 +88,6 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) + { + struct gpio_desc *speaker_id; + struct acpi_device *adev; +- struct device *physdev; + LIST_HEAD(resources); + const char *sub; + uint32_t subid; +@@ -101,7 +100,8 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) + return -ENODEV; + } + +- physdev = get_device(acpi_get_first_physical_node(adev)); ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); + ret = acpi_dev_get_resources(adev, &resources, tas2781_get_i2c_res, p); + if (ret < 0) { + dev_err(p->dev, "Failed to get ACPI resource.\n"); +@@ -151,14 +151,12 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) + end_2563: + acpi_dev_free_resource_list(&resources); + strscpy(p->dev_name, hid, sizeof(p->dev_name)); +- put_device(physdev); + acpi_dev_put(adev); + + return 0; + + err: + dev_err(p->dev, "read acpi error, ret: %d\n", ret); +- put_device(physdev); + acpi_dev_put(adev); + + return ret; +@@ -173,13 +171,13 @@ static void tas2781_hda_playback_hook(struct device *dev, int action) + case HDA_GEN_PCM_ACT_OPEN: + pm_runtime_get_sync(dev); + scoped_guard(mutex, &tas_hda->priv->codec_lock) { +- tasdevice_tuning_switch(tas_hda->priv, 0); ++ tasdevice_tuning_switch(tas_hda->priv, 0, false); + tas_hda->priv->playback_started = true; + } + break; + case HDA_GEN_PCM_ACT_CLOSE: + scoped_guard(mutex, &tas_hda->priv->codec_lock) { +- tasdevice_tuning_switch(tas_hda->priv, 1); ++ tasdevice_tuning_switch(tas_hda->priv, 1, false); + tas_hda->priv->playback_started = false; + } + +@@ -722,7 +720,7 @@ static int tas2781_runtime_suspend(struct device *dev) + * Stop the playback if it's unused. + */ + if (tas_hda->priv->playback_started) { +- tasdevice_tuning_switch(tas_hda->priv, 1); ++ tasdevice_tuning_switch(tas_hda->priv, 1, false); + tas_hda->priv->playback_started = false; + } + +@@ -752,7 +750,7 @@ static int tas2781_system_suspend(struct device *dev) + + /* Shutdown chip before system suspend */ + if (tas_hda->priv->playback_started) +- tasdevice_tuning_switch(tas_hda->priv, 1); ++ tasdevice_tuning_switch(tas_hda->priv, 1, false); + + /* + * Reset GPIO may be shared, so cannot reset here. +@@ -785,7 +783,7 @@ static int tas2781_system_resume(struct device *dev) + TASDEVICE_BIN_BLK_PRE_POWER_UP); + + if (tas_hda->priv->playback_started) +- tasdevice_tuning_switch(tas_hda->priv, 0); ++ tasdevice_tuning_switch(tas_hda->priv, 0, false); + + return 0; + } +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +@@ -328,7 +328,6 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + { + struct tasdevice_priv *p = tas_hda->priv; + struct acpi_device *adev; +- struct device *physdev; + u32 values[HDA_MAX_COMPONENTS]; + const char *property; + size_t nval; +@@ -341,7 +340,9 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + } + + strscpy(p->dev_name, hid, sizeof(p->dev_name)); +- physdev = get_device(acpi_get_first_physical_node(adev)); ++ ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); + acpi_dev_put(adev); + if (!physdev) + return -ENODEV; +@@ -381,13 +382,11 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + goto err; + } + } +- put_device(physdev); + + return 0; ++ + err: + dev_err(p->dev, "read acpi error, ret: %d\n", ret); +- put_device(physdev); +- + return ret; + } + +@@ -400,11 +399,11 @@ static void tas2781_hda_playback_hook(struct device *dev, int action) + pm_runtime_get_sync(dev); + guard(mutex)(&tas_priv->codec_lock); + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK) +- tasdevice_tuning_switch(tas_hda->priv, 0); ++ tasdevice_tuning_switch(tas_hda->priv, 0, false); + } else if (action == HDA_GEN_PCM_ACT_CLOSE) { + guard(mutex)(&tas_priv->codec_lock); + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK) +- tasdevice_tuning_switch(tas_priv, 1); ++ tasdevice_tuning_switch(tas_priv, 1, false); + pm_runtime_put_autosuspend(dev); + } + } +@@ -848,7 +847,7 @@ static int tas2781_runtime_suspend(struct device *dev) + + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK + && tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 1); ++ tasdevice_tuning_switch(tas_priv, 1, false); + + tas_priv->tasdevice[tas_priv->index].cur_book = -1; + tas_priv->tasdevice[tas_priv->index].cur_conf = -1; +@@ -865,7 +864,7 @@ static int tas2781_runtime_resume(struct device *dev) + + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK + && tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 0); ++ tasdevice_tuning_switch(tas_priv, 0, false); + + return 0; + } +@@ -883,7 +882,7 @@ static int tas2781_system_suspend(struct device *dev) + /* Shutdown chip before system suspend */ + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK + && tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 1); ++ tasdevice_tuning_switch(tas_priv, 1, false); + + return 0; + } +@@ -918,7 +917,7 @@ static int tas2781_system_resume(struct device *dev) + tas_priv->fw_state = TASDEVICE_DSP_FW_ALL_OK; + + if (tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 0); ++ tasdevice_tuning_switch(tas_priv, 0, false); + } + + return ret; +diff --git a/sound/hda/common/auto_parser.c b/sound/hda/common/auto_parser.c +--- a/sound/hda/common/auto_parser.c ++++ b/sound/hda/common/auto_parser.c +@@ -601,9 +601,9 @@ static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums) + return -1; + } + +-/* get a unique suffix or an index number */ ++/* get a unique suffix */ + static const char *check_output_sfx(hda_nid_t nid, const hda_nid_t *pins, +- int num_pins, int *indexp) ++ int num_pins) + { + static const char * const channel_sfx[] = { + " Front", " Surround", " CLFE", " Side" +@@ -615,11 +615,8 @@ static const char *check_output_sfx(hda_nid_t nid, const hda_nid_t *pins, + return NULL; + if (num_pins == 1) + return ""; +- if (num_pins > ARRAY_SIZE(channel_sfx)) { +- if (indexp) +- *indexp = i; ++ if (num_pins > ARRAY_SIZE(channel_sfx)) + return ""; +- } + return channel_sfx[i]; + } + +@@ -638,27 +635,9 @@ static const char *check_output_pfx(struct hda_codec *codec, hda_nid_t nid) + return ""; + } + +-static int get_hp_label_index(struct hda_codec *codec, hda_nid_t nid, +- const hda_nid_t *pins, int num_pins) +-{ +- int i, j, idx = 0; +- +- const char *pfx = check_output_pfx(codec, nid); +- +- i = find_idx_in_nid_list(nid, pins, num_pins); +- if (i < 0) +- return -1; +- for (j = 0; j < i; j++) +- if (pfx == check_output_pfx(codec, pins[j])) +- idx++; +- +- return idx; +-} +- + static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, +- const char *name, char *label, int maxlen, +- int *indexp) ++ const char *name, char *label, int maxlen) + { + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid); + int attr = snd_hda_get_input_pin_attr(def_conf); +@@ -671,19 +650,11 @@ static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + + if (cfg) { + /* try to give a unique suffix if needed */ +- sfx = check_output_sfx(nid, cfg->line_out_pins, cfg->line_outs, +- indexp); ++ sfx = check_output_sfx(nid, cfg->line_out_pins, cfg->line_outs); ++ if (!sfx) ++ sfx = check_output_sfx(nid, cfg->speaker_pins, cfg->speaker_outs); + if (!sfx) +- sfx = check_output_sfx(nid, cfg->speaker_pins, cfg->speaker_outs, +- indexp); +- if (!sfx) { +- /* don't add channel suffix for Headphone controls */ +- int idx = get_hp_label_index(codec, nid, cfg->hp_pins, +- cfg->hp_outs); +- if (idx >= 0 && indexp) +- *indexp = idx; + sfx = ""; +- } + } + snprintf(label, maxlen, "%s%s%s", pfx, name, sfx); + return 1; +@@ -699,7 +670,6 @@ static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + * @cfg: the parsed pin configuration + * @label: the string buffer to store + * @maxlen: the max length of string buffer (including termination) +- * @indexp: the pointer to return the index number (for multiple ctls) + * + * Get a label for the given pin. This function works for both input and + * output pins. When @cfg is given as non-NULL, the function tries to get +@@ -708,47 +678,33 @@ static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + * This function tries to give a unique label string for the pin as much as + * possible. For example, when the multiple line-outs are present, it adds + * the channel suffix like "Front", "Surround", etc (only when @cfg is given). +- * If no unique name with a suffix is available and @indexp is non-NULL, the +- * index number is stored in the pointer. + */ + int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, +- char *label, int maxlen, int *indexp) ++ char *label, int maxlen) + { + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid); + const char *name = NULL; + int i; + bool hdmi; + +- if (indexp) +- *indexp = 0; + if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE) + return 0; + + switch (get_defcfg_device(def_conf)) { + case AC_JACK_LINE_OUT: + return fill_audio_out_name(codec, nid, cfg, "Line Out", +- label, maxlen, indexp); ++ label, maxlen); + case AC_JACK_SPEAKER: + return fill_audio_out_name(codec, nid, cfg, "Speaker", +- label, maxlen, indexp); ++ label, maxlen); + case AC_JACK_HP_OUT: + return fill_audio_out_name(codec, nid, cfg, "Headphone", +- label, maxlen, indexp); ++ label, maxlen); + case AC_JACK_SPDIF_OUT: + case AC_JACK_DIG_OTHER_OUT: + hdmi = is_hdmi_cfg(def_conf); + name = hdmi ? "HDMI" : "SPDIF"; +- if (cfg && indexp) +- for (i = 0; i < cfg->dig_outs; i++) { +- hda_nid_t pin = cfg->dig_out_pins[i]; +- unsigned int c; +- if (pin == nid) +- break; +- c = snd_hda_codec_get_pincfg(codec, pin); +- if (hdmi == is_hdmi_cfg(c)) +- (*indexp)++; +- } + break; + default: + if (cfg) { +diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c +--- a/sound/hda/common/codec.c ++++ b/sound/hda/common/codec.c +@@ -39,6 +39,12 @@ static int call_exec_verb(struct hda_bus *bus, struct hda_codec *codec, + int err; + + CLASS(snd_hda_power_pm, pm)(codec); ++ if (pm.err < 0 && !bus->core.chip_init) { ++ codec_warn(codec, ++ "Failed to send cmd 0x%x ret=[%d], hda control stopped\n", ++ cmd, pm.err); ++ return pm.err; ++ } + guard(mutex)(&bus->core.cmd_mutex); + if (flags & HDA_RW_NO_RESPONSE_FALLBACK) + bus->no_response_fallback = 1; +@@ -2967,8 +2973,11 @@ static void hda_codec_pm_complete(struct device *dev) + dev->power.power_state = PMSG_RESUME; + + if (pm_runtime_suspended(dev) && (codec->jackpoll_interval || +- hda_codec_need_resume(codec) || codec->forced_resume)) ++ hda_codec_need_resume(codec) || codec->forced_resume || ++ codec->acomp_requested_resume)) { ++ codec->acomp_requested_resume = 0; + pm_request_resume(dev); ++ } + } + + static int hda_codec_pm_suspend(struct device *dev) +@@ -3370,7 +3379,7 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, + for (; knew->name; knew++) { + struct snd_kcontrol *kctl; + int addr = 0, idx = 0; +- if (knew->iface == (__force snd_ctl_elem_iface_t)-1) ++ if (knew->iface == -1) + continue; /* skip this codec private value */ + for (;;) { + kctl = snd_ctl_new1(knew, codec); +diff --git a/sound/hda/common/hda_auto_parser.h b/sound/hda/common/hda_auto_parser.h +--- a/sound/hda/common/hda_auto_parser.h ++++ b/sound/hda/common/hda_auto_parser.h +@@ -46,7 +46,7 @@ const char *hda_get_autocfg_input_label(struct hda_codec *codec, + int input); + int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, +- char *label, int maxlen, int *indexp); ++ char *label, int maxlen); + + enum { + INPUT_PIN_ATTR_UNUSED, /* pin not connected */ +diff --git a/sound/hda/common/hda_local.h b/sound/hda/common/hda_local.h +--- a/sound/hda/common/hda_local.h ++++ b/sound/hda/common/hda_local.h +@@ -723,4 +723,11 @@ void snd_hda_codec_display_power(struct hda_codec *codec, bool enable); + #define codec_dbg(codec, fmt, args...) \ + dev_dbg(hda_codec_dev(codec), fmt, ##args) + ++/* append a suffix string safely; equivalent with strlcat() */ ++static inline void hda_append_suffix(char *str, const char *suffix, size_t size) ++{ ++ size_t len = strnlen(str, size); ++ strscpy(str + len, suffix, size - len); ++} ++ + #endif /* __SOUND_HDA_LOCAL_H */ +diff --git a/sound/hda/common/jack.c b/sound/hda/common/jack.c +--- a/sound/hda/common/jack.c ++++ b/sound/hda/common/jack.c +@@ -612,10 +612,10 @@ static int add_jack_kctl(struct hda_codec *codec, hda_nid_t nid, + if (base_name) + strscpy(name, base_name, sizeof(name)); + else +- snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name), NULL); ++ snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name)); + if (phantom_jack) + /* Example final name: "Internal Mic Phantom Jack" */ +- strncat(name, " Phantom", sizeof(name) - strlen(name) - 1); ++ hda_append_suffix(name, " Phantom", sizeof(name)); + err = snd_hda_jack_add_kctl(codec, nid, name, phantom_jack, 0, NULL); + if (err < 0) + return err; +diff --git a/sound/hda/common/proc.c b/sound/hda/common/proc.c +--- a/sound/hda/common/proc.c ++++ b/sound/hda/common/proc.c +@@ -624,7 +624,7 @@ static void print_conn_list(struct snd_info_buffer *buffer, + /* Get Cache connections info */ + cache_len = snd_hda_get_conn_list(codec, nid, &list); + if (cache_len >= 0 && (cache_len != conn_len || +- memcmp(list, conn, conn_len) != 0)) { ++ memcmp(list, conn, conn_len * sizeof(*conn)) != 0)) { + snd_iprintf(buffer, " In-driver Connection: %d\n", cache_len); + if (cache_len > 0) { + snd_iprintf(buffer, " "); +diff --git a/sound/hda/controllers/Kconfig b/sound/hda/controllers/Kconfig +--- a/sound/hda/controllers/Kconfig ++++ b/sound/hda/controllers/Kconfig +@@ -33,6 +33,7 @@ config SND_HDA_TEGRA + config SND_HDA_CIX_IPBLOQ + tristate "CIX IPBLOQ HD Audio" + depends on ARCH_CIX || COMPILE_TEST ++ depends on ARCH_DMA_ADDR_T_64BIT + select SND_HDA + select SND_HDA_ALIGNED_MMIO + help +diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c +--- a/sound/hda/controllers/intel.c ++++ b/sound/hda/controllers/intel.c +@@ -100,6 +100,8 @@ enum { + #define ATIHDMI_NUM_CAPTURE 0 + #define ATIHDMI_NUM_PLAYBACK 8 + ++/* Hygon HD Audio controller */ ++#define PCI_DEVICE_ID_HYGON_18H_M05H_HDA 0x14a9 + + static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; + static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +@@ -241,6 +243,7 @@ enum { + AZX_DRIVER_ZHAOXIN, + AZX_DRIVER_ZHAOXINHDMI, + AZX_DRIVER_LOONGSON, ++ AZX_DRIVER_HYGON, + AZX_DRIVER_GENERIC, + AZX_NUM_DRIVERS, /* keep this as last entry */ + }; +@@ -360,6 +363,7 @@ static const char * const driver_short_names[] = { + [AZX_DRIVER_ZHAOXIN] = "HDA Zhaoxin", + [AZX_DRIVER_ZHAOXINHDMI] = "HDA Zhaoxin HDMI", + [AZX_DRIVER_LOONGSON] = "HDA Loongson", ++ [AZX_DRIVER_HYGON] = "HDA Hygon", + [AZX_DRIVER_GENERIC] = "HD-Audio Generic", + }; + +@@ -1926,6 +1930,10 @@ static int azx_first_init(struct azx *chip) + if (chip->driver_type == AZX_DRIVER_ZHAOXINHDMI) + bus->polling_mode = 1; + ++ if (chip->driver_type == AZX_DRIVER_HYGON && ++ chip->pci->device == PCI_DEVICE_ID_HYGON_18H_M05H_HDA) ++ bus->access_sdnctl_in_dword = 1; ++ + bus->remap_addr = pcim_iomap_region(pci, 0, "ICH HD audio"); + if (IS_ERR(bus->remap_addr)) + return PTR_ERR(bus->remap_addr); +@@ -2189,6 +2197,15 @@ static int azx_probe(struct pci_dev *pci, + dev_warn(&pci->dev, "dmic_detect option is deprecated, pass snd-intel-dspcfg.dsp_driver=1 option instead\n"); + } + ++ /* A sanity check against wild device binding; ++ * here the range 0x200 is enough for the registers used at probe, ++ * but it doesn't mean covering all HD-audio registers ++ */ ++ if (pci_resource_len(pci, 0) < 0x200) { ++ dev_err(&pci->dev, "Too small PCI BAR0\n"); ++ return -EINVAL; ++ } ++ + err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) { +@@ -2381,7 +2398,7 @@ static int azx_probe_continue(struct azx *chip) + + #ifdef CONFIG_SND_HDA_PATCH_LOADER + if (patch[dev] && *patch[dev]) { +- const struct firmware *fw = NULL; ++ const struct firmware *fw __free(firmware) = NULL; + + dev_info(&pci->dev, "Applying patch firmware '%s'\n", + patch[dev]); +@@ -2390,7 +2407,6 @@ static int azx_probe_continue(struct azx *chip) + "Cannot load firmware, continue without patching\n"); + } else { + err = snd_hda_load_patch(&chip->bus, fw->size, fw->data); +- release_firmware(fw); + if (err < 0) + goto out_free; + } +@@ -2838,6 +2854,11 @@ static const struct pci_device_id azx_ids[] = { + .driver_data = AZX_DRIVER_LOONGSON | AZX_DCAPS_NO_TCSEL }, + { PCI_VDEVICE(LOONGSON, PCI_DEVICE_ID_LOONGSON_HDMI), + .driver_data = AZX_DRIVER_LOONGSON | AZX_DCAPS_NO_TCSEL }, ++ /* Hygon HDAudio */ ++ { PCI_VDEVICE(HYGON, PCI_DEVICE_ID_HYGON_18H_M05H_HDA), ++ .driver_data = AZX_DRIVER_HYGON | AZX_DCAPS_POSFIX_LPIB | AZX_DCAPS_NO_MSI }, ++ /* Lisuan HD-audio */ ++ { PCI_DEVICE(0x4c54, 0x5010), .driver_data = AZX_DRIVER_GENERIC }, + { 0, } + }; + MODULE_DEVICE_TABLE(pci, azx_ids); +diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c +--- a/sound/hda/core/controller.c ++++ b/sound/hda/core/controller.c +@@ -511,7 +511,10 @@ void snd_hdac_bus_exit_link_reset(struct hdac_bus *bus) + { + unsigned long timeout; + +- snd_hdac_chip_updateb(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET); ++ else ++ snd_hdac_chip_updateb(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET); + + timeout = jiffies + msecs_to_jiffies(100); + while (!snd_hdac_chip_readb(bus, GCTL) && time_before(jiffies, timeout)) +@@ -576,7 +579,10 @@ static void azx_int_disable(struct hdac_bus *bus) + + /* disable interrupts in stream descriptor */ + list_for_each_entry(azx_dev, &bus->stream_list, list) +- snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, SD_INT_MASK, 0); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0); + + /* disable SIE for all streams & disable controller CIE and GIE */ + snd_hdac_chip_writel(bus, INTCTL, 0); +@@ -682,6 +688,11 @@ int snd_hdac_bus_handle_stream_irq(struct hdac_bus *bus, unsigned int status, + sd_status = snd_hdac_stream_readb(azx_dev, SD_STS); + snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); + handled |= 1 << azx_dev->index; ++ if (sd_status & (SD_INT_FIFO_ERR | SD_INT_DESC_ERR)) { ++ dev_warn_ratelimited(bus->dev, ++ "stream %u dma error: 0x%02x\n", ++ azx_dev->index, sd_status); ++ } + if ((!azx_dev->substream && !azx_dev->cstream) || + !azx_dev->running || !(sd_status & SD_INT_COMPLETE)) + continue; +@@ -713,7 +724,7 @@ int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus) + BDL_SIZE, &s->bdl); + num_streams++; + if (err < 0) +- return -ENOMEM; ++ goto error_bdl; + } + + if (WARN_ON(!num_streams)) +@@ -722,12 +733,24 @@ int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus) + err = snd_dma_alloc_pages(dma_type, bus->dev, + num_streams * 8, &bus->posbuf); + if (err < 0) +- return -ENOMEM; ++ goto error_bdl; + list_for_each_entry(s, &bus->stream_list, list) + s->posbuf = (__le32 *)(bus->posbuf.area + s->index * 8); + + /* single page (at least 4096 bytes) must suffice for both ringbuffes */ +- return snd_dma_alloc_pages(dma_type, bus->dev, PAGE_SIZE, &bus->rb); ++ err = snd_dma_alloc_pages(dma_type, bus->dev, PAGE_SIZE, &bus->rb); ++ if (err < 0) ++ goto error_posbuf; ++ return 0; ++ ++error_posbuf: ++ snd_dma_free_pages(&bus->posbuf); ++error_bdl: ++ list_for_each_entry(s, &bus->stream_list, list) { ++ if (s->bdl.area) ++ snd_dma_free_pages(&s->bdl); ++ } ++ return -ENOMEM; + } + EXPORT_SYMBOL_GPL(snd_hdac_bus_alloc_stream_pages); + +diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c +--- a/sound/hda/core/device.c ++++ b/sound/hda/core/device.c +@@ -663,6 +663,7 @@ static const struct hda_vendor_id hda_vendor_ids[] = { + { 0x1af4, "QEMU" }, + { 0x1fa8, "Senarytech" }, + { 0x434d, "C-Media" }, ++ { 0x4c54, "Lisuan" }, + { 0x8086, "Intel" }, + { 0x8384, "SigmaTel" }, + {} /* terminator */ +@@ -765,7 +766,7 @@ unsigned int snd_hdac_stream_format_bits(snd_pcm_format_t format, snd_pcm_subfor + + params_set_format(¶ms, snd_hdac_format_normalize(format)); + snd_mask_set(hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT), +- (__force unsigned int)subformat); ++ subformat); + + bits = snd_pcm_hw_params_bits(¶ms); + if (maxbits) +diff --git a/sound/hda/core/hda_bus_type.c b/sound/hda/core/hda_bus_type.c +--- a/sound/hda/core/hda_bus_type.c ++++ b/sound/hda/core/hda_bus_type.c +@@ -39,10 +39,7 @@ EXPORT_SYMBOL_GPL(hdac_get_device_id); + + static int hdac_codec_match(struct hdac_device *dev, const struct hdac_driver *drv) + { +- if (hdac_get_device_id(dev, drv)) +- return 1; +- else +- return 0; ++ return !!hdac_get_device_id(dev, drv); + } + + static int hda_bus_match(struct device *dev, const struct device_driver *drv) +@@ -59,9 +56,7 @@ static int hda_bus_match(struct device *dev, const struct device_driver *drv) + */ + if (hdrv->match) + return hdrv->match(hdev, hdrv); +- else +- return hdac_codec_match(hdev, hdrv); +- return 1; ++ return hdac_codec_match(hdev, hdrv); + } + + static int hda_uevent(const struct device *dev, struct kobj_uevent_env *env) +diff --git a/sound/hda/core/stream.c b/sound/hda/core/stream.c +--- a/sound/hda/core/stream.c ++++ b/sound/hda/core/stream.c +@@ -146,8 +146,12 @@ void snd_hdac_stream_start(struct hdac_stream *azx_dev) + stripe_ctl = snd_hdac_get_stream_stripe_ctl(bus, azx_dev->substream); + else + stripe_ctl = 0; +- snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, +- stripe_ctl); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, ++ stripe_ctl); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, ++ stripe_ctl); + } + /* set DMA start and interrupt mask */ + if (bus->access_sdnctl_in_dword) +@@ -166,11 +170,22 @@ EXPORT_SYMBOL_GPL(snd_hdac_stream_start); + */ + static void snd_hdac_stream_clear(struct hdac_stream *azx_dev) + { +- snd_hdac_stream_updateb(azx_dev, SD_CTL, +- SD_CTL_DMA_START | SD_INT_MASK, 0); +- snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ +- if (azx_dev->stripe) +- snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0); ++ struct hdac_bus *bus = azx_dev->bus; ++ ++ if (bus->access_sdnctl_in_dword) { ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, ++ SD_CTL_DMA_START | SD_INT_MASK, 0); ++ snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ ++ if (azx_dev->stripe) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0); ++ } else { ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, ++ SD_CTL_DMA_START | SD_INT_MASK, 0); ++ snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ ++ if (azx_dev->stripe) ++ snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0); ++ } ++ + azx_dev->running = false; + } + +@@ -225,12 +240,16 @@ void snd_hdac_stream_reset(struct hdac_stream *azx_dev) + { + unsigned char val; + int dma_run_state; ++ struct hdac_bus *bus = azx_dev->bus; + + snd_hdac_stream_clear(azx_dev); + + dma_run_state = snd_hdac_stream_readb(azx_dev, SD_CTL) & SD_CTL_DMA_START; + +- snd_hdac_stream_updateb(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); + + /* wait for hardware to report that the stream entered reset */ + snd_hdac_stream_readb_poll(azx_dev, SD_CTL, val, (val & SD_CTL_STREAM_RESET), 3, 300); +@@ -238,7 +257,10 @@ void snd_hdac_stream_reset(struct hdac_stream *azx_dev) + if (azx_dev->bus->dma_stop_delay && dma_run_state) + udelay(azx_dev->bus->dma_stop_delay); + +- snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0); + + /* wait for hardware to report that the stream is out of reset */ + snd_hdac_stream_readb_poll(azx_dev, SD_CTL, val, !(val & SD_CTL_STREAM_RESET), 3, 300); +diff --git a/sound/isa/msnd/msnd_pinnacle.c b/sound/isa/msnd/msnd_pinnacle.c +--- a/sound/isa/msnd/msnd_pinnacle.c ++++ b/sound/isa/msnd/msnd_pinnacle.c +@@ -367,7 +367,8 @@ static int snd_msnd_init_sma(struct snd_msnd *chip) + static int upload_dsp_code(struct snd_card *card) + { + struct snd_msnd *chip = card->private_data; +- const struct firmware *init_fw = NULL, *perm_fw = NULL; ++ const struct firmware *init_fw __free(firmware) = NULL; ++ const struct firmware *perm_fw __free(firmware) = NULL; + int err; + + outb(HPBLKSEL_0, chip->io + HP_BLKS); +@@ -375,28 +376,21 @@ static int upload_dsp_code(struct snd_card *card) + err = request_firmware(&init_fw, INITCODEFILE, card->dev); + if (err < 0) { + dev_err(card->dev, LOGNAME ": Error loading " INITCODEFILE); +- goto cleanup1; ++ return err; + } + err = request_firmware(&perm_fw, PERMCODEFILE, card->dev); + if (err < 0) { + dev_err(card->dev, LOGNAME ": Error loading " PERMCODEFILE); +- goto cleanup; ++ return err; + } + + memcpy_toio(chip->mappedbase, perm_fw->data, perm_fw->size); + if (snd_msnd_upload_host(chip, init_fw->data, init_fw->size) < 0) { + dev_warn(card->dev, LOGNAME ": Error uploading to DSP\n"); +- err = -ENODEV; +- goto cleanup; ++ return -ENODEV; + } + dev_info(card->dev, LOGNAME ": DSP firmware uploaded\n"); +- err = 0; +- +-cleanup: +- release_firmware(perm_fw); +-cleanup1: +- release_firmware(init_fw); +- return err; ++ return 0; + } + + #ifdef MSND_CLASSIC +diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c +--- a/sound/isa/sscape.c ++++ b/sound/isa/sscape.c +@@ -526,7 +526,7 @@ static int upload_dma_data(struct soundscape *s, const unsigned char *data, + static int sscape_upload_bootblock(struct snd_card *card) + { + struct soundscape *sscape = get_card_soundscape(card); +- const struct firmware *init_fw = NULL; ++ const struct firmware *init_fw __free(firmware) = NULL; + int data = 0; + int ret; + +@@ -537,8 +537,6 @@ static int sscape_upload_bootblock(struct snd_card *card) + } + ret = upload_dma_data(sscape, init_fw->data, init_fw->size); + +- release_firmware(init_fw); +- + guard(spinlock_irqsave)(&sscape->lock); + if (ret == 0) + data = host_read_ctrl_unsafe(sscape->io_base, 100); +@@ -562,7 +560,7 @@ static int sscape_upload_bootblock(struct snd_card *card) + static int sscape_upload_microcode(struct snd_card *card, int version) + { + struct soundscape *sscape = get_card_soundscape(card); +- const struct firmware *init_fw = NULL; ++ const struct firmware *init_fw __free(firmware) = NULL; + char name[14]; + int err; + +@@ -579,8 +577,6 @@ static int sscape_upload_microcode(struct snd_card *card, int version) + dev_info(card->dev, "sscape: MIDI firmware loaded %zu KBs\n", + init_fw->size >> 10); + +- release_firmware(init_fw); +- + return err; + } + +diff --git a/sound/isa/wavefront/wavefront_fx.c b/sound/isa/wavefront/wavefront_fx.c +--- a/sound/isa/wavefront/wavefront_fx.c ++++ b/sound/isa/wavefront/wavefront_fx.c +@@ -232,41 +232,32 @@ snd_wavefront_fx_start (snd_wavefront_t *dev) + { + unsigned int i; + int err; +- const struct firmware *firmware = NULL; ++ const struct firmware *firmware __free(firmware) = NULL; + + if (dev->fx_initialized) + return 0; + + err = request_firmware(&firmware, "yamaha/yss225_registers.bin", + dev->card->dev); +- if (err < 0) { +- err = -1; +- goto out; +- } ++ if (err < 0) ++ return -1; + + for (i = 0; i + 1 < firmware->size; i += 2) { + if (firmware->data[i] >= 8 && firmware->data[i] < 16) { + outb(firmware->data[i + 1], + dev->base + firmware->data[i]); + } else if (firmware->data[i] == WAIT_IDLE) { +- if (!wavefront_fx_idle(dev)) { +- err = -1; +- goto out; +- } ++ if (!wavefront_fx_idle(dev)) ++ return -1; + } else { + dev_err(dev->card->dev, + "invalid address in register data\n"); +- err = -1; +- goto out; ++ return -1; + } + } + + dev->fx_initialized = 1; +- err = 0; +- +-out: +- release_firmware(firmware); +- return err; ++ return 0; + } + + MODULE_FIRMWARE("yamaha/yss225_registers.bin"); +diff --git a/sound/isa/wavefront/wavefront_synth.c b/sound/isa/wavefront/wavefront_synth.c +--- a/sound/isa/wavefront/wavefront_synth.c ++++ b/sound/isa/wavefront/wavefront_synth.c +@@ -2053,7 +2053,7 @@ wavefront_download_firmware (snd_wavefront_t *dev, char *path) + const unsigned char *buf; + int len, err; + int section_cnt_downloaded = 0; +- const struct firmware *firmware; ++ const struct firmware *firmware __free(firmware) = NULL; + + err = request_firmware(&firmware, path, dev->card->dev); + if (err < 0) { +@@ -2108,11 +2108,9 @@ wavefront_download_firmware (snd_wavefront_t *dev, char *path) + section_cnt_downloaded++; + } + +- release_firmware(firmware); + return 0; + + failure: +- release_firmware(firmware); + dev_err(dev->card->dev, "firmware download failed!!!\n"); + return 1; + } +diff --git a/sound/pci/asihpi/asihpi.c b/sound/pci/asihpi/asihpi.c +--- a/sound/pci/asihpi/asihpi.c ++++ b/sound/pci/asihpi/asihpi.c +@@ -280,7 +280,7 @@ static void print_hwparams(struct snd_pcm_substream *substream, + snd_pcm_format_width(params_format(p)) / 8); + } + +-#define INVALID_FORMAT (__force snd_pcm_format_t)(-1) ++#define INVALID_FORMAT -1 + + static const snd_pcm_format_t hpi_to_alsa_formats[] = { + INVALID_FORMAT, /* INVALID */ +diff --git a/sound/pci/asihpi/hpi6000.c b/sound/pci/asihpi/hpi6000.c +--- a/sound/pci/asihpi/hpi6000.c ++++ b/sound/pci/asihpi/hpi6000.c +@@ -537,6 +537,11 @@ static short create_adapter_obj(struct hpi_adapter_obj *pao, + hr1.size = sizeof(hr1); + + error = hpi6000_message_response_sequence(pao, 0, &hm, &hr0); ++ if (error) { ++ HPI_DEBUG_LOG(ERROR, "message transport error %d\n", ++ error); ++ return error; ++ } + if (hr0.error) { + HPI_DEBUG_LOG(DEBUG, "message error %d\n", hr0.error); + return hr0.error; +diff --git a/sound/pci/asihpi/hpidspcd.c b/sound/pci/asihpi/hpidspcd.c +--- a/sound/pci/asihpi/hpidspcd.c ++++ b/sound/pci/asihpi/hpidspcd.c +@@ -23,7 +23,7 @@ struct dsp_code_private { + short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + u32 *os_error_code) + { +- const struct firmware *firmware; ++ const struct firmware *firmware __free(firmware) = NULL; + struct pci_dev *dev = os_data; + struct code_header header; + char fw_name[20]; +@@ -37,11 +37,11 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + if (err || !firmware) { + dev_err(&dev->dev, "%d, request_firmware failed for %s\n", + err, fw_name); +- goto error1; ++ goto error; + } + if (firmware->size < sizeof(header)) { + dev_err(&dev->dev, "Header size too small %s\n", fw_name); +- goto error2; ++ goto error; + } + memcpy(&header, firmware->data, sizeof(header)); + +@@ -51,7 +51,7 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + dev_err(&dev->dev, + "Invalid firmware header size %d != file %zd\n", + header.size, firmware->size); +- goto error2; ++ goto error; + } + + if (HPI_VER_MAJOR(header.version) != HPI_VER_MAJOR(HPI_VER)) { +@@ -59,7 +59,7 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + dev_err(&dev->dev, + "Incompatible firmware version DSP image %X != Driver %X\n", + header.version, HPI_VER); +- goto error2; ++ goto error; + } + + if (header.version != HPI_VER) { +@@ -72,19 +72,17 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + dsp_code->pvt = kmalloc_obj(*dsp_code->pvt); + if (!dsp_code->pvt) { + err_ret = HPI_ERROR_MEMORY_ALLOC; +- goto error2; ++ goto error; + } + + dsp_code->pvt->dev = dev; +- dsp_code->pvt->firmware = firmware; ++ dsp_code->pvt->firmware = no_free_ptr(firmware); + dsp_code->header = header; + dsp_code->block_length = header.size / sizeof(u32); + dsp_code->word_count = sizeof(header) / sizeof(u32); + return 0; + +-error2: +- release_firmware(firmware); +-error1: ++error: + dsp_code->block_length = 0; + return err_ret; + } +diff --git a/sound/pci/asihpi/hpioctl.c b/sound/pci/asihpi/hpioctl.c +--- a/sound/pci/asihpi/hpioctl.c ++++ b/sound/pci/asihpi/hpioctl.c +@@ -385,8 +385,7 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, + if (pci_resource_flags(pci_dev, idx) & IORESOURCE_MEM) { + memlen = pci_resource_len(pci_dev, idx); + pci.ap_mem_base[idx] = +- ioremap(pci_resource_start(pci_dev, idx), +- memlen); ++ pcim_iomap(pci_dev, idx, memlen); + if (!pci.ap_mem_base[idx]) { + HPI_DEBUG_LOG(ERROR, + "ioremap failed, aborting\n"); +@@ -509,13 +508,6 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, + return 0; + + err: +- while (--idx >= 0) { +- if (pci.ap_mem_base[idx]) { +- iounmap(pci.ap_mem_base[idx]); +- pci.ap_mem_base[idx] = NULL; +- } +- } +- + if (adapter.p_buffer) { + adapter.buffer_size = 0; + vfree(adapter.p_buffer); +@@ -527,14 +519,11 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, + + void asihpi_adapter_remove(struct pci_dev *pci_dev) + { +- int idx; + struct hpi_message hm; + struct hpi_response hr; + struct hpi_adapter *pa; +- struct hpi_pci pci; + + pa = pci_get_drvdata(pci_dev); +- pci = pa->adapter->pci; + + /* Disable IRQ generation on DSP side */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, +@@ -550,10 +539,6 @@ void asihpi_adapter_remove(struct pci_dev *pci_dev) + hm.adapter_index = pa->adapter->index; + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); + +- /* unmap PCI memory space, mapped during device init. */ +- for (idx = 0; idx < HPI_MAX_ADAPTER_MEM_SPACES; ++idx) +- iounmap(pci.ap_mem_base[idx]); +- + if (pa->irq) + free_irq(pa->irq, pa); + +diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c +--- a/sound/pci/cs46xx/cs46xx_lib.c ++++ b/sound/pci/cs46xx/cs46xx_lib.c +@@ -387,7 +387,7 @@ static int load_firmware(struct snd_cs46xx *chip, + unsigned int nums, fwlen, fwsize; + const __le32 *fwdat; + struct dsp_module_desc *module = NULL; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + char fw_path[32]; + + sprintf(fw_path, "cs46xx/%s", fw_name); +@@ -395,10 +395,8 @@ static int load_firmware(struct snd_cs46xx *chip, + if (err < 0) + return err; + fwsize = fw->size / 4; +- if (fwsize < 2) { +- err = -EINVAL; +- goto error; +- } ++ if (fwsize < 2) ++ return -EINVAL; + + err = -ENOMEM; + module = kzalloc_obj(*module); +@@ -454,14 +452,12 @@ static int load_firmware(struct snd_cs46xx *chip, + } + + *module_ret = module; +- release_firmware(fw); + return 0; + + error_inval: + err = -EINVAL; + error: + free_module_desc(module); +- release_firmware(fw); + return err; + } + +@@ -500,22 +496,18 @@ MODULE_FIRMWARE("cs46xx/ba1"); + + static int load_firmware(struct snd_cs46xx *chip) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int i, size, err; + + err = request_firmware(&fw, "cs46xx/ba1", &chip->pci->dev); + if (err < 0) + return err; +- if (fw->size != sizeof(*chip->ba1)) { +- err = -EINVAL; +- goto error; +- } ++ if (fw->size != sizeof(*chip->ba1)) ++ return -EINVAL; + + chip->ba1 = vmalloc(sizeof(*chip->ba1)); +- if (!chip->ba1) { +- err = -ENOMEM; +- goto error; +- } ++ if (!chip->ba1) ++ return -ENOMEM; + + memcpy_le32(chip->ba1, fw->data, sizeof(*chip->ba1)); + +@@ -524,11 +516,9 @@ static int load_firmware(struct snd_cs46xx *chip) + for (i = 0; i < BA1_MEMORY_COUNT; i++) + size += chip->ba1->memory[i].size; + if (size > BA1_DWORD_SIZE * 4) +- err = -EINVAL; ++ return -EINVAL; + +- error: +- release_firmware(fw); +- return err; ++ return 0; + } + + static __maybe_unused int snd_cs46xx_download_image(struct snd_cs46xx *chip) +diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c +--- a/sound/pci/emu10k1/emufx.c ++++ b/sound/pci/emu10k1/emufx.c +@@ -990,7 +990,7 @@ static int snd_emu10k1_list_controls(struct snd_emu10k1 *emu, + i < icode->gpr_list_control_count) { + memset(gctl, 0, sizeof(*gctl)); + id = &ctl->kcontrol->id; +- gctl->id.iface = (__force int)id->iface; ++ gctl->id.iface = id->iface; + strscpy(gctl->id.name, id->name, sizeof(gctl->id.name)); + gctl->id.index = id->index; + gctl->id.device = id->device; +@@ -1156,7 +1156,7 @@ static void + snd_emu10k1_init_mono_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 1; + if (high_res_gpr_volume) { +@@ -1180,7 +1180,7 @@ static void + snd_emu10k1_init_stereo_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 2; + if (high_res_gpr_volume) { +@@ -1205,7 +1205,7 @@ static void + snd_emu10k1_init_mono_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 1; + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; +@@ -1218,7 +1218,7 @@ static void + snd_emu10k1_init_stereo_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 2; + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; +@@ -1542,7 +1542,7 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) + * Process tone control + */ + ctl = &controls[nctl + 0]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Bass"); + ctl->vcount = 2; + ctl->count = 10; +@@ -1551,7 +1551,7 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) + ctl->value[0] = ctl->value[1] = 20; + ctl->translation = EMU10K1_GPR_TRANSLATION_BASS; + ctl = &controls[nctl + 1]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Treble"); + ctl->vcount = 2; + ctl->count = 10; +@@ -2137,7 +2137,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) + * Process tone control + */ + ctl = &controls[i + 0]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Bass"); + ctl->vcount = 2; + ctl->count = 10; +@@ -2147,7 +2147,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) + ctl->tlv = snd_emu10k1_bass_treble_db_scale; + ctl->translation = EMU10K1_GPR_TRANSLATION_BASS; + ctl = &controls[i + 1]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Treble"); + ctl->vcount = 2; + ctl->count = 10; +diff --git a/sound/pci/korg1212/korg1212.c b/sound/pci/korg1212/korg1212.c +--- a/sound/pci/korg1212/korg1212.c ++++ b/sound/pci/korg1212/korg1212.c +@@ -1980,7 +1980,7 @@ static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + __maybe_unused unsigned ioport_size; + __maybe_unused unsigned iomem2_size; + struct snd_korg1212 *korg1212 = card->private_data; +- const struct firmware *dsp_code; ++ const struct firmware *dsp_code __free(firmware) = NULL; + + err = pcim_enable_device(pci); + if (err < 0) +@@ -2147,10 +2147,8 @@ static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + + korg1212->dma_dsp = snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, + dsp_code->size); +- if (!korg1212->dma_dsp) { +- release_firmware(dsp_code); ++ if (!korg1212->dma_dsp) + return -ENOMEM; +- } + + K1212_DEBUG_PRINTK("K1212_DEBUG: DSP Code area = 0x%p (0x%08x) %d bytes [%s]\n", + korg1212->dma_dsp->area, korg1212->dma_dsp->addr, dsp_code->size, +@@ -2158,8 +2156,6 @@ static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + + memcpy(korg1212->dma_dsp->area, dsp_code->data, dsp_code->size); + +- release_firmware(dsp_code); +- + rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_RebootCard, 0, 0, 0, 0); + + if (rc) +diff --git a/sound/pci/mixart/mixart_hwdep.c b/sound/pci/mixart/mixart_hwdep.c +--- a/sound/pci/mixart/mixart_hwdep.c ++++ b/sound/pci/mixart/mixart_hwdep.c +@@ -560,12 +560,11 @@ int snd_mixart_setup_firmware(struct mixart_mgr *mgr) + "miXart8.xlx", "miXart8.elf", "miXart8AES.xlx" + }; + char path[32]; +- +- const struct firmware *fw_entry; + int i, err; + + for (i = 0; i < 3; i++) { + sprintf(path, "mixart/%s", fw_files[i]); ++ const struct firmware *fw_entry __free(firmware) = NULL; + if (request_firmware(&fw_entry, path, &mgr->pci->dev)) { + dev_err(&mgr->pci->dev, + "miXart: can't load firmware %s\n", path); +@@ -573,7 +572,6 @@ int snd_mixart_setup_firmware(struct mixart_mgr *mgr) + } + /* fake hwdep dsp record */ + err = mixart_dsp_load(mgr, i, fw_entry); +- release_firmware(fw_entry); + if (err < 0) + return err; + mgr->dsp_loaded |= 1 << i; +diff --git a/sound/pci/pcxhr/pcxhr_hwdep.c b/sound/pci/pcxhr/pcxhr_hwdep.c +--- a/sound/pci/pcxhr/pcxhr_hwdep.c ++++ b/sound/pci/pcxhr/pcxhr_hwdep.c +@@ -367,7 +367,6 @@ int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) + }; + char path[32]; + +- const struct firmware *fw_entry; + int i, err; + int fw_set = mgr->fw_file_set; + +@@ -375,6 +374,7 @@ int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) + if (!fw_files[fw_set][i]) + continue; + sprintf(path, "pcxhr/%s", fw_files[fw_set][i]); ++ const struct firmware *fw_entry __free(firmware) = NULL; + if (request_firmware(&fw_entry, path, &mgr->pci->dev)) { + dev_err(&mgr->pci->dev, + "pcxhr: can't load firmware %s\n", +@@ -383,7 +383,6 @@ int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) + } + /* fake hwdep dsp record */ + err = pcxhr_dsp_load(mgr, i, fw_entry); +- release_firmware(fw_entry); + if (err < 0) + return err; + mgr->dsp_loaded |= 1 << i; +diff --git a/sound/pci/sis7019.c b/sound/pci/sis7019.c +--- a/sound/pci/sis7019.c ++++ b/sound/pci/sis7019.c +@@ -1260,16 +1260,16 @@ static int sis_chip_create(struct snd_card *card, + sis->irq = -1; + sis->ioport = pci_resource_start(pci, 0); + +- rc = pcim_request_all_regions(pci, "SiS7019"); ++ rc = pcim_request_region(pci, 0, "SiS7019"); + if (rc) { +- dev_err(&pci->dev, "unable request regions\n"); ++ dev_err(&pci->dev, "unable request I/O region\n"); + return rc; + } + +- sis->ioaddr = devm_ioremap(&pci->dev, pci_resource_start(pci, 1), 0x4000); +- if (!sis->ioaddr) { ++ sis->ioaddr = pcim_iomap_region(pci, 1, "SiS7019"); ++ if (IS_ERR(sis->ioaddr)) { + dev_err(&pci->dev, "unable to remap MMIO, aborting\n"); +- return -EIO; ++ return PTR_ERR(sis->ioaddr); + } + + rc = sis_alloc_suspend(sis); +diff --git a/sound/pci/via82xx_modem.c b/sound/pci/via82xx_modem.c +--- a/sound/pci/via82xx_modem.c ++++ b/sound/pci/via82xx_modem.c +@@ -573,24 +573,18 @@ static inline unsigned int calc_linear_pos(struct via82xx_modem *chip, + viadev->bufsize2, viadev->idx_table[idx].offset, + viadev->idx_table[idx].size, count); + #endif +- if (count && size < count) { ++ if (! count) ++ /* bogus count 0 on the DMA boundary? */ ++ res = viadev->idx_table[idx].offset; ++ else ++ /* count register returns full size ++ * when end of buffer is reached ++ */ ++ res = viadev->idx_table[idx].offset + size; ++ if (check_invalid_pos(viadev, res)) { + dev_dbg(chip->card->dev, +- "invalid via82xx_cur_ptr, using last valid pointer\n"); ++ "invalid via82xx_cur_ptr (2), using last valid pointer\n"); + res = viadev->lastpos; +- } else { +- if (! count) +- /* bogus count 0 on the DMA boundary? */ +- res = viadev->idx_table[idx].offset; +- else +- /* count register returns full size +- * when end of buffer is reached +- */ +- res = viadev->idx_table[idx].offset + size; +- if (check_invalid_pos(viadev, res)) { +- dev_dbg(chip->card->dev, +- "invalid via82xx_cur_ptr (2), using last valid pointer\n"); +- res = viadev->lastpos; +- } + } + } + viadev->lastpos = res; /* remember the last position */ +diff --git a/sound/sh/aica.c b/sound/sh/aica.c +--- a/sound/sh/aica.c ++++ b/sound/sh/aica.c +@@ -518,8 +518,9 @@ static const struct snd_kcontrol_new snd_aica_pcmvolume_control = { + static int load_aica_firmware(void) + { + int err; +- const struct firmware *fw_entry; + spu_reset(); ++ ++ const struct firmware *fw_entry __free(firmware) = NULL; + err = request_firmware(&fw_entry, "aica_firmware.bin", &pd->dev); + if (unlikely(err)) + return err; +@@ -527,7 +528,6 @@ static int load_aica_firmware(void) + spu_disable(); + spu_memload(0, fw_entry->data, fw_entry->size); + spu_enable(); +- release_firmware(fw_entry); + return err; + } + +diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c +--- a/sound/soc/amd/acp-config.c ++++ b/sound/soc/amd/acp-config.c +@@ -63,6 +63,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook 18 M1807GA"), + }, + }, ++ { ++ /* HP OmniBook X Flip 14-kc0xxx (Strix Point, ACP 7.2) */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "HP"), ++ DMI_MATCH(DMI_BOARD_NAME, "8EA1"), ++ }, ++ }, + {} + }; + +@@ -379,5 +386,17 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[] = { + }; + EXPORT_SYMBOL(snd_soc_acpi_amd_acp70_sof_machines); + ++struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[] = { ++ { ++ .id = "AMDI1010", ++ .drv_name = "acp7x-dsp", ++ .pdata = &acp_quirk_data, ++ .fw_filename = "sof-acp7x.ri", ++ .sof_tplg_filename = "sof-acp7x.tplg", ++ }, ++ {}, ++}; ++EXPORT_SYMBOL(snd_soc_acpi_amd_acp7x_sof_machines); ++ + MODULE_DESCRIPTION("AMD ACP Machine Configuration Module"); + MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c +--- a/sound/soc/amd/acp-pcm-dma.c ++++ b/sound/soc/amd/acp-pcm-dma.c +@@ -691,12 +691,10 @@ static irqreturn_t dma_irq_handler(int irq, void *arg) + { + u16 dscr_idx; + u32 intr_flag, ext_intr_status; +- struct audio_drv_data *irq_data; ++ struct audio_drv_data *irq_data = arg; + void __iomem *acp_mmio; +- struct device *dev = arg; + bool valid_irq = false; + +- irq_data = dev_get_drvdata(dev); + acp_mmio = irq_data->acp_mmio; + + ext_intr_status = acp_reg_read(acp_mmio, mmACP_EXTERNAL_INTR_STAT); +@@ -1291,10 +1289,10 @@ static int acp_audio_probe(struct platform_device *pdev) + + irq = platform_get_irq(pdev, 0); + if (irq < 0) +- return -ENODEV; ++ return irq; + + status = devm_request_irq(&pdev->dev, irq, dma_irq_handler, +- 0, "ACP_IRQ", &pdev->dev); ++ 0, "ACP_IRQ", audio_drv_data); + if (status) { + dev_err(&pdev->dev, "ACP IRQ request failed\n"); + return status; +diff --git a/sound/soc/amd/acp/acp-i2s.c b/sound/soc/amd/acp/acp-i2s.c +--- a/sound/soc/amd/acp/acp-i2s.c ++++ b/sound/soc/amd/acp/acp-i2s.c +@@ -686,6 +686,10 @@ static int acp_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_d + return 0; + } + ++static const u64 acp_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops = { + .startup = acp_i2s_startup, + .hw_params = acp_i2s_hwparams, +@@ -693,6 +697,8 @@ const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops = { + .trigger = acp_i2s_trigger, + .set_fmt = acp_i2s_set_fmt, + .set_tdm_slot = acp_i2s_set_tdm_slot, ++ .auto_selectable_formats = &acp_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + EXPORT_SYMBOL_NS_GPL(asoc_acp_cpu_dai_ops, "SND_SOC_ACP_COMMON"); + +diff --git a/sound/soc/amd/acp/acp-mach-common.c b/sound/soc/amd/acp/acp-mach-common.c +--- a/sound/soc/amd/acp/acp-mach-common.c ++++ b/sound/soc/amd/acp/acp-mach-common.c +@@ -938,15 +938,10 @@ static int acp_max98388_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai = + snd_soc_card_get_codec_dai(card, + MAX98388_CODEC_DAI); +- int ret; + +- ret = snd_soc_dai_set_fmt(codec_dai, +- SND_SOC_DAIFMT_CBC_CFC | SND_SOC_DAIFMT_I2S | +- SND_SOC_DAIFMT_NB_NF); +- if (ret < 0) +- return ret; +- +- return ret; ++ return snd_soc_dai_set_fmt(codec_dai, ++ SND_SOC_DAIFMT_CBC_CFC | SND_SOC_DAIFMT_I2S | ++ SND_SOC_DAIFMT_NB_NF); + } + + static const struct snd_soc_ops acp_max98388_ops = { +diff --git a/sound/soc/amd/acp/acp-pci.c b/sound/soc/amd/acp/acp-pci.c +--- a/sound/soc/amd/acp/acp-pci.c ++++ b/sound/soc/amd/acp/acp-pci.c +@@ -118,17 +118,10 @@ static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id + if (!chip) + return -ENOMEM; + +- if (pci_enable_device(pci)) ++ if (pcim_enable_device(pci)) + return dev_err_probe(&pci->dev, -ENODEV, + "pci_enable_device failed\n"); + +- ret = pci_request_regions(pci, "AMD ACP3x audio"); +- if (ret < 0) { +- dev_err(&pci->dev, "pci_request_regions failed\n"); +- ret = -ENOMEM; +- goto disable_pci; +- } +- + pci_set_master(pci); + + chip->acp_rev = pci->revision; +@@ -161,24 +154,21 @@ static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id + break; + default: + dev_err(dev, "Unsupported device revision:0x%x\n", pci->revision); +- ret = -EINVAL; +- goto release_regions; ++ return -EINVAL; + } + chip->flag = flag; + + addr = pci_resource_start(pci, 0); +- chip->base = devm_ioremap(&pci->dev, addr, pci_resource_len(pci, 0)); +- if (!chip->base) { +- ret = -ENOMEM; +- goto release_regions; +- } ++ chip->base = pcim_iomap_region(pci, 0, "AMD ACP3x audio"); ++ if (IS_ERR(chip->base)) ++ return PTR_ERR(chip->base); + + chip->addr = addr; + + chip->acp_hw_ops_init(chip); + ret = acp_hw_init(chip); + if (ret) +- goto release_regions; ++ goto de_init; + + ret = devm_request_irq(dev, pci->irq, irq_handler, + IRQF_SHARED, "ACP_I2S_IRQ", chip); +@@ -214,10 +204,6 @@ static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id + + de_init: + acp_hw_deinit(chip); +-release_regions: +- pci_release_regions(pci); +-disable_pci: +- pci_disable_device(pci); + + return ret; + }; +diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c +--- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c ++++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c +@@ -432,7 +432,7 @@ static int soc_card_dai_links_create(struct snd_soc_card *card) + if (!soc_aux) + return -ENOMEM; + +- ret = asoc_sdw_parse_sdw_endpoints(card, soc_aux, soc_dais, soc_ends, &num_confs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, soc_aux, soc_dais, soc_ends, &num_confs); + if (ret < 0) + return ret; + +@@ -521,15 +521,15 @@ static int mc_probe(struct platform_device *pdev) + dmi_check_system(soc_sdw_quirk_table); + + if (quirk_override != -1) { +- dev_info(card->dev, "Overriding quirk 0x%lx => 0x%x\n", ++ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + soc_sdw_quirk, quirk_override); + soc_sdw_quirk = quirk_override; + } + +- log_quirks(card->dev); ++ log_quirks(&pdev->dev); + + ctx->mc_quirk = soc_sdw_quirk; +- dev_dbg(card->dev, "legacy quirk 0x%lx\n", ctx->mc_quirk); ++ dev_dbg(&pdev->dev, "legacy quirk 0x%lx\n", ctx->mc_quirk); + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ + for (i = 0; i < ctx->codec_info_list_count; i++) + codec_info_list[i].amp_num = 0; +@@ -546,12 +546,12 @@ static int mc_probe(struct platform_device *pdev) + for (i = 0; i < ctx->codec_info_list_count; i++) + amp_num += codec_info_list[i].amp_num; + +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + " cfg-amp:%d", amp_num); + if (!card->components) + return -ENOMEM; + if (soc_sdw_quirk & ASOC_SDW_ACP_DMIC) { +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%s mic:acp-dmic cfg-mics:%d", + card->components, + 1); +@@ -560,9 +560,9 @@ static int mc_probe(struct platform_device *pdev) + } + + /* Register the card */ +- ret = devm_snd_soc_register_card(card->dev, card); ++ ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { +- dev_err_probe(card->dev, ret, "snd_soc_register_card failed %d\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed %d\n", ret); + asoc_sdw_mc_dailink_exit_loop(card); + return ret; + } +@@ -580,8 +580,8 @@ static void mc_remove(struct platform_device *pdev) + } + + static const struct platform_device_id mc_id_table[] = { +- { "amd_sdw", }, +- {} ++ { .name = "amd_sdw" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, mc_id_table); + +diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c +--- a/sound/soc/amd/acp/acp-sdw-sof-mach.c ++++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c +@@ -303,7 +303,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) + if (!sof_aux) + return -ENOMEM; + +- ret = asoc_sdw_parse_sdw_endpoints(card, sof_aux, sof_dais, sof_ends, &num_devs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_devs); + if (ret < 0) + return ret; + +@@ -388,12 +388,12 @@ static int mc_probe(struct platform_device *pdev) + dmi_check_system(sof_sdw_quirk_table); + + if (quirk_override != -1) { +- dev_info(card->dev, "Overriding quirk 0x%lx => 0x%x\n", ++ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + sof_sdw_quirk, quirk_override); + sof_sdw_quirk = quirk_override; + } + +- log_quirks(card->dev); ++ log_quirks(&pdev->dev); + + ctx->mc_quirk = sof_sdw_quirk; + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ +@@ -412,15 +412,15 @@ static int mc_probe(struct platform_device *pdev) + for (i = 0; i < ctx->codec_info_list_count; i++) + amp_num += codec_info_list[i].amp_num; + +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + " cfg-amp:%d", amp_num); + if (!card->components) + return -ENOMEM; + + /* Register the card */ +- ret = devm_snd_soc_register_card(card->dev, card); ++ ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { +- dev_err_probe(card->dev, ret, "snd_soc_register_card failed %d\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed %d\n", ret); + asoc_sdw_mc_dailink_exit_loop(card); + return ret; + } +@@ -438,8 +438,8 @@ static void mc_remove(struct platform_device *pdev) + } + + static const struct platform_device_id mc_id_table[] = { +- { "amd_sof_sdw", }, +- {} ++ { .name = "amd_sof_sdw" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, mc_id_table); + +diff --git a/sound/soc/amd/acp/acp-sof-mach.c b/sound/soc/amd/acp/acp-sof-mach.c +--- a/sound/soc/amd/acp/acp-sof-mach.c ++++ b/sound/soc/amd/acp/acp-sof-mach.c +@@ -128,31 +128,31 @@ static int acp_sof_probe(struct platform_device *pdev) + static const struct platform_device_id board_ids[] = { + { + .name = "rt5682-rt1019", +- .driver_data = (kernel_ulong_t)&sof_rt5682_rt1019_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682_rt1019_data, + }, + { + .name = "rt5682-max", +- .driver_data = (kernel_ulong_t)&sof_rt5682_max_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682_max_data, + }, + { + .name = "rt5682s-max", +- .driver_data = (kernel_ulong_t)&sof_rt5682s_max_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682s_max_data, + }, + { + .name = "rt5682s-rt1019", +- .driver_data = (kernel_ulong_t)&sof_rt5682s_rt1019_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682s_rt1019_data, + }, + { + .name = "nau8825-max", +- .driver_data = (kernel_ulong_t)&sof_nau8825_data ++ .driver_data = (kernel_ulong_t)&sof_nau8825_data, + }, + { + .name = "rt5682s-hs-rt1019", +- .driver_data = (kernel_ulong_t)&sof_rt5682s_hs_rt1019_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682s_hs_rt1019_data, + }, + { + .name = "nau8821-max", +- .driver_data = (kernel_ulong_t)&sof_nau8821_max98388_data ++ .driver_data = (kernel_ulong_t)&sof_nau8821_max98388_data, + }, + { } + }; +diff --git a/sound/soc/amd/acp/amd-acp70-acpi-match.c b/sound/soc/amd/acp/amd-acp70-acpi-match.c +--- a/sound/soc/amd/acp/amd-acp70-acpi-match.c ++++ b/sound/soc/amd/acp/amd-acp70-acpi-match.c +@@ -659,6 +659,56 @@ static const struct snd_soc_acpi_link_adr acp70_rt721_l1u0_tas2783x2_l1u8b[] = { + {} + }; + ++static const struct snd_soc_acpi_adr_device rt712_vb_l1u0_adr[] = { ++ { ++ .adr = 0x000130025D071201ull, ++ /* ++ * On this platform speakers are provided by two TAS2783 amps, ++ * so the AIF2 amp path is left unused: jack + DMIC only. ++ */ ++ .num_endpoints = ARRAY_SIZE(jack_dmic_endpoints), ++ .endpoints = jack_dmic_endpoints, ++ .name_prefix = "rt712" ++ } ++}; ++ ++/* ++ * Unique ID 0xC drives the left speaker and 0x9 the right one. The order of the ++ * entries matters as much as the endpoints: asoc_sdw_parse_sdw_endpoints() ++ * appends them to the dailink in array order and sdw_compute_slave_ports() ++ * hands out payload block offsets in that same order, so the first entry is the ++ * one that receives channel 0. ++ */ ++static const struct snd_soc_acpi_adr_device tas2783x2_l0u9c_adr[] = { ++ { ++ .adr = 0x00003C0102000001ull, ++ .num_endpoints = 1, ++ .endpoints = &spk_l_endpoint, ++ .name_prefix = "tas2783-1", ++ }, ++ { ++ .adr = 0x0000390102000001ull, ++ .num_endpoints = 1, ++ .endpoints = &spk_r_endpoint, ++ .name_prefix = "tas2783-2", ++ }, ++}; ++ ++/* HP OmniBook X Flip 14-kc0xxx (board 8EA1) */ ++static const struct snd_soc_acpi_link_adr acp70_tas2783x2_l0u9c_rt712_vb_l1u0[] = { ++ { ++ .mask = BIT(0), ++ .num_adr = ARRAY_SIZE(tas2783x2_l0u9c_adr), ++ .adr_d = tas2783x2_l0u9c_adr, ++ }, ++ { ++ .mask = BIT(1), ++ .num_adr = ARRAY_SIZE(rt712_vb_l1u0_adr), ++ .adr_d = rt712_vb_l1u0_adr, ++ }, ++ {} ++}; ++ + static const struct snd_soc_acpi_endpoint rt721_endpoints[] = { + { /* Jack Playback/Capture Endpoint (AIF1) */ + .num = 0, +@@ -704,6 +754,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[] = { + .links = acp70_tas2783_2, + .drv_name = "amd_sdw", + }, ++ { ++ .link_mask = BIT(0) | BIT(1), ++ .links = acp70_tas2783x2_l0u9c_rt712_vb_l1u0, ++ .machine_check = snd_soc_acpi_amd_sdca_is_device_rt712_vb, ++ .drv_name = "amd_sdw", ++ }, + { + .link_mask = BIT(0) | BIT(1), + .links = acp70_rt1320_l0_rt722_l1, +diff --git a/sound/soc/amd/mach-config.h b/sound/soc/amd/mach-config.h +--- a/sound/soc/amd/mach-config.h ++++ b/sound/soc/amd/mach-config.h +@@ -28,6 +28,7 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[]; + extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[]; + extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[]; + extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_sdw_machines[]; ++extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[]; + + struct config_entry { + u32 flags; +diff --git a/sound/soc/amd/raven/acp3x-i2s.c b/sound/soc/amd/raven/acp3x-i2s.c +--- a/sound/soc/amd/raven/acp3x-i2s.c ++++ b/sound/soc/amd/raven/acp3x-i2s.c +@@ -250,11 +250,17 @@ static int acp3x_i2s_trigger(struct snd_pcm_substream *substream, + return ret; + } + ++static const u64 acp3x_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + static const struct snd_soc_dai_ops acp3x_i2s_dai_ops = { + .hw_params = acp3x_i2s_hwparams, + .trigger = acp3x_i2s_trigger, + .set_fmt = acp3x_i2s_set_fmt, + .set_tdm_slot = acp3x_i2s_set_tdm_slot, ++ .auto_selectable_formats = &acp3x_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_component_driver acp3x_dai_component = { +diff --git a/sound/soc/amd/vangogh/acp5x-i2s.c b/sound/soc/amd/vangogh/acp5x-i2s.c +--- a/sound/soc/amd/vangogh/acp5x-i2s.c ++++ b/sound/soc/amd/vangogh/acp5x-i2s.c +@@ -337,11 +337,17 @@ static int acp5x_i2s_trigger(struct snd_pcm_substream *substream, + return ret; + } + ++static const u64 acp5x_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + static const struct snd_soc_dai_ops acp5x_i2s_dai_ops = { + .hw_params = acp5x_i2s_hwparams, + .trigger = acp5x_i2s_trigger, + .set_fmt = acp5x_i2s_set_fmt, + .set_tdm_slot = acp5x_i2s_set_tdm_slot, ++ .auto_selectable_formats = &acp5x_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_component_driver acp5x_dai_component = { +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -795,12 +795,12 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + } + }, + { +- .driver_data = &acp6x_card, +- .matches = { +- DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), +- DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook_ASUSLaptop M6501RR_M6501RR"), +- } +- }, ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook_ASUSLaptop M6501RR_M6501RR"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c +--- a/sound/soc/apple/mca.c ++++ b/sound/soc/apple/mca.c +@@ -133,12 +133,17 @@ struct mca_cluster { + struct clk *clk_parent; + struct dma_chan *dma_chans[SNDRV_PCM_STREAM_LAST + 1]; + +- bool port_started[SNDRV_PCM_STREAM_LAST + 1]; +- int port_driver; /* The cluster driving this cluster's port */ ++ bool clk_provider; ++ ++ bool port_clk_started[SNDRV_PCM_STREAM_LAST + 1]; ++ int port_clk_driver; /* The cluster driving this cluster's port */ + + bool clocks_in_use[SNDRV_PCM_STREAM_LAST + 1]; + struct device_link *pd_link; + ++ /* In case of clock consumer FE */ ++ int syncgen_in_use; ++ + unsigned int bclk_ratio; + + /* Masks etc. picked up via the set_tdm_slot method */ +@@ -157,7 +162,7 @@ struct mca_data { + struct reset_control *rstc; + struct device_link *pd_link; + +- /* Mutex for accessing port_driver of foreign clusters */ ++ /* Mutex for accessing port_clk_driver of foreign clusters */ + struct mutex port_mutex; + + int nclusters; +@@ -210,16 +215,22 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd, + SERDES_STATUS_EN | SERDES_STATUS_RST, + SERDES_STATUS_RST); + /* +- * Experiments suggest that it takes at most ~1 us +- * for the bit to clear, so wait 2 us for good measure. ++ * The SERDES cluster needs a bit of time to reset itself ++ * and settle before we start poking it. This is... slow... + */ +- udelay(2); ++ udelay(25); + WARN_ON(readl_relaxed(cl->base + serdes_unit + REG_SERDES_STATUS) & + SERDES_STATUS_RST); + mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL, + FIELD_PREP(SERDES_CONF_SYNC_SEL, 0)); + mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL, + FIELD_PREP(SERDES_CONF_SYNC_SEL, cl->no + 1)); ++ /* ++ * ADMAC gets started right after this. This delay seems ++ * to be needed for that to be reliable, e.g. ensure the ++ * clock is stable? ++ */ ++ udelay(100); + break; + default: + break; +@@ -256,11 +267,29 @@ static int mca_fe_trigger(struct snd_pcm_substream *substream, int cmd, + return 0; + } + ++static int mca_fe_get_portmask(struct snd_pcm_substream *substream) ++{ ++ struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dpcm *dpcm; ++ int mask = 0; ++ ++ for_each_dpcm_be(fe, substream->stream, dpcm) { ++ int no = mca_dai_to_cluster(snd_soc_rtd_to_cpu(dpcm->be, 0))->no; ++ ++ mask |= 1 << no; ++ } ++ ++ return mask; ++} ++ + static int mca_fe_enable_clocks(struct mca_cluster *cl) + { + struct mca_data *mca = cl->host; + int ret; + ++ if (!cl->clk_provider) ++ return -EINVAL; ++ + ret = clk_prepare_enable(cl->clk_parent); + if (ret) { + dev_err(mca->dev, +@@ -311,7 +340,7 @@ static bool mca_fe_clocks_in_use(struct mca_cluster *cl) + for (i = 0; i < mca->nclusters; i++) { + be_cl = &mca->clusters[i]; + +- if (be_cl->port_driver != cl->no) ++ if (be_cl->port_clk_driver != cl->no) + continue; + + for_each_pcm_streams(stream) { +@@ -323,36 +352,6 @@ static bool mca_fe_clocks_in_use(struct mca_cluster *cl) + return false; + } + +-static int mca_be_prepare(struct snd_pcm_substream *substream, +- struct snd_soc_dai *dai) +-{ +- struct mca_cluster *cl = mca_dai_to_cluster(dai); +- struct mca_data *mca = cl->host; +- struct mca_cluster *fe_cl; +- int ret; +- +- if (cl->port_driver < 0) +- return -EINVAL; +- +- fe_cl = &mca->clusters[cl->port_driver]; +- +- /* +- * Typically the CODECs we are paired with will require clocks +- * to be present at time of unmute with the 'mute_stream' op +- * or at time of DAPM widget power-up. We need to enable clocks +- * here at the latest (frontend prepare would be too late). +- */ +- if (!mca_fe_clocks_in_use(fe_cl)) { +- ret = mca_fe_enable_clocks(fe_cl); +- if (ret < 0) +- return ret; +- } +- +- cl->clocks_in_use[substream->stream] = true; +- +- return 0; +-} +- + static int mca_be_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { +@@ -360,7 +359,7 @@ static int mca_be_hw_free(struct snd_pcm_substream *substream, + struct mca_data *mca = cl->host; + struct mca_cluster *fe_cl; + +- if (cl->port_driver < 0) ++ if (cl->port_clk_driver < 0) + return -EINVAL; + + /* +@@ -368,7 +367,7 @@ static int mca_be_hw_free(struct snd_pcm_substream *substream, + * belong to the same PCM, accesses should have been + * synchronized at ASoC level. + */ +- fe_cl = &mca->clusters[cl->port_driver]; ++ fe_cl = &mca->clusters[cl->port_clk_driver]; + if (!mca_fe_clocks_in_use(fe_cl)) + return 0; /* Nothing to do */ + +@@ -380,6 +379,56 @@ static int mca_be_hw_free(struct snd_pcm_substream *substream, + return 0; + } + ++static int mca_fe_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ struct mca_data *mca = cl->host; ++ ++ if (cl->clk_provider) ++ return 0; ++ ++ /* Turn on the cluster power domain if not already in use */ ++ if (!cl->syncgen_in_use) { ++ int port = ffs(mca_fe_get_portmask(substream)); ++ ++ cl->pd_link = device_link_add(mca->dev, cl->pd_dev, ++ DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | ++ DL_FLAG_RPM_ACTIVE); ++ if (!cl->pd_link) { ++ dev_err(mca->dev, ++ "cluster %d: unable to prop-up power domain\n", cl->no); ++ return -EINVAL; ++ } ++ ++ mca_modify(cl, REG_SYNCGEN_MCLK_SEL, SYNCGEN_MCLK_SEL, BIT(port)); ++ mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, ++ SYNCGEN_STATUS_EN); ++ } ++ cl->syncgen_in_use |= 1 << substream->stream; ++ ++ return 0; ++} ++ ++static int mca_fe_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ ++ if (cl->clk_provider) ++ return 0; ++ ++ cl->syncgen_in_use &= ~(1 << substream->stream); ++ if (cl->syncgen_in_use) ++ return 0; ++ ++ mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, 0); ++ if (cl->pd_link) ++ device_link_del(cl->pd_link); ++ ++ return 0; ++} ++ + static unsigned int mca_crop_mask(unsigned int mask, int nchans) + { + while (hweight32(mask) > nchans) +@@ -390,7 +439,7 @@ static unsigned int mca_crop_mask(unsigned int mask, int nchans) + + static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit, + unsigned int mask, int slots, int nchans, +- int slot_width, bool is_tx, int port) ++ int slot_width, bool is_tx, int portmask) + { + __iomem void *serdes_base = cl->base + serdes_unit; + u32 serdes_conf, serdes_conf_mask; +@@ -449,7 +498,7 @@ static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit, + serdes_base + REG_RX_SERDES_SLOTMASK); + writel_relaxed(~((u32)mca_crop_mask(mask, nchans)), + serdes_base + REG_RX_SERDES_SLOTMASK + 0x4); +- writel_relaxed(1 << port, ++ writel_relaxed(portmask, + serdes_base + REG_RX_SERDES_PORT); + } + +@@ -505,9 +554,18 @@ static int mca_fe_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + u32 serdes_conf = 0; + u32 bitstart; + +- if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != +- SND_SOC_DAIFMT_BP_FP) ++ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { ++ case SND_SOC_DAIFMT_BP_FP: ++ cl->clk_provider = true; ++ break; ++ ++ case SND_SOC_DAIFMT_BC_FC: ++ cl->clk_provider = false; ++ break; ++ ++ default: + goto err; ++ } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: +@@ -564,24 +622,6 @@ static int mca_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) + return 0; + } + +-static int mca_fe_get_port(struct snd_pcm_substream *substream) +-{ +- struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); +- struct snd_soc_pcm_runtime *be; +- struct snd_soc_dpcm *dpcm; +- +- be = NULL; +- for_each_dpcm_be(fe, substream->stream, dpcm) { +- be = dpcm->be; +- break; +- } +- +- if (!be) +- return -EINVAL; +- +- return mca_dai_to_cluster(snd_soc_rtd_to_cpu(be, 0))->no; +-} +- + static int mca_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +@@ -595,7 +635,7 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, + unsigned long bclk_ratio; + unsigned int tdm_slots, tdm_slot_width, tdm_mask; + u32 regval, pad; +- int ret, port, nchans_ceiled; ++ int ret, portmask, nchans_ceiled; + + if (!cl->tdm_slot_width) { + /* +@@ -644,13 +684,13 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, + tdm_mask = (1 << tdm_slots) - 1; + } + +- port = mca_fe_get_port(substream); +- if (port < 0) +- return port; ++ portmask = mca_fe_get_portmask(substream); ++ if (!portmask) ++ return -EINVAL; + + ret = mca_configure_serdes(cl, is_tx ? CLUSTER_TX_OFF : CLUSTER_RX_OFF, + tdm_mask, tdm_slots, params_channels(params), +- tdm_slot_width, is_tx, port); ++ tdm_slot_width, is_tx, portmask); + if (ret) + return ret; + +@@ -699,6 +739,19 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 mca_fe_selectable_formats[] = ++{ ++ /* pattern 1 */ ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF, ++ ++ /* pattern 2 */ ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF ++}; ++ + static const struct snd_soc_dai_ops mca_fe_ops = { + .startup = mca_fe_startup, + .set_fmt = mca_fe_set_fmt, +@@ -706,66 +759,128 @@ static const struct snd_soc_dai_ops mca_fe_ops = { + .set_tdm_slot = mca_fe_set_tdm_slot, + .hw_params = mca_fe_hw_params, + .trigger = mca_fe_trigger, ++ .prepare = mca_fe_prepare, ++ .hw_free = mca_fe_hw_free, ++ .auto_selectable_formats = mca_fe_selectable_formats, ++ .num_auto_selectable_formats = ARRAY_SIZE(mca_fe_selectable_formats), + }; + +-static bool mca_be_started(struct mca_cluster *cl) ++/* ++ * Is there a FE attached which will be feeding this port's clocks? ++ */ ++static bool mca_be_clk_started(struct mca_cluster *cl) + { + int stream; + + for_each_pcm_streams(stream) +- if (cl->port_started[stream]) ++ if (cl->port_clk_started[stream]) + return true; + return false; + } + ++static struct snd_soc_pcm_runtime *mca_be_get_fe(struct snd_soc_pcm_runtime *be, ++ int stream) ++{ ++ struct snd_soc_pcm_runtime *fe = NULL; ++ struct snd_soc_dpcm *dpcm; ++ ++ for_each_dpcm_fe(be, stream, dpcm) { ++ if (fe && dpcm->fe != fe) { ++ dev_err(be->dev, "many FE per one BE unsupported\n"); ++ return NULL; ++ } ++ ++ fe = dpcm->fe; ++ } ++ ++ return fe; ++} ++ ++static int mca_be_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); ++ struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ struct mca_data *mca = cl->host; ++ struct mca_cluster *fe_cl, *fe_clk_cl; ++ int ret; ++ ++ fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); ++ ++ if (!fe_cl->clk_provider) ++ return 0; ++ ++ if (cl->port_clk_driver < 0) ++ return 0; ++ ++ fe_clk_cl = &mca->clusters[cl->port_clk_driver]; ++ ++ /* ++ * Typically the CODECs we are paired with will require clocks ++ * to be present at time of unmute with the 'mute_stream' op ++ * or at time of DAPM widget power-up. We need to enable clocks ++ * here at the latest (frontend prepare would be too late). ++ */ ++ if (!mca_fe_clocks_in_use(fe_clk_cl)) { ++ ret = mca_fe_enable_clocks(fe_clk_cl); ++ if (ret < 0) ++ return ret; ++ } ++ ++ cl->clocks_in_use[substream->stream] = true; ++ ++ return 0; ++} ++ + static int mca_be_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { + struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); +- struct snd_soc_pcm_runtime *fe; ++ struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); + struct mca_cluster *cl = mca_dai_to_cluster(dai); + struct mca_cluster *fe_cl; + struct mca_data *mca = cl->host; +- struct snd_soc_dpcm *dpcm; +- +- fe = NULL; +- +- for_each_dpcm_fe(be, substream->stream, dpcm) { +- if (fe && dpcm->fe != fe) { +- dev_err(mca->dev, "many FE per one BE unsupported\n"); +- return -EINVAL; +- } +- +- fe = dpcm->fe; +- } + + if (!fe) + return -EINVAL; +- + fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); + +- if (mca_be_started(cl)) { ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ writel_relaxed(PORT_DATA_SEL_TXA(fe_cl->no), ++ cl->base + REG_PORT_DATA_SEL); ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA, ++ PORT_ENABLES_TX_DATA); ++ } ++ ++ if (!fe_cl->clk_provider) ++ return 0; ++ ++ if (mca_be_clk_started(cl)) { + /* + * Port is already started in the other direction. + * Make sure there isn't a conflict with another cluster +- * driving the port. ++ * driving the port clocks. + */ +- if (cl->port_driver != fe_cl->no) ++ if (cl->port_clk_driver != fe_cl->no) + return -EINVAL; + +- cl->port_started[substream->stream] = true; ++ cl->port_clk_started[substream->stream] = true; + return 0; + } + +- writel_relaxed(PORT_ENABLES_CLOCKS | PORT_ENABLES_TX_DATA, +- cl->base + REG_PORT_ENABLES); + writel_relaxed(FIELD_PREP(PORT_CLOCK_SEL, fe_cl->no + 1), + cl->base + REG_PORT_CLOCK_SEL); ++ + writel_relaxed(PORT_DATA_SEL_TXA(fe_cl->no), + cl->base + REG_PORT_DATA_SEL); ++ ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_CLOCKS, ++ PORT_ENABLES_CLOCKS); ++ + scoped_guard(mutex, &mca->port_mutex) +- cl->port_driver = fe_cl->no; +- cl->port_started[substream->stream] = true; ++ cl->port_clk_driver = fe_cl->no; ++ cl->port_clk_started[substream->stream] = true; + + return 0; + } +@@ -773,20 +888,34 @@ static int mca_be_startup(struct snd_pcm_substream *substream, + static void mca_be_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { ++ struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); + struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ struct mca_cluster *fe_cl; + struct mca_data *mca = cl->host; + +- cl->port_started[substream->stream] = false; ++ if (!fe) ++ return; ++ fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); + +- if (!mca_be_started(cl)) { ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA, 0); ++ writel_relaxed(0, cl->base + REG_PORT_DATA_SEL); ++ } ++ ++ if (!fe_cl->clk_provider) ++ return; ++ ++ cl->port_clk_started[substream->stream] = false; ++ if (!mca_be_clk_started(cl)) { + /* + * Were we the last direction to shutdown? +- * Turn off the lights. ++ * Turn off the lights (clocks). + */ +- writel_relaxed(0, cl->base + REG_PORT_ENABLES); +- writel_relaxed(0, cl->base + REG_PORT_DATA_SEL); ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_CLOCKS, 0); ++ writel_relaxed(0, cl->base + REG_PORT_CLOCK_SEL); + scoped_guard(mutex, &mca->port_mutex) +- cl->port_driver = -1; ++ cl->port_clk_driver = -1; + } + } + +@@ -1092,7 +1221,7 @@ static int apple_mca_probe(struct platform_device *pdev) + cl->host = mca; + cl->no = i; + cl->base = base + CLUSTER_STRIDE * i; +- cl->port_driver = -1; ++ cl->port_clk_driver = -1; + cl->clk_parent = of_clk_get(pdev->dev.of_node, i); + if (IS_ERR(cl->clk_parent)) { + dev_err(&pdev->dev, "unable to obtain clock %d: %ld\n", +diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c +--- a/sound/soc/atmel/atmel-i2s.c ++++ b/sound/soc/atmel/atmel-i2s.c +@@ -540,12 +540,16 @@ static int atmel_i2s_dai_probe(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 atmel_i2s_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_I2S; ++ + static const struct snd_soc_dai_ops atmel_i2s_dai_ops = { + .probe = atmel_i2s_dai_probe, + .prepare = atmel_i2s_prepare, + .trigger = atmel_i2s_trigger, + .hw_params = atmel_i2s_hw_params, + .set_fmt = atmel_i2s_set_dai_fmt, ++ .auto_selectable_formats = &atmel_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver atmel_i2s_dai = { +diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c +--- a/sound/soc/atmel/atmel_ssc_dai.c ++++ b/sound/soc/atmel/atmel_ssc_dai.c +@@ -825,6 +825,11 @@ static int atmel_ssc_resume(struct snd_soc_component *component) + #define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + ++static const u64 atmel_ssc_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + static const struct snd_soc_dai_ops atmel_ssc_dai_ops = { + .startup = atmel_ssc_startup, + .shutdown = atmel_ssc_shutdown, +@@ -833,6 +838,8 @@ static const struct snd_soc_dai_ops atmel_ssc_dai_ops = { + .hw_params = atmel_ssc_hw_params, + .set_fmt = atmel_ssc_set_dai_fmt, + .set_clkdiv = atmel_ssc_set_dai_clkdiv, ++ .auto_selectable_formats = &atmel_ssc_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver atmel_ssc_dai = { +diff --git a/sound/soc/atmel/atmel_wm8904.c b/sound/soc/atmel/atmel_wm8904.c +--- a/sound/soc/atmel/atmel_wm8904.c ++++ b/sound/soc/atmel/atmel_wm8904.c +@@ -82,32 +82,33 @@ static struct snd_soc_card atmel_asoc_wm8904_card = { + + static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev) + { +- struct device_node *np = pdev->dev.of_node; ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node; + struct device_node *codec_np, *cpu_np; + struct snd_soc_card *card = &atmel_asoc_wm8904_card; + struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; + int ret; + + if (!np) { +- dev_err(&pdev->dev, "only device tree supported\n"); ++ dev_err(dev, "only device tree supported\n"); + return -EINVAL; + } + + ret = snd_soc_of_parse_card_name(card, "atmel,model"); + if (ret) { +- dev_err(&pdev->dev, "failed to parse card name\n"); ++ dev_err(dev, "failed to parse card name\n"); + return ret; + } + + ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing"); + if (ret) { +- dev_err(&pdev->dev, "failed to parse audio routing\n"); ++ dev_err(dev, "failed to parse audio routing\n"); + return ret; + } + + cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0); + if (!cpu_np) { +- dev_err(&pdev->dev, "failed to get dai and pcm info\n"); ++ dev_err(dev, "failed to get dai and pcm info\n"); + ret = -EINVAL; + return ret; + } +@@ -117,7 +118,7 @@ static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev) + + codec_np = of_parse_phandle(np, "atmel,audio-codec", 0); + if (!codec_np) { +- dev_err(&pdev->dev, "failed to get codec info\n"); ++ dev_err(dev, "failed to get codec info\n"); + ret = -EINVAL; + return ret; + } +diff --git a/sound/soc/atmel/mchp-i2s-mcc.c b/sound/soc/atmel/mchp-i2s-mcc.c +--- a/sound/soc/atmel/mchp-i2s-mcc.c ++++ b/sound/soc/atmel/mchp-i2s-mcc.c +@@ -912,6 +912,13 @@ static int mchp_i2s_mcc_dai_probe(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 mchp_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_GATED | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = { + .probe = mchp_i2s_mcc_dai_probe, + .set_sysclk = mchp_i2s_mcc_set_sysclk, +@@ -922,6 +929,8 @@ static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = { + .hw_free = mchp_i2s_mcc_hw_free, + .set_fmt = mchp_i2s_mcc_set_dai_fmt, + .set_tdm_slot = mchp_i2s_mcc_set_dai_tdm_slot, ++ .auto_selectable_formats = &mchp_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define MCHP_I2SMCC_RATES SNDRV_PCM_RATE_8000_192000 +diff --git a/sound/soc/atmel/mchp-pdmc.c b/sound/soc/atmel/mchp-pdmc.c +--- a/sound/soc/atmel/mchp-pdmc.c ++++ b/sound/soc/atmel/mchp-pdmc.c +@@ -741,6 +741,8 @@ static int mchp_pdmc_pcm_new(struct snd_soc_pcm_runtime *rtd, + return ret; + } + ++static const u64 mchp_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_PDM; ++ + static const struct snd_soc_dai_ops mchp_pdmc_dai_ops = { + .probe = mchp_pdmc_dai_probe, + .set_fmt = mchp_pdmc_set_fmt, +@@ -748,6 +750,8 @@ static const struct snd_soc_dai_ops mchp_pdmc_dai_ops = { + .hw_params = mchp_pdmc_hw_params, + .trigger = mchp_pdmc_trigger, + .pcm_new = &mchp_pdmc_pcm_new, ++ .auto_selectable_formats = &mchp_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver mchp_pdmc_dai = { +diff --git a/sound/soc/au1x/db1200.c b/sound/soc/au1x/db1200.c +--- a/sound/soc/au1x/db1200.c ++++ b/sound/soc/au1x/db1200.c +@@ -42,7 +42,7 @@ static const struct platform_device_id db1200_pids[] = { + .name = "db1550-i2s", + .driver_data = 5, + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, db1200_pids); + +diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c +--- a/sound/soc/au1x/i2sc.c ++++ b/sound/soc/au1x/i2sc.c +@@ -202,11 +202,22 @@ static int au1xi2s_startup(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 au1xi2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops au1xi2s_dai_ops = { + .startup = au1xi2s_startup, + .trigger = au1xi2s_trigger, + .hw_params = au1xi2s_hw_params, + .set_fmt = au1xi2s_set_fmt, ++ .auto_selectable_formats = &au1xi2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver au1xi2s_dai_driver = { +diff --git a/sound/soc/au1x/psc-ac97.c b/sound/soc/au1x/psc-ac97.c +--- a/sound/soc/au1x/psc-ac97.c ++++ b/sound/soc/au1x/psc-ac97.c +@@ -210,9 +210,7 @@ static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream, + { + struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai); + unsigned long r, ro, stat; +- int chans, t, stype = substream->stream; +- +- chans = params_channels(params); ++ int t, stype = substream->stream; + + r = ro = __raw_readl(AC97_CFG(pscdata)); + stat = __raw_readl(AC97_STAT(pscdata)); +diff --git a/sound/soc/au1x/psc-i2s.c b/sound/soc/au1x/psc-i2s.c +--- a/sound/soc/au1x/psc-i2s.c ++++ b/sound/soc/au1x/psc-i2s.c +@@ -262,11 +262,22 @@ static int au1xpsc_i2s_startup(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 au1xpsc_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops au1xpsc_i2s_dai_ops = { + .startup = au1xpsc_i2s_startup, + .trigger = au1xpsc_i2s_trigger, + .hw_params = au1xpsc_i2s_hw_params, + .set_fmt = au1xpsc_i2s_set_fmt, ++ .auto_selectable_formats = &au1xpsc_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_driver au1xpsc_i2s_dai_template = { +diff --git a/sound/soc/bcm/bcm2835-i2s.c b/sound/soc/bcm/bcm2835-i2s.c +--- a/sound/soc/bcm/bcm2835-i2s.c ++++ b/sound/soc/bcm/bcm2835-i2s.c +@@ -748,6 +748,17 @@ static int bcm2835_i2s_dai_probe(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 bcm2835_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = { + .probe = bcm2835_i2s_dai_probe, + .startup = bcm2835_i2s_startup, +@@ -758,6 +769,8 @@ static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = { + .set_fmt = bcm2835_i2s_set_dai_fmt, + .set_bclk_ratio = bcm2835_i2s_set_dai_bclk_ratio, + .set_tdm_slot = bcm2835_i2s_set_dai_tdm_slot, ++ .auto_selectable_formats = &bcm2835_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver bcm2835_i2s_dai = { +diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c +--- a/sound/soc/bcm/cygnus-ssp.c ++++ b/sound/soc/bcm/cygnus-ssp.c +@@ -1133,6 +1133,11 @@ static int cygnus_ssp_resume(struct snd_soc_component *component) + #define cygnus_ssp_resume NULL + #endif + ++static const u64 cygnus_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B; ++ + static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { + .startup = cygnus_ssp_startup, + .shutdown = cygnus_ssp_shutdown, +@@ -1141,6 +1146,8 @@ static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { + .set_fmt = cygnus_ssp_set_fmt, + .set_sysclk = cygnus_ssp_set_sysclk, + .set_tdm_slot = cygnus_set_dai_tdm_slot, ++ .auto_selectable_formats = &cygnus_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_ops cygnus_spdif_dai_ops = { +diff --git a/sound/soc/cirrus/ep93xx-i2s.c b/sound/soc/cirrus/ep93xx-i2s.c +--- a/sound/soc/cirrus/ep93xx-i2s.c ++++ b/sound/soc/cirrus/ep93xx-i2s.c +@@ -401,6 +401,15 @@ static int ep93xx_i2s_resume(struct snd_soc_component *component) + #define ep93xx_i2s_resume NULL + #endif + ++static const u64 ep93xx_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = { + .probe = ep93xx_i2s_dai_probe, + .startup = ep93xx_i2s_startup, +@@ -408,6 +417,8 @@ static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = { + .hw_params = ep93xx_i2s_hw_params, + .set_sysclk = ep93xx_i2s_set_sysclk, + .set_fmt = ep93xx_i2s_set_dai_fmt, ++ .auto_selectable_formats = &ep93xx_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define EP93XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -238,6 +238,7 @@ config SND_SOC_ALL_CODECS + imply SND_SOC_RT715_SDCA_SDW + imply SND_SOC_RT721_SDCA_SDW + imply SND_SOC_RT722_SDCA_SDW ++ imply SND_SOC_RT766_SDCA_SDW + imply SND_SOC_RT1308_SDW + imply SND_SOC_RT1316_SDW + imply SND_SOC_RT1318 +@@ -367,6 +368,7 @@ config SND_SOC_ALL_CODECS + imply SND_SOC_WSA881X + imply SND_SOC_WSA883X + imply SND_SOC_WSA884X ++ imply SND_SOC_WSA885X + imply SND_SOC_ZL38060 + help + Normally ASoC codec drivers are only built if a machine driver which +@@ -615,8 +617,8 @@ config SND_SOC_AK4613 + depends on I2C + + config SND_SOC_AK4619 +- tristate "AKM AK4619 CODEC" +- depends on I2C ++ tristate "AKM AK4619 CODEC" ++ depends on I2C + + config SND_SOC_AK4642 + tristate "AKM AK4642 CODEC" +@@ -726,6 +728,10 @@ config SND_SOC_AW87390 + sound quality, which is a new high efficiency, low + noise, constant large volume, 6th Smart K audio amplifier. + ++config SND_SOC_AW88399_LIB ++ tristate ++ select SND_SOC_AW88395_LIB ++ + config SND_SOC_AW88399 + tristate "Soc Audio for awinic aw88399" + depends on I2C +@@ -733,6 +739,7 @@ config SND_SOC_AW88399 + select REGMAP_I2C + select GPIOLIB + select SND_SOC_AW88395_LIB ++ select SND_SOC_AW88399_LIB + help + This option enables support for aw88399 Smart PA. + The awinic AW88399 is an I2S/TDM input, high efficiency +@@ -889,6 +896,7 @@ config SND_SOC_CS35L56_SDW + tristate "Cirrus Logic CS35L56 CODEC (SDW)" + depends on SOUNDWIRE + select REGMAP_SOUNDWIRE ++ select IRQ_DOMAIN + select SND_SOC_CS35L56 + select SND_SOC_CS35L56_SHARED + help +@@ -1201,16 +1209,16 @@ config SND_SOC_JZ4725B_CODEC + will be called snd-soc-jz4725b-codec. + + config SND_SOC_JZ4760_CODEC +- depends on MACH_INGENIC || COMPILE_TEST +- depends on OF +- select REGMAP +- tristate "Ingenic JZ4760 internal CODEC" +- help +- Enable support for the internal CODEC found in the JZ4760 SoC +- from Ingenic. ++ depends on MACH_INGENIC || COMPILE_TEST ++ depends on OF ++ select REGMAP ++ tristate "Ingenic JZ4760 internal CODEC" ++ help ++ Enable support for the internal CODEC found in the JZ4760 SoC ++ from Ingenic. + +- This driver can also be built as a module. If so, the module +- will be called snd-soc-jz4760-codec. ++ This driver can also be built as a module. If so, the module ++ will be called snd-soc-jz4760-codec. + + config SND_SOC_JZ4770_CODEC + depends on MACH_INGENIC || COMPILE_TEST +@@ -1888,7 +1896,7 @@ config SND_SOC_RT5670 + depends on I2C + + config SND_SOC_RT5677 +- tristate ++ tristate "Realtek RT5677 Codec" + depends on I2C + select REGMAP_I2C + select REGMAP_IRQ +@@ -1978,6 +1986,15 @@ config SND_SOC_RT715_SDCA_SDW + select REGMAP_SOUNDWIRE + select REGMAP_SOUNDWIRE_MBQ + ++config SND_SOC_RT766_SDCA_SDW ++ tristate "Realtek RT766 SDCA Codec - SDW" ++ depends on SOUNDWIRE ++ depends on SND_SOC_SDCA ++ select SND_SOC_SDCA_HID ++ select SND_SOC_SDCA_IRQ ++ select REGMAP_SOUNDWIRE ++ select REGMAP_SOUNDWIRE_MBQ ++ + config SND_SOC_RT9120 + tristate "Richtek RT9120 Stereo Class-D Amplifier" + depends on I2C +@@ -2297,8 +2314,8 @@ config SND_SOC_TLV320ADC3XXX + depends on I2C + depends on GPIOLIB + help +- Enable support for Texas Instruments TLV320ADC3001 and TLV320ADC3101 +- ADCs. ++ Enable support for Texas Instruments TLV320ADC3001 and TLV320ADC3101 ++ ADCs. + + config SND_SOC_TLV320AIC23 + tristate +@@ -2771,6 +2788,16 @@ config SND_SOC_WSA884X + This enables support for Qualcomm WSA8840/WSA8845/WSA8845H Class-D + Smart Speaker Amplifier. + ++config SND_SOC_WSA885X ++ tristate "WSA885X Codec" ++ depends on I2C ++ select REGMAP_I2C ++ help ++ This enables support for Qualcomm WSA885X Stereo Smart Speaker ++ Amplifier. The codec driver programs the amplifier register ++ map and exposes the DAI and mixer controls used by Qualcomm ++ audio machine drivers. ++ + config SND_SOC_ZL38060 + tristate "Microsemi ZL38060 Connected Home Audio Processor" + depends on SPI_MASTER +@@ -2894,28 +2921,32 @@ config SND_SOC_TPA6130A2 + depends on I2C + + config SND_SOC_LPASS_MACRO_COMMON +- tristate ++ tristate + + config SND_SOC_LPASS_WSA_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm WSA Macro in LPASS(Low Power Audio SubSystem)" + + config SND_SOC_LPASS_VA_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm VA Macro in LPASS(Low Power Audio SubSystem)" + + config SND_SOC_LPASS_RX_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm RX Macro in LPASS(Low Power Audio SubSystem)" + + config SND_SOC_LPASS_TX_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm TX Macro in LPASS(Low Power Audio SubSystem)" +diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile +--- a/sound/soc/codecs/Makefile ++++ b/sound/soc/codecs/Makefile +@@ -55,6 +55,7 @@ snd-soc-aw88395-y := aw88395/aw88395.o + snd-soc-aw88166-y := aw88166.o + snd-soc-aw88261-y := aw88261.o + snd-soc-aw88399-y := aw88399.o ++snd-soc-aw88399-lib-y := aw88399-lib.o + snd-soc-bd28623-y := bd28623.o + snd-soc-bt-sco-y := bt-sco.o + snd-soc-chv3-codec-y := chv3-codec.o +@@ -284,6 +285,7 @@ snd-soc-rt715-y := rt715.o rt715-sdw.o + snd-soc-rt715-sdca-y := rt715-sdca.o rt715-sdca-sdw.o + snd-soc-rt721-sdca-y := rt721-sdca.o rt721-sdca-sdw.o + snd-soc-rt722-sdca-y := rt722-sdca.o rt722-sdca-sdw.o ++snd-soc-rt766-sdca-y := rt766-sdca.o rt766-sdca-sdw.o + snd-soc-rt9120-y := rt9120.o + snd-soc-rt9123-y := rt9123.o + snd-soc-rt9123p-y := rt9123p.o +@@ -426,6 +428,7 @@ snd-soc-wm-hubs-y := wm_hubs.o + snd-soc-wsa881x-y := wsa881x.o + snd-soc-wsa883x-y := wsa883x.o + snd-soc-wsa884x-y := wsa884x.o ++snd-soc-wsa885x-y := wsa885x.o + snd-soc-zl38060-y := zl38060.o + # Amp + snd-soc-max9877-y := max9877.o +@@ -496,6 +499,7 @@ obj-$(CONFIG_SND_SOC_AW88395) +=snd-soc-aw88395.o + obj-$(CONFIG_SND_SOC_AW88166) +=snd-soc-aw88166.o + obj-$(CONFIG_SND_SOC_AW88261) +=snd-soc-aw88261.o + obj-$(CONFIG_SND_SOC_AW88399) += snd-soc-aw88399.o ++obj-$(CONFIG_SND_SOC_AW88399_LIB) += snd-soc-aw88399-lib.o + obj-$(CONFIG_SND_SOC_BD28623) += snd-soc-bd28623.o + obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o + obj-$(CONFIG_SND_SOC_CHV3_CODEC) += snd-soc-chv3-codec.o +@@ -724,6 +728,7 @@ obj-$(CONFIG_SND_SOC_RT715) += snd-soc-rt715.o + obj-$(CONFIG_SND_SOC_RT715_SDCA_SDW) += snd-soc-rt715-sdca.o + obj-$(CONFIG_SND_SOC_RT721_SDCA_SDW) += snd-soc-rt721-sdca.o + obj-$(CONFIG_SND_SOC_RT722_SDCA_SDW) += snd-soc-rt722-sdca.o ++obj-$(CONFIG_SND_SOC_RT766_SDCA_SDW) += snd-soc-rt766-sdca.o + obj-$(CONFIG_SND_SOC_RT9120) += snd-soc-rt9120.o + obj-$(CONFIG_SND_SOC_RT9123) += snd-soc-rt9123.o + obj-$(CONFIG_SND_SOC_RT9123P) += snd-soc-rt9123p.o +@@ -876,6 +881,7 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o + obj-$(CONFIG_SND_SOC_WSA881X) += snd-soc-wsa881x.o + obj-$(CONFIG_SND_SOC_WSA883X) += snd-soc-wsa883x.o + obj-$(CONFIG_SND_SOC_WSA884X) += snd-soc-wsa884x.o ++obj-$(CONFIG_SND_SOC_WSA885X) += snd-soc-wsa885x.o + obj-$(CONFIG_SND_SOC_ZL38060) += snd-soc-zl38060.o + + # Amp +diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c +--- a/sound/soc/codecs/ab8500-codec.c ++++ b/sound/soc/codecs/ab8500-codec.c +@@ -14,6 +14,7 @@ + * for ST-Ericsson. + */ + ++#include + #include + #include + #include +@@ -989,9 +990,8 @@ static int sid_status_control_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); + +- mutex_lock(&drvdata->ctrl_lock); ++ guard(mutex)(&drvdata->ctrl_lock); + ucontrol->value.enumerated.item[0] = drvdata->sid_status; +- mutex_unlock(&drvdata->ctrl_lock); + + return 0; + } +@@ -1014,7 +1014,7 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, + return -EIO; + } + +- mutex_lock(&drvdata->ctrl_lock); ++ guard(mutex)(&drvdata->ctrl_lock); + + sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF); + if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { +@@ -1025,7 +1025,8 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, + } else { + status = -EBUSY; + } +- goto out; ++ dev_dbg(component->dev, "%s: Exit\n", __func__); ++ return status; + } + + snd_soc_component_write(component, AB8500_SIDFIRADR, 0); +@@ -1043,9 +1044,6 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, + + drvdata->sid_status = SID_FIR_CONFIGURED; + +-out: +- mutex_unlock(&drvdata->ctrl_lock); +- + dev_dbg(component->dev, "%s: Exit\n", __func__); + + return status; +diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c +--- a/sound/soc/codecs/ad1836.c ++++ b/sound/soc/codecs/ad1836.c +@@ -193,9 +193,15 @@ static int ad1836_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 ad1836_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops ad1836_dai_ops = { + .hw_params = ad1836_hw_params, + .set_fmt = ad1836_set_dai_fmt, ++ .auto_selectable_formats = &ad1836_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define AD183X_DAI(_name, num_dacs, num_adcs) \ +diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c +--- a/sound/soc/codecs/ad193x.c ++++ b/sound/soc/codecs/ad193x.c +@@ -394,6 +394,14 @@ static int ad193x_startup(struct snd_pcm_substream *substream, + &constr); + } + ++static const u64 ad193x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops ad193x_dai_ops = { + .startup = ad193x_startup, + .hw_params = ad193x_hw_params, +@@ -401,6 +409,8 @@ static const struct snd_soc_dai_ops ad193x_dai_ops = { + .set_tdm_slot = ad193x_set_tdm_slot, + .set_sysclk = ad193x_set_dai_sysclk, + .set_fmt = ad193x_set_dai_fmt, ++ .auto_selectable_formats = &ad193x_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/adau7118-hw.c b/sound/soc/codecs/adau7118-hw.c +--- a/sound/soc/codecs/adau7118-hw.c ++++ b/sound/soc/codecs/adau7118-hw.c +@@ -22,7 +22,7 @@ static const struct of_device_id adau7118_of_match[] = { + MODULE_DEVICE_TABLE(of, adau7118_of_match); + + static const struct platform_device_id adau7118_id[] = { +- { .name = "adau7118" }, ++ { .name = "adau7118" }, + { } + }; + MODULE_DEVICE_TABLE(platform, adau7118_id); +diff --git a/sound/soc/codecs/adav80x.c b/sound/soc/codecs/adav80x.c +--- a/sound/soc/codecs/adav80x.c ++++ b/sound/soc/codecs/adav80x.c +@@ -743,11 +743,19 @@ static void adav80x_dai_shutdown(struct snd_pcm_substream *substream, + adav80x->rate = 0; + } + ++static const u64 adav80x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops adav80x_dai_ops = { + .set_fmt = adav80x_set_dai_fmt, + .hw_params = adav80x_hw_params, + .startup = adav80x_dai_startup, + .shutdown = adav80x_dai_shutdown, ++ .auto_selectable_formats = &adav80x_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define ADAV80X_PLAYBACK_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ +diff --git a/sound/soc/codecs/ak4613.c b/sound/soc/codecs/ak4613.c +--- a/sound/soc/codecs/ak4613.c ++++ b/sound/soc/codecs/ak4613.c +@@ -95,6 +95,7 @@ + * see + * AK4613_ENABLE_TDM_TEST + */ ++#include + #include + #include + #include +@@ -384,7 +385,7 @@ static void ak4613_dai_shutdown(struct snd_pcm_substream *substream, + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + struct device *dev = component->dev; + +- mutex_lock(&priv->lock); ++ guard(mutex)(&priv->lock); + priv->cnt--; + if (priv->cnt < 0) { + dev_err(dev, "unexpected counter error\n"); +@@ -392,7 +393,6 @@ static void ak4613_dai_shutdown(struct snd_pcm_substream *substream, + } + if (!priv->cnt) + priv->ctrl1 = 0; +- mutex_unlock(&priv->lock); + } + + static void ak4613_hw_constraints(struct ak4613_priv *priv, +@@ -507,10 +507,9 @@ static int ak4613_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->lock); ++ guard(mutex)(&priv->lock); + ak4613_hw_constraints(priv, substream); + priv->cnt++; +- mutex_unlock(&priv->lock); + + return 0; + } +@@ -599,45 +598,47 @@ static int ak4613_dai_hw_params(struct snd_pcm_substream *substream, + */ + ret = -EINVAL; + +- mutex_lock(&priv->lock); +- if (priv->cnt > 1) { +- /* +- * If it was already working, use current priv->ctrl1 +- */ +- ret = 0; +- } else { +- /* +- * It is not yet working, +- */ +- unsigned int channel = params_channels(params); +- u8 tdm; ++ scoped_guard(mutex, &priv->lock) { ++ if (priv->cnt > 1) { ++ /* ++ * If it was already working, use current priv->ctrl1 ++ */ ++ ret = 0; ++ } else { ++ /* ++ * It is not yet working, ++ */ ++ unsigned int channel = params_channels(params); ++ u8 tdm; + +- /* STEREO or TDM */ +- if (channel == 2) +- tdm = AK4613_CONFIG_MODE_STEREO; +- else +- tdm = AK4613_CONFIG_GET(priv, MODE); ++ /* STEREO or TDM */ ++ if (channel == 2) ++ tdm = AK4613_CONFIG_MODE_STEREO; ++ else ++ tdm = AK4613_CONFIG_GET(priv, MODE); + +- for (i = ARRAY_SIZE(ak4613_iface) - 1; i >= 0; i--) { +- const struct ak4613_interface *iface = ak4613_iface + i; ++ for (i = ARRAY_SIZE(ak4613_iface) - 1; i >= 0; i--) { ++ const struct ak4613_interface *iface = ak4613_iface + i; + +- if ((iface->fmt == fmt) && (iface->width == width)) { +- /* +- * Ctrl1 +- * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | +- * |TDM1|TDM0|DIF2|DIF1|DIF0|ATS1|ATS0|SMUTE| +- * < tdm > < iface->dif > +- */ +- priv->ctrl1 = (tdm << 6) | (iface->dif << 3); +- ret = 0; +- break; ++ if (iface->fmt == fmt && iface->width == width) { ++ /* ++ * Ctrl1 ++ * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | ++ * |TDM1|TDM0|DIF2|DIF1|DIF0|ATS1|ATS0|SMUTE| ++ * < tdm > < iface->dif > ++ */ ++ priv->ctrl1 = (tdm << 6) | (iface->dif << 3); ++ ret = 0; ++ break; ++ } + } + } + } +- mutex_unlock(&priv->lock); + +- if (ret < 0) +- goto hw_params_end; ++ if (ret < 0) { ++ dev_warn(dev, "unsupported data width/format combination\n"); ++ return ret; ++ } + + snd_soc_component_update_bits(component, CTRL1, FMT_MASK, priv->ctrl1); + snd_soc_component_update_bits(component, CTRL2, DFS_MASK, ctrl2); +@@ -645,10 +646,6 @@ static int ak4613_dai_hw_params(struct snd_pcm_substream *substream, + snd_soc_component_update_bits(component, ICTRL, ICTRL_MASK, priv->ic); + snd_soc_component_update_bits(component, OCTRL, OCTRL_MASK, priv->oc); + +-hw_params_end: +- if (ret < 0) +- dev_warn(dev, "unsupported data width/format combination\n"); +- + return ret; + } + +diff --git a/sound/soc/codecs/arizona-jack.c b/sound/soc/codecs/arizona-jack.c +--- a/sound/soc/codecs/arizona-jack.c ++++ b/sound/soc/codecs/arizona-jack.c +@@ -5,6 +5,7 @@ + * Copyright (C) 2012-2014 Wolfson Microelectronics plc + */ + ++#include + #include + #include + #include +@@ -707,15 +708,13 @@ static void arizona_micd_timeout_work(struct work_struct *work) + struct arizona_priv, + micd_timeout_work.work); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + + dev_dbg(info->arizona->dev, "MICD timed out, reporting HP\n"); + + info->detecting = false; + + arizona_identify_headphone(info); +- +- mutex_unlock(&info->lock); + } + + static int arizona_micd_adc_read(struct arizona_priv *info) +@@ -921,12 +920,11 @@ static void arizona_micd_detect(struct work_struct *work) + + cancel_delayed_work_sync(&info->micd_timeout_work); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + + /* If the cable was removed while measuring ignore the result */ + if (!(info->jack->status & SND_JACK_MECHANICAL)) { + dev_dbg(arizona->dev, "Ignoring MICDET for removed cable\n"); +- mutex_unlock(&info->lock); + return; + } + +@@ -936,7 +934,6 @@ static void arizona_micd_detect(struct work_struct *work) + arizona_button_reading(info); + + pm_runtime_mark_last_busy(arizona->dev); +- mutex_unlock(&info->lock); + } + + static irqreturn_t arizona_micdet(int irq, void *data) +@@ -948,10 +945,10 @@ static irqreturn_t arizona_micdet(int irq, void *data) + cancel_delayed_work_sync(&info->micd_detect_work); + cancel_delayed_work_sync(&info->micd_timeout_work); + +- mutex_lock(&info->lock); +- if (!info->detecting) +- debounce = 0; +- mutex_unlock(&info->lock); ++ scoped_guard(mutex, &info->lock) { ++ if (!info->detecting) ++ debounce = 0; ++ } + + if (debounce) + queue_delayed_work(system_power_efficient_wq, +@@ -969,9 +966,8 @@ static void arizona_hpdet_work(struct work_struct *work) + struct arizona_priv, + hpdet_work.work); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + arizona_start_hpdet_acc_id(info); +- mutex_unlock(&info->lock); + } + + static int arizona_hpdet_wait(struct arizona_priv *info) +@@ -1018,9 +1014,9 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + cancelled_hp = cancel_delayed_work_sync(&info->hpdet_work); + cancelled_mic = cancel_delayed_work_sync(&info->micd_timeout_work); + +- pm_runtime_get_sync(arizona->dev); ++ guard(pm_runtime_active_auto)(arizona->dev); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + + if (info->micd_clamp) { + mask = ARIZONA_MICD_CLAMP_STS; +@@ -1036,8 +1032,6 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + ret = regmap_read(arizona->regmap, ARIZONA_AOD_IRQ_RAW_STATUS, &val); + if (ret) { + dev_err(arizona->dev, "Failed to read jackdet status: %d\n", ret); +- mutex_unlock(&info->lock); +- pm_runtime_put_autosuspend(arizona->dev); + return IRQ_NONE; + } + +@@ -1056,62 +1050,61 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + &info->micd_timeout_work, + msecs_to_jiffies(micd_timeout)); + } ++ } else { ++ info->last_jackdet = val; + +- goto out; +- } +- info->last_jackdet = val; ++ if (info->last_jackdet == present) { ++ dev_dbg(arizona->dev, "Detected jack\n"); ++ snd_soc_jack_report(info->jack, SND_JACK_MECHANICAL, SND_JACK_MECHANICAL); + +- if (info->last_jackdet == present) { +- dev_dbg(arizona->dev, "Detected jack\n"); +- snd_soc_jack_report(info->jack, SND_JACK_MECHANICAL, SND_JACK_MECHANICAL); ++ info->detecting = true; ++ info->mic = false; ++ info->jack_flips = 0; + +- info->detecting = true; +- info->mic = false; +- info->jack_flips = 0; ++ if (!arizona->pdata.hpdet_acc_id) { ++ arizona_start_mic(info); ++ } else { ++ queue_delayed_work(system_power_efficient_wq, ++ &info->hpdet_work, ++ msecs_to_jiffies(HPDET_DEBOUNCE)); ++ } + +- if (!arizona->pdata.hpdet_acc_id) { +- arizona_start_mic(info); ++ if (info->micd_clamp || !arizona->pdata.jd_invert) ++ regmap_update_bits(arizona->regmap, ++ ARIZONA_JACK_DETECT_DEBOUNCE, ++ ARIZONA_MICD_CLAMP_DB | ++ ARIZONA_JD1_DB, 0); + } else { +- queue_delayed_work(system_power_efficient_wq, +- &info->hpdet_work, +- msecs_to_jiffies(HPDET_DEBOUNCE)); +- } ++ dev_dbg(arizona->dev, "Detected jack removal\n"); ++ ++ arizona_stop_mic(info); ++ ++ info->num_hpdet_res = 0; ++ for (i = 0; i < ARRAY_SIZE(info->hpdet_res); i++) ++ info->hpdet_res[i] = 0; ++ info->mic = false; ++ info->hpdet_done = false; ++ info->hpdet_retried = false; ++ ++ snd_soc_jack_report(info->jack, 0, ++ ARIZONA_JACK_MASK | info->micd_button_mask); ++ ++ /* ++ * If the jack was removed during a headphone detection we ++ * need to wait for the headphone detection to finish, as ++ * it can not be aborted. We don't want to be able to start ++ * a new headphone detection from a fresh insert until this ++ * one is finished. ++ */ ++ arizona_hpdet_wait(info); + +- if (info->micd_clamp || !arizona->pdata.jd_invert) + regmap_update_bits(arizona->regmap, + ARIZONA_JACK_DETECT_DEBOUNCE, +- ARIZONA_MICD_CLAMP_DB | +- ARIZONA_JD1_DB, 0); +- } else { +- dev_dbg(arizona->dev, "Detected jack removal\n"); +- +- arizona_stop_mic(info); +- +- info->num_hpdet_res = 0; +- for (i = 0; i < ARRAY_SIZE(info->hpdet_res); i++) +- info->hpdet_res[i] = 0; +- info->mic = false; +- info->hpdet_done = false; +- info->hpdet_retried = false; +- +- snd_soc_jack_report(info->jack, 0, ARIZONA_JACK_MASK | info->micd_button_mask); +- +- /* +- * If the jack was removed during a headphone detection we +- * need to wait for the headphone detection to finish, as +- * it can not be aborted. We don't want to be able to start +- * a new headphone detection from a fresh insert until this +- * one is finished. +- */ +- arizona_hpdet_wait(info); +- +- regmap_update_bits(arizona->regmap, +- ARIZONA_JACK_DETECT_DEBOUNCE, +- ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB, +- ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB); ++ ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB, ++ ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB); ++ } + } + +-out: + /* Clear trig_sts to make sure DCVDD is not forced up */ + regmap_write(arizona->regmap, ARIZONA_AOD_WKUP_AND_TRIG, + ARIZONA_MICD_CLAMP_FALL_TRIG_STS | +@@ -1119,10 +1112,6 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + ARIZONA_JD1_FALL_TRIG_STS | + ARIZONA_JD1_RISE_TRIG_STS); + +- mutex_unlock(&info->lock); +- +- pm_runtime_put_autosuspend(arizona->dev); +- + return IRQ_HANDLED; + } + +diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c +--- a/sound/soc/codecs/arizona.c ++++ b/sound/soc/codecs/arizona.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -1158,17 +1159,16 @@ int arizona_dvfs_up(struct snd_soc_component *component, unsigned int flags) + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&priv->dvfs_lock); ++ guard(mutex)(&priv->dvfs_lock); + + if (!priv->dvfs_cached && !priv->dvfs_reqs) { + ret = arizona_dvfs_enable(component); + if (ret) +- goto err; ++ return ret; + } + + priv->dvfs_reqs |= flags; +-err: +- mutex_unlock(&priv->dvfs_lock); ++ + return ret; + } + EXPORT_SYMBOL_GPL(arizona_dvfs_up); +@@ -1179,7 +1179,7 @@ int arizona_dvfs_down(struct snd_soc_component *component, unsigned int flags) + unsigned int old_reqs; + int ret = 0; + +- mutex_lock(&priv->dvfs_lock); ++ guard(mutex)(&priv->dvfs_lock); + + old_reqs = priv->dvfs_reqs; + priv->dvfs_reqs &= ~flags; +@@ -1187,7 +1187,6 @@ int arizona_dvfs_down(struct snd_soc_component *component, unsigned int flags) + if (!priv->dvfs_cached && old_reqs && !priv->dvfs_reqs) + ret = arizona_dvfs_disable(component); + +- mutex_unlock(&priv->dvfs_lock); + return ret; + } + EXPORT_SYMBOL_GPL(arizona_dvfs_down); +@@ -1199,7 +1198,7 @@ int arizona_dvfs_sysclk_ev(struct snd_soc_dapm_widget *w, + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&priv->dvfs_lock); ++ guard(mutex)(&priv->dvfs_lock); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +@@ -1222,7 +1221,6 @@ int arizona_dvfs_sysclk_ev(struct snd_soc_dapm_widget *w, + break; + } + +- mutex_unlock(&priv->dvfs_lock); + return ret; + } + EXPORT_SYMBOL_GPL(arizona_dvfs_sysclk_ev); +@@ -1657,13 +1655,11 @@ static void arizona_wm5102_set_dac_comp(struct snd_soc_component *component, + { 0x80, 0x0 }, + }; + +- mutex_lock(&arizona->dac_comp_lock); +- +- dac_comp[1].def = arizona->dac_comp_coeff; +- if (rate >= 176400) +- dac_comp[2].def = arizona->dac_comp_enabled; +- +- mutex_unlock(&arizona->dac_comp_lock); ++ scoped_guard(mutex, &arizona->dac_comp_lock) { ++ dac_comp[1].def = arizona->dac_comp_coeff; ++ if (rate >= 176400) ++ dac_comp[2].def = arizona->dac_comp_enabled; ++ } + + regmap_multi_reg_write(arizona->regmap, + dac_comp, +diff --git a/sound/soc/codecs/aw87390.c b/sound/soc/codecs/aw87390.c +--- a/sound/soc/codecs/aw87390.c ++++ b/sound/soc/codecs/aw87390.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -225,11 +226,10 @@ static int aw87390_profile_set(struct snd_kcontrol *kcontrol, + struct aw87390 *aw87390 = snd_soc_component_get_drvdata(codec); + int ret; + +- mutex_lock(&aw87390->lock); ++ guard(mutex)(&aw87390->lock); + ret = aw87390_dev_set_profile_index(aw87390->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change\n"); +- mutex_unlock(&aw87390->lock); + return 0; + } + +@@ -238,8 +238,6 @@ static int aw87390_profile_set(struct snd_kcontrol *kcontrol, + aw87390_power_on(aw87390->aw_pa); + } + +- mutex_unlock(&aw87390->lock); +- + return 1; + } + +@@ -250,7 +248,7 @@ static const struct snd_kcontrol_new aw87390_controls[] = { + + static int aw87390_request_firmware_file(struct aw87390 *aw87390) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + int ret; + + aw87390->aw_pa->fw_status = AW87390_DEV_FW_FAILED; +@@ -265,14 +263,11 @@ static int aw87390_request_firmware_file(struct aw87390 *aw87390) + + aw87390->aw_cfg = devm_kzalloc(aw87390->aw_pa->dev, + struct_size(aw87390->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw87390->aw_cfg) { +- release_firmware(cont); ++ if (!aw87390->aw_cfg) + return -ENOMEM; +- } + + aw87390->aw_cfg->len = cont->size; + memcpy(aw87390->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + ret = aw88395_dev_load_acf_check(aw87390->aw_pa, aw87390->aw_cfg); + if (ret) { +@@ -280,14 +275,12 @@ static int aw87390_request_firmware_file(struct aw87390 *aw87390) + return ret; + } + +- mutex_lock(&aw87390->lock); ++ guard(mutex)(&aw87390->lock); + + ret = aw88395_dev_cfg_load(aw87390->aw_pa, aw87390->aw_cfg); + if (ret) + dev_err(aw87390->aw_pa->dev, "aw_dev acf parse failed\n"); + +- mutex_unlock(&aw87390->lock); +- + return ret; + } + +diff --git a/sound/soc/codecs/aw88081.c b/sound/soc/codecs/aw88081.c +--- a/sound/soc/codecs/aw88081.c ++++ b/sound/soc/codecs/aw88081.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -763,9 +764,8 @@ static void aw88081_startup_work(struct work_struct *work) + struct aw88081 *aw88081 = + container_of(work, struct aw88081, start_work.work); + +- mutex_lock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + aw88081_start_pa(aw88081); +- mutex_unlock(&aw88081->lock); + } + + static void aw88081_start(struct aw88081 *aw88081, bool sync_start) +@@ -942,11 +942,10 @@ static int aw88081_profile_set(struct snd_kcontrol *kcontrol, + int ret; + + /* pa stop or stopping just set profile */ +- mutex_lock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + ret = aw88081_dev_set_profile_index(aw88081->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88081->lock); + return 0; + } + +@@ -955,8 +954,6 @@ static int aw88081_profile_set(struct snd_kcontrol *kcontrol, + aw88081_start(aw88081, AW88081_SYNC_START); + } + +- mutex_unlock(&aw88081->lock); +- + return 1; + } + +@@ -1136,7 +1133,7 @@ static int aw88081_dev_init(struct aw88081 *aw88081, struct aw_container *aw_cfg + + static int aw88081_request_firmware_file(struct aw88081 *aw88081) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + int ret; + +@@ -1150,26 +1147,21 @@ static int aw88081_request_firmware_file(struct aw88081 *aw88081) + AW88081_ACF_FILE, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88081->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw_cfg) { +- release_firmware(cont); ++ if (!aw_cfg) + return -ENOMEM; +- } ++ + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); + + aw88081->aw_cfg = aw_cfg; + +- release_firmware(cont); +- + ret = aw88395_dev_load_acf_check(aw88081->aw_pa, aw88081->aw_cfg); + if (ret) + return ret; + +- mutex_lock(&aw88081->lock); +- ret = aw88081_dev_init(aw88081, aw88081->aw_cfg); +- mutex_unlock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + +- return ret; ++ return aw88081_dev_init(aw88081, aw88081->aw_cfg); + } + + static int aw88081_playback_event(struct snd_soc_dapm_widget *w, +@@ -1178,7 +1170,7 @@ static int aw88081_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88081 *aw88081 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88081_start(aw88081, AW88081_ASYNC_START); +@@ -1189,7 +1181,6 @@ static int aw88081_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88081->lock); + + return 0; + } +diff --git a/sound/soc/codecs/aw88166.c b/sound/soc/codecs/aw88166.c +--- a/sound/soc/codecs/aw88166.c ++++ b/sound/soc/codecs/aw88166.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -1173,9 +1174,8 @@ static void aw88166_startup_work(struct work_struct *work) + struct aw88166 *aw88166 = + container_of(work, struct aw88166, start_work.work); + +- mutex_lock(&aw88166->lock); ++ guard(mutex)(&aw88166->lock); + aw88166_start_pa(aw88166); +- mutex_unlock(&aw88166->lock); + } + + static void aw88166_start(struct aw88166 *aw88166, bool sync_start) +@@ -1413,11 +1413,10 @@ static int aw88166_profile_set(struct snd_kcontrol *kcontrol, + struct aw88166 *aw88166 = snd_soc_component_get_drvdata(codec); + int ret; + +- mutex_lock(&aw88166->lock); ++ guard(mutex)(&aw88166->lock); + ret = aw88166_dev_set_profile_index(aw88166->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88166->lock); + return 0; + } + +@@ -1426,8 +1425,6 @@ static int aw88166_profile_set(struct snd_kcontrol *kcontrol, + aw88166_start(aw88166, AW88166_SYNC_START); + } + +- mutex_unlock(&aw88166->lock); +- + return 1; + } + +@@ -1573,7 +1570,7 @@ static int aw88166_dev_init(struct aw88166 *aw88166, struct aw_container *aw_cfg + + static int aw88166_request_firmware_file(struct aw88166 *aw88166) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + const char *fw_name; + int ret; + +@@ -1593,13 +1590,11 @@ static int aw88166_request_firmware_file(struct aw88166 *aw88166) + + aw88166->aw_cfg = devm_kzalloc(aw88166->aw_pa->dev, + struct_size(aw88166->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw88166->aw_cfg) { +- release_firmware(cont); ++ if (!aw88166->aw_cfg) + return -ENOMEM; +- } ++ + aw88166->aw_cfg->len = (int)cont->size; + memcpy(aw88166->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + ret = aw88395_dev_load_acf_check(aw88166->aw_pa, aw88166->aw_cfg); + if (ret) { +@@ -1607,12 +1602,12 @@ static int aw88166_request_firmware_file(struct aw88166 *aw88166) + return ret; + } + +- mutex_lock(&aw88166->lock); +- /* aw device init */ +- ret = aw88166_dev_init(aw88166, aw88166->aw_cfg); +- if (ret) +- dev_err(aw88166->aw_pa->dev, "dev init failed\n"); +- mutex_unlock(&aw88166->lock); ++ scoped_guard(mutex, &aw88166->lock) { ++ /* aw device init */ ++ ret = aw88166_dev_init(aw88166, aw88166->aw_cfg); ++ if (ret) ++ dev_err(aw88166->aw_pa->dev, "dev init failed\n"); ++ } + + return ret; + } +@@ -1639,7 +1634,7 @@ static int aw88166_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88166 *aw88166 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88166->lock); ++ guard(mutex)(&aw88166->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88166_start(aw88166, AW88166_ASYNC_START); +@@ -1650,7 +1645,6 @@ static int aw88166_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88166->lock); + + return 0; + } +diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c +--- a/sound/soc/codecs/aw88261.c ++++ b/sound/soc/codecs/aw88261.c +@@ -8,6 +8,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -960,11 +961,10 @@ static int aw88261_profile_set(struct snd_kcontrol *kcontrol, + int ret; + + /* pa stop or stopping just set profile */ +- mutex_lock(&aw88261->lock); ++ guard(mutex)(&aw88261->lock); + ret = aw88261_dev_set_profile_index(aw88261->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88261->lock); + return 0; + } + +@@ -973,8 +973,6 @@ static int aw88261_profile_set(struct snd_kcontrol *kcontrol, + aw88261_start(aw88261); + } + +- mutex_unlock(&aw88261->lock); +- + return 1; + } + +@@ -1038,7 +1036,7 @@ static int aw88261_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88261->lock); ++ guard(mutex)(&aw88261->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88261_start(aw88261); +@@ -1049,7 +1047,6 @@ static int aw88261_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88261->lock); + + return 0; + } +@@ -1152,7 +1149,7 @@ static int aw88261_dev_init(struct aw88261 *aw88261, struct aw_container *aw_cfg + + static int aw88261_request_firmware_file(struct aw88261 *aw88261) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + const char *fw_name; + int ret; +@@ -1172,13 +1169,11 @@ static int aw88261_request_firmware_file(struct aw88261 *aw88261) + fw_name, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88261->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw_cfg) { +- release_firmware(cont); ++ if (!aw_cfg) + return -ENOMEM; +- } ++ + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + aw88261->aw_cfg = aw_cfg; + +@@ -1188,12 +1183,12 @@ static int aw88261_request_firmware_file(struct aw88261 *aw88261) + return ret; + } + +- mutex_lock(&aw88261->lock); +- /* aw device init */ +- ret = aw88261_dev_init(aw88261, aw88261->aw_cfg); +- if (ret) +- dev_err(aw88261->aw_pa->dev, "dev init failed"); +- mutex_unlock(&aw88261->lock); ++ scoped_guard(mutex, &aw88261->lock) { ++ /* aw device init */ ++ ret = aw88261_dev_init(aw88261, aw88261->aw_cfg); ++ if (ret) ++ dev_err(aw88261->aw_pa->dev, "dev init failed"); ++ } + + return ret; + } +diff --git a/sound/soc/codecs/aw88395/aw88395.c b/sound/soc/codecs/aw88395/aw88395.c +--- a/sound/soc/codecs/aw88395/aw88395.c ++++ b/sound/soc/codecs/aw88395/aw88395.c +@@ -8,6 +8,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -51,9 +52,8 @@ static void aw88395_startup_work(struct work_struct *work) + struct aw88395 *aw88395 = + container_of(work, struct aw88395, start_work.work); + +- mutex_lock(&aw88395->lock); ++ guard(mutex)(&aw88395->lock); + aw88395_start_pa(aw88395); +- mutex_unlock(&aw88395->lock); + } + + static void aw88395_start(struct aw88395 *aw88395, bool sync_start) +@@ -224,11 +224,10 @@ static int aw88395_profile_set(struct snd_kcontrol *kcontrol, + int ret; + + /* pa stop or stopping just set profile */ +- mutex_lock(&aw88395->lock); ++ guard(mutex)(&aw88395->lock); + ret = aw88395_dev_set_profile_index(aw88395->aw_pa, ucontrol->value.integer.value[0]); + if (ret < 0) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88395->lock); + return 0; + } + +@@ -237,8 +236,6 @@ static int aw88395_profile_set(struct snd_kcontrol *kcontrol, + aw88395_start(aw88395, AW88395_SYNC_START); + } + +- mutex_unlock(&aw88395->lock); +- + return 1; + } + +@@ -366,7 +363,7 @@ static int aw88395_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88395 *aw88395 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88395->lock); ++ guard(mutex)(&aw88395->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88395_start(aw88395, AW88395_ASYNC_START); +@@ -377,7 +374,6 @@ static int aw88395_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88395->lock); + + return 0; + } +@@ -461,7 +457,7 @@ static void aw88395_hw_reset(struct aw88395 *aw88395) + + static int aw88395_request_firmware_file(struct aw88395 *aw88395) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + int ret; + +@@ -477,13 +473,11 @@ static int aw88395_request_firmware_file(struct aw88395 *aw88395) + AW88395_ACF_FILE, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88395->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw_cfg) { +- release_firmware(cont); ++ if (!aw_cfg) + return -ENOMEM; +- } ++ + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + aw88395->aw_cfg = aw_cfg; + +@@ -495,12 +489,12 @@ static int aw88395_request_firmware_file(struct aw88395 *aw88395) + + dev_dbg(aw88395->aw_pa->dev, "%s : bin load success\n", __func__); + +- mutex_lock(&aw88395->lock); +- /* aw device init */ +- ret = aw88395_dev_init(aw88395->aw_pa, aw88395->aw_cfg); +- if (ret < 0) +- dev_err(aw88395->aw_pa->dev, "dev init failed"); +- mutex_unlock(&aw88395->lock); ++ scoped_guard(mutex, &aw88395->lock) { ++ /* aw device init */ ++ ret = aw88395_dev_init(aw88395->aw_pa, aw88395->aw_cfg); ++ if (ret < 0) ++ dev_err(aw88395->aw_pa->dev, "dev init failed"); ++ } + + return ret; + } +diff --git a/sound/soc/codecs/aw88395/aw88395_device.c b/sound/soc/codecs/aw88395/aw88395_device.c +--- a/sound/soc/codecs/aw88395/aw88395_device.c ++++ b/sound/soc/codecs/aw88395/aw88395_device.c +@@ -8,6 +8,7 @@ + // Author: Ben Yi + // + ++#include + #include + #include + #include +@@ -70,7 +71,7 @@ int aw_dev_dsp_write(struct aw_device *aw_dev, + u32 reg_value; + int ret; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + switch (data_type) { + case AW_DSP_16_DATA: + ret = aw_dev_dsp_write_16bit(aw_dev, dsp_addr, dsp_data); +@@ -93,7 +94,6 @@ int aw_dev_dsp_write(struct aw_device *aw_dev, + /* clear dsp chip select state*/ + if (regmap_read(aw_dev->regmap, AW88395_ID_REG, ®_value)) + dev_err(aw_dev->dev, "%s fail to clear chip state. Err=%d\n", __func__, ret); +- mutex_unlock(&aw_dev->dsp_lock); + + return ret; + } +@@ -156,7 +156,7 @@ int aw_dev_dsp_read(struct aw_device *aw_dev, + u32 reg_value; + int ret; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + switch (data_type) { + case AW_DSP_16_DATA: + ret = aw_dev_dsp_read_16bit(aw_dev, dsp_addr, dsp_data); +@@ -179,7 +179,6 @@ int aw_dev_dsp_read(struct aw_device *aw_dev, + /* clear dsp chip select state*/ + if (regmap_read(aw_dev->regmap, AW88395_ID_REG, ®_value)) + dev_err(aw_dev->dev, "%s fail to clear chip state. Err=%d\n", __func__, ret); +- mutex_unlock(&aw_dev->dsp_lock); + + return ret; + } +@@ -1110,42 +1109,36 @@ static int aw_dev_dsp_update_container(struct aw_device *aw_dev, + #ifdef AW88395_DSP_I2C_WRITES + u32 tmp_len; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + ret = regmap_write(aw_dev->regmap, AW88395_DSPMADD_REG, base); + if (ret) +- goto error_operation; ++ return ret; + + for (i = 0; i < len; i += AW88395_MAX_RAM_WRITE_BYTE_SIZE) { + tmp_len = min(len - i, AW88395_MAX_RAM_WRITE_BYTE_SIZE); + ret = regmap_raw_write(aw_dev->regmap, AW88395_DSPMDAT_REG, + &data[i], tmp_len); + if (ret) +- goto error_operation; ++ return ret; + } +- mutex_unlock(&aw_dev->dsp_lock); + #else + __be16 reg_val; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + /* i2c write */ + ret = regmap_write(aw_dev->regmap, AW88395_DSPMADD_REG, base); + if (ret) +- goto error_operation; ++ return ret; + for (i = 0; i < len; i += 2) { + reg_val = cpu_to_be16p((u16 *)(data + i)); + ret = regmap_write(aw_dev->regmap, AW88395_DSPMDAT_REG, + (u16)reg_val); + if (ret) +- goto error_operation; ++ return ret; + } +- mutex_unlock(&aw_dev->dsp_lock); + #endif + + return 0; +- +-error_operation: +- mutex_unlock(&aw_dev->dsp_lock); +- return ret; + } + + static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, +@@ -1231,14 +1224,14 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + { + unsigned int reg_val; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + /* check the odd bits of reg 0x40 */ + regmap_write(aw_dev->regmap, AW88395_DSPMADD_REG, AW88395_DSP_ODD_NUM_BIT_TEST); + regmap_read(aw_dev->regmap, AW88395_DSPMADD_REG, ®_val); + if (reg_val != AW88395_DSP_ODD_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check reg 0x40 odd bit failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_ODD_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } + + /* check the even bits of reg 0x40 */ +@@ -1247,7 +1240,7 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + if (reg_val != AW88395_DSP_EVEN_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check reg 0x40 even bit failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_EVEN_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } + + /* check dsp_fw_base_addr */ +@@ -1256,7 +1249,7 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + if (reg_val != AW88395_DSP_EVEN_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check dsp fw addr failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_EVEN_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } + + /* check dsp_cfg_base_addr */ +@@ -1265,15 +1258,10 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + if (reg_val != AW88395_DSP_ODD_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_ODD_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } +- mutex_unlock(&aw_dev->dsp_lock); + + return 0; +- +-error: +- mutex_unlock(&aw_dev->dsp_lock); +- return -EPERM; + } + + int aw88395_dev_fw_update(struct aw_device *aw_dev, bool up_dsp_fw_en, bool force_up_en) +diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/aw88399-lib.c +@@ -0,0 +1,1409 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// aw88399-lib.c -- AW88399 Common functions for ASoC and HDA audio drivers ++// ++// Copyright (c) 2023 AWINIC Technology CO., LTD ++// ++// Author: Weidong Wang ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "aw88395/aw88395_device.h" ++ ++const struct regmap_config aw88399_remap_config = { ++ .val_bits = 16, ++ .reg_bits = 8, ++ .max_register = AW88399_REG_MAX, ++ .reg_format_endian = REGMAP_ENDIAN_LITTLE, ++ .val_format_endian = REGMAP_ENDIAN_BIG, ++}; ++EXPORT_SYMBOL_GPL(aw88399_remap_config); ++ ++static void aw_dev_pwd(struct aw_device *aw_dev, bool pwd) ++{ ++ int ret; ++ ++ if (pwd) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_PWDN_MASK, AW88399_PWDN_POWER_DOWN_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_PWDN_MASK, AW88399_PWDN_WORKING_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++} ++ ++static void aw_dev_get_int_status(struct aw_device *aw_dev, unsigned short *int_status) ++{ ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSINT_REG, ®_val); ++ if (ret) ++ dev_err(aw_dev->dev, "read interrupt reg fail, ret=%d", ret); ++ else ++ *int_status = reg_val; ++ ++ dev_dbg(aw_dev->dev, "read interrupt reg=0x%04x", *int_status); ++} ++ ++static void aw_dev_clear_int_status(struct aw_device *aw_dev) ++{ ++ u16 int_status; ++ ++ /* read int status and clear */ ++ aw_dev_get_int_status(aw_dev, &int_status); ++ /* make sure int status is clear */ ++ aw_dev_get_int_status(aw_dev, &int_status); ++ if (int_status) ++ dev_dbg(aw_dev->dev, "int status(%d) is not cleaned.\n", int_status); ++} ++ ++static int aw_dev_get_iis_status(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); ++ if (ret) ++ return ret; ++ if ((reg_val & AW88399_BIT_PLL_CHECK) != AW88399_BIT_PLL_CHECK) { ++ dev_err(aw_dev->dev, "check pll lock fail, reg_val:0x%04x", reg_val); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int aw_dev_check_mode1_pll(struct aw_device *aw_dev) ++{ ++ int ret, i; ++ ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = aw_dev_get_iis_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode1 iis signal check error"); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ return 0; ++ } ++ } ++ ++ return -EPERM; ++} ++ ++static int aw_dev_check_mode2_pll(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ int ret, i; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_PLLCTRL2_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ reg_val &= (~AW88399_CCO_MUX_MASK); ++ if (reg_val == AW88399_CCO_MUX_DIVIDED_VALUE) { ++ dev_dbg(aw_dev->dev, "CCO_MUX is already divider"); ++ return -EPERM; ++ } ++ ++ /* change mode2 */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, ++ ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_DIVIDED_VALUE); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = aw_dev_get_iis_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode2 iis signal check error"); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ break; ++ } ++ } ++ ++ /* change mode1 */ ++ regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, ++ ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_BYPASS_VALUE); ++ if (ret == 0) { ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = aw_dev_get_iis_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode2 switch to mode1, iis signal check error"); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ break; ++ } ++ } ++ } ++ ++ return ret; ++} ++ ++int aw_dev_check_syspll(struct aw_device *aw_dev) ++{ ++ int ret; ++ ++ ret = aw_dev_check_mode1_pll(aw_dev); ++ if (ret) { ++ dev_dbg(aw_dev->dev, "mode1 check iis failed try switch to mode2 check"); ++ ret = aw_dev_check_mode2_pll(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode2 check iis failed"); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw_dev_check_syspll); ++ ++static int aw_dev_check_sysst(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int check_val; ++ unsigned int reg_val; ++ int ret, i; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_PWMCTRL3_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ if (reg_val & (~AW88399_NOISE_GATE_EN_MASK)) ++ check_val = AW88399_BIT_SYSST_NOSWS_CHECK; ++ else ++ check_val = AW88399_BIT_SYSST_SWS_CHECK; ++ ++ /* ++ * On some hardware the BSTS (boost-finished) status bit does not ++ * reliably assert even when audio output is working normally. ++ * Allow per-instance bypass when flagged by the side-codec driver. ++ */ ++ if (aw88399->bsts_unreliable) ++ check_val &= ~AW88399_BSTS_FINISHED_VALUE; ++ ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ if ((reg_val & (~AW88399_BIT_SYSST_CHECK_MASK) & check_val) != check_val) { ++ dev_err(aw_dev->dev, "check sysst fail, cnt=%d, reg_val=0x%04x, check:0x%x", ++ i, reg_val, AW88399_BIT_SYSST_NOSWS_CHECK); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ return 0; ++ } ++ } ++ ++ return -EPERM; ++} ++ ++static void aw_dev_amppd(struct aw_device *aw_dev, bool amppd) ++{ ++ int ret; ++ ++ if (amppd) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_AMPPD_MASK, AW88399_AMPPD_POWER_DOWN_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_AMPPD_MASK, AW88399_AMPPD_WORKING_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++} ++ ++void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable) ++{ ++ int ret; ++ ++ if (is_enable) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_DSPBY_MASK, AW88399_DSPBY_WORKING_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_DSPBY_MASK, AW88399_DSPBY_BYPASS_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed\n", __func__); ++} ++EXPORT_SYMBOL_GPL(aw_dev_dsp_enable); ++ ++static int aw88399_dev_get_icalk(struct aw88399 *aw88399, int16_t *icalk) ++{ ++ uint16_t icalkh_val, icalkl_val, icalk_val; ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRH4_REG, ®_val); ++ if (ret) ++ return ret; ++ icalkh_val = reg_val & (~AW88399_EF_ISN_GESLP_H_MASK); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRL4_REG, ®_val); ++ if (ret) ++ return ret; ++ icalkl_val = reg_val & (~AW88399_EF_ISN_GESLP_L_MASK); ++ ++ if (aw88399->check_val == AW_EF_AND_CHECK) ++ icalk_val = icalkh_val & icalkl_val; ++ else ++ icalk_val = icalkh_val | icalkl_val; ++ ++ if (icalk_val & (~AW88399_EF_ISN_GESLP_SIGN_MASK)) ++ icalk_val = icalk_val | AW88399_EF_ISN_GESLP_SIGN_NEG; ++ *icalk = (int16_t)icalk_val; ++ ++ return 0; ++} ++ ++static int aw88399_dev_get_vcalk(struct aw88399 *aw88399, int16_t *vcalk) ++{ ++ uint16_t vcalkh_val, vcalkl_val, vcalk_val; ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRH3_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ vcalkh_val = reg_val & (~AW88399_EF_VSN_GESLP_H_MASK); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRL3_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ vcalkl_val = reg_val & (~AW88399_EF_VSN_GESLP_L_MASK); ++ ++ if (aw88399->check_val == AW_EF_AND_CHECK) ++ vcalk_val = vcalkh_val & vcalkl_val; ++ else ++ vcalk_val = vcalkh_val | vcalkl_val; ++ ++ if (vcalk_val & AW88399_EF_VSN_GESLP_SIGN_MASK) ++ vcalk_val = vcalk_val | AW88399_EF_VSN_GESLP_SIGN_NEG; ++ *vcalk = (int16_t)vcalk_val; ++ ++ return 0; ++} ++ ++static int aw88399_dev_get_internal_vcalk(struct aw88399 *aw88399, int16_t *vcalk) ++{ ++ uint16_t vcalkh_val, vcalkl_val, vcalk_val; ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRH2_REG, ®_val); ++ if (ret) ++ return ret; ++ vcalkh_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_H_MASK); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRL2_REG, ®_val); ++ if (ret) ++ return ret; ++ vcalkl_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_L_MASK); ++ ++ if (aw88399->check_val == AW_EF_AND_CHECK) ++ vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) & ++ (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); ++ else ++ vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) | ++ (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); ++ ++ if (vcalk_val & (~AW88399_TEM4_SIGN_MASK)) ++ vcalk_val = vcalk_val | AW88399_TEM4_SIGN_NEG; ++ ++ *vcalk = (int16_t)vcalk_val; ++ ++ return 0; ++} ++ ++static int aw_dev_set_vcalb(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int vsense_select, vsense_value; ++ int32_t ical_k, vcal_k, vcalb; ++ int16_t icalk, vcalk; ++ uint16_t reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_VSNCTRL1_REG, &vsense_value); ++ if (ret) ++ return ret; ++ ++ vsense_select = vsense_value & (~AW88399_VDSEL_MASK); ++ ++ ret = aw88399_dev_get_icalk(aw88399, &icalk); ++ if (ret) { ++ dev_err(aw_dev->dev, "get icalk failed\n"); ++ return ret; ++ } ++ ++ ical_k = icalk * AW88399_ICABLK_FACTOR + AW88399_CABL_BASE_VALUE; ++ ++ switch (vsense_select) { ++ case AW88399_DEV_VDSEL_VSENSE: ++ ret = aw88399_dev_get_vcalk(aw88399, &vcalk); ++ vcal_k = vcalk * AW88399_VCABLK_FACTOR + AW88399_CABL_BASE_VALUE; ++ vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_FACTOR / AW88399_ISCAL_FACTOR * ++ ical_k / vcal_k * aw88399->vcalb_init_val; ++ break; ++ case AW88399_DEV_VDSEL_DAC: ++ ret = aw88399_dev_get_internal_vcalk(aw88399, &vcalk); ++ vcal_k = vcalk * AW88399_VCABLK_DAC_FACTOR + AW88399_CABL_BASE_VALUE; ++ vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_DAC_FACTOR / ++ AW88399_ISCAL_DAC_FACTOR * ical_k / ++ vcal_k * aw88399->vcalb_init_val; ++ break; ++ default: ++ dev_err(aw_dev->dev, "%s: unsupported vsense\n", __func__); ++ ret = -EINVAL; ++ break; ++ } ++ if (ret) ++ return ret; ++ ++ vcalb = vcalb >> AW88399_VCALB_ADJ_FACTOR; ++ reg_val = (uint32_t)vcalb; ++ ++ regmap_write(aw_dev->regmap, AW88399_DSPVCALB_REG, reg_val); ++ ++ return 0; ++} ++ ++int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc) ++{ ++ struct aw_device *aw_dev = ++ container_of(cali_desc, struct aw_device, cali_desc); ++ uint16_t re_lbits, re_hbits; ++ u32 cali_re; ++ int ret; ++ ++ if ((aw_dev->cali_desc.cali_re >= AW88399_CALI_RE_MAX) || ++ (aw_dev->cali_desc.cali_re <= AW88399_CALI_RE_MIN)) ++ return -EINVAL; ++ ++ cali_re = AW88399_SHOW_RE_TO_DSP_RE((aw_dev->cali_desc.cali_re + ++ aw_dev->cali_desc.ra), AW88399_DSP_RE_SHIFT); ++ ++ re_hbits = (cali_re & (~AW88399_CALI_RE_HBITS_MASK)) >> AW88399_CALI_RE_HBITS_SHIFT; ++ re_lbits = (cali_re & (~AW88399_CALI_RE_LBITS_MASK)) >> AW88399_CALI_RE_LBITS_SHIFT; ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_ACR1_REG, re_hbits); ++ if (ret) { ++ dev_err(aw_dev->dev, "set cali re error"); ++ return ret; ++ } ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_ACR2_REG, re_lbits); ++ if (ret) ++ dev_err(aw_dev->dev, "set cali re error"); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aw_dev_update_cali_re); ++ ++static int aw_dev_fw_crc_check(struct aw_device *aw_dev) ++{ ++ uint16_t check_val, fw_len_val; ++ unsigned int reg_val; ++ int ret; ++ ++ /* calculate fw_end_addr */ ++ fw_len_val = ((aw_dev->dsp_fw_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_FW_BASE_ADDR; ++ ++ /* write fw_end_addr to crc_end_addr */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_END_ADDR_MASK, fw_len_val); ++ if (ret) ++ return ret; ++ /* enable fw crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_ENABLE_VALUE); ++ ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ ++ /* read crc check result */ ++ regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; ++ ++ /* disable fw crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_DISABLE_VALUE); ++ if (ret) ++ return ret; ++ ++ if (check_val != AW88399_CRC_CHECK_PASS_VAL) { ++ dev_err(aw_dev->dev, "%s failed, check_val 0x%x != 0x%x", ++ __func__, check_val, AW88399_CRC_CHECK_PASS_VAL); ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int aw_dev_cfg_crc_check(struct aw_device *aw_dev) ++{ ++ uint16_t check_val, cfg_len_val; ++ unsigned int reg_val; ++ int ret; ++ ++ /* calculate cfg end addr */ ++ cfg_len_val = ((aw_dev->dsp_cfg_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_CFG_BASE_ADDR; ++ ++ /* write cfg_end_addr to crc_end_addr */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_END_ADDR_MASK, cfg_len_val); ++ if (ret) ++ return ret; ++ ++ /* enable cfg crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_ENABLE_VALUE); ++ if (ret) ++ return ret; ++ ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ ++ /* read crc check result */ ++ ret = regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; ++ ++ /* disable cfg crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_DISABLE_VALUE); ++ if (ret) ++ return ret; ++ ++ if (check_val != AW88399_CRC_CHECK_PASS_VAL) { ++ dev_err(aw_dev->dev, "crc_check failed, check val 0x%x != 0x%x", ++ check_val, AW88399_CRC_CHECK_PASS_VAL); ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int aw_dev_hw_crc_check(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ int ret; ++ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_BYPASS_VALUE); ++ if (ret) ++ return ret; ++ ++ ret = aw_dev_fw_crc_check(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "fw_crc_check failed\n"); ++ goto crc_check_failed; ++ } ++ ++ ret = aw_dev_cfg_crc_check(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "cfg_crc_check failed\n"); ++ goto crc_check_failed; ++ } ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_CRCCTRL_REG, aw88399->crc_init_val); ++ if (ret) ++ return ret; ++ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); ++ ++ return ret; ++ ++crc_check_failed: ++ regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); ++ return ret; ++} ++ ++static void aw_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag) ++{ ++ int ret; ++ ++ if (flag) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCTRL3_REG, ++ ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_ENABLE_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_DISABLE_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++} ++ ++int aw_dev_get_dsp_status(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_WDT_REG, ®_val); ++ if (ret) ++ return ret; ++ if (!(reg_val & (~AW88399_WDT_CNT_MASK))) ++ return -EPERM; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw_dev_get_dsp_status); ++ ++static int aw_dev_dsp_check(struct aw_device *aw_dev) ++{ ++ int ret, i; ++ ++ switch (aw_dev->dsp_cfg) { ++ case AW88399_DEV_DSP_BYPASS: ++ dev_dbg(aw_dev->dev, "dsp bypass"); ++ ret = 0; ++ break; ++ case AW88399_DEV_DSP_WORK: ++ aw_dev_dsp_enable(aw_dev, false); ++ aw_dev_dsp_enable(aw_dev, true); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ for (i = 0; i < AW88399_DEV_DSP_CHECK_MAX; i++) { ++ ret = aw_dev_get_dsp_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "dsp wdt status error=%d", ret); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } ++ } ++ break; ++ default: ++ dev_err(aw_dev->dev, "unknown dsp cfg=%d", aw_dev->dsp_cfg); ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value) ++{ ++ struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; ++ unsigned int reg_value; ++ u16 real_value; ++ int ret; ++ ++ real_value = min((value + vol_desc->init_volume), (unsigned int)AW88399_MUTE_VOL); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL2_REG, ®_value); ++ if (ret) ++ return ret; ++ ++ dev_dbg(aw_dev->dev, "value 0x%x , reg:0x%x", value, real_value); ++ ++ real_value = (real_value << AW88399_VOL_START_BIT) | (reg_value & AW88399_VOL_MASK); ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_SYSCTRL2_REG, real_value); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aw_dev_set_volume); ++ ++static void aw_dev_fade_in(struct aw_device *aw_dev) ++{ ++ struct aw_volume_desc *desc = &aw_dev->volume_desc; ++ u16 fade_in_vol = desc->ctl_volume; ++ int fade_step = aw_dev->fade_step; ++ int i; ++ ++ if (fade_step == 0 || aw_dev->fade_in_time == 0) { ++ aw_dev_set_volume(aw_dev, fade_in_vol); ++ return; ++ } ++ ++ for (i = AW88399_MUTE_VOL; i >= fade_in_vol; i -= fade_step) { ++ aw_dev_set_volume(aw_dev, i); ++ usleep_range(aw_dev->fade_in_time, aw_dev->fade_in_time + 10); ++ } ++ ++ if (i != fade_in_vol) ++ aw_dev_set_volume(aw_dev, fade_in_vol); ++} ++ ++static void aw_dev_fade_out(struct aw_device *aw_dev) ++{ ++ struct aw_volume_desc *desc = &aw_dev->volume_desc; ++ int fade_step = aw_dev->fade_step; ++ int i; ++ ++ if (fade_step == 0 || aw_dev->fade_out_time == 0) { ++ aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); ++ return; ++ } ++ ++ for (i = desc->ctl_volume; i <= AW88399_MUTE_VOL; i += fade_step) { ++ aw_dev_set_volume(aw_dev, i); ++ usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); ++ } ++ ++ if (i != AW88399_MUTE_VOL) { ++ aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); ++ usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); ++ } ++} ++ ++void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute) ++{ ++ if (is_mute) { ++ aw_dev_fade_out(aw_dev); ++ regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_HMUTE_MASK, AW88399_HMUTE_ENABLE_VALUE); ++ } else { ++ regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_HMUTE_MASK, AW88399_HMUTE_DISABLE_VALUE); ++ aw_dev_fade_in(aw_dev); ++ } ++} ++EXPORT_SYMBOL_GPL(aw88399_dev_mute); ++ ++static void aw88399_dev_set_dither(struct aw88399 *aw88399, bool dither) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ ++ if (dither) ++ regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_ENABLE_VALUE); ++ else ++ regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_DISABLE_VALUE); ++} ++ ++static int aw88399_dev_start(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ int ret; ++ ++ if (aw_dev->status == AW88399_DEV_PW_ON) { ++ dev_dbg(aw_dev->dev, "already power on"); ++ return 0; ++ } ++ ++ aw88399_dev_set_dither(aw88399, false); ++ ++ /* power on */ ++ aw_dev_pwd(aw_dev, false); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ ++ ret = aw_dev_check_syspll(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "pll check failed cannot start"); ++ goto pll_check_fail; ++ } ++ ++ /* amppd on */ ++ aw_dev_amppd(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 50); ++ ++ /* check i2s status */ ++ ret = aw_dev_check_sysst(aw88399); ++ if (ret) { ++ dev_err(aw_dev->dev, "sysst check failed"); ++ goto sysst_check_fail; ++ } ++ ++ if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) { ++ ret = aw_dev_hw_crc_check(aw88399); ++ if (ret) { ++ dev_err(aw_dev->dev, "dsp crc check failed"); ++ goto crc_check_fail; ++ } ++ aw_dev_dsp_enable(aw_dev, false); ++ aw_dev_set_vcalb(aw88399); ++ aw_dev_update_cali_re(&aw_dev->cali_desc); ++ ++ ret = aw_dev_dsp_check(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "dsp status check failed"); ++ goto dsp_check_fail; ++ } ++ } else { ++ dev_dbg(aw_dev->dev, "start pa with dsp bypass"); ++ } ++ ++ /* enable tx feedback */ ++ aw_dev_i2s_tx_enable(aw_dev, true); ++ ++ if (aw88399->dither_st == AW88399_DITHER_EN_ENABLE_VALUE) ++ aw88399_dev_set_dither(aw88399, true); ++ ++ /* close mute */ ++ aw88399_dev_mute(aw_dev, false); ++ /* clear inturrupt */ ++ aw_dev_clear_int_status(aw_dev); ++ aw_dev->status = AW88399_DEV_PW_ON; ++ ++ return 0; ++ ++dsp_check_fail: ++crc_check_fail: ++ aw_dev_dsp_enable(aw_dev, false); ++sysst_check_fail: ++ aw_dev_clear_int_status(aw_dev); ++ aw_dev_amppd(aw_dev, true); ++pll_check_fail: ++ aw_dev_pwd(aw_dev, true); ++ aw_dev->status = AW88399_DEV_PW_OFF; ++ ++ return ret; ++} ++ ++static int aw_dev_dsp_update_container(struct aw_device *aw_dev, ++ unsigned char *data, unsigned int len, unsigned short base) ++{ ++ u32 tmp_len; ++ int i, ret; ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_DSPMADD_REG, base); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < len; i += AW88399_MAX_RAM_WRITE_BYTE_SIZE) { ++ tmp_len = min(len - i, AW88399_MAX_RAM_WRITE_BYTE_SIZE); ++ ret = regmap_raw_write(aw_dev->regmap, AW88399_DSPMDAT_REG, ++ &data[i], tmp_len); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int aw_dev_get_ra(struct aw_cali_desc *cali_desc) ++{ ++ struct aw_device *aw_dev = ++ container_of(cali_desc, struct aw_device, cali_desc); ++ u32 dsp_ra; ++ int ret; ++ ++ ret = aw_dev_dsp_read(aw_dev, AW88399_DSP_REG_CFG_ADPZ_RA, ++ &dsp_ra, AW_DSP_32_DATA); ++ if (ret) { ++ dev_err(aw_dev->dev, "read ra error"); ++ return ret; ++ } ++ ++ cali_desc->ra = AW88399_DSP_RE_TO_SHOW_RE(dsp_ra, ++ AW88399_DSP_RE_SHIFT); ++ ++ return 0; ++} ++ ++static int aw_dev_dsp_update_cfg(struct aw_device *aw_dev, ++ unsigned char *data, unsigned int len) ++{ ++ int ret; ++ ++ dev_dbg(aw_dev->dev, "dsp config len:%d", len); ++ ++ if (!len || !data) { ++ dev_err(aw_dev->dev, "dsp config data is null or len is 0"); ++ return -EINVAL; ++ } ++ ++ ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_CFG_ADDR); ++ if (ret) ++ return ret; ++ ++ aw_dev->dsp_cfg_len = len; ++ ++ ret = aw_dev_get_ra(&aw_dev->cali_desc); ++ ++ return ret; ++} ++ ++static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, ++ unsigned char *data, unsigned int len) ++{ ++ int ret; ++ ++ dev_dbg(aw_dev->dev, "dsp firmware len:%d", len); ++ ++ if (!len || !data) { ++ dev_err(aw_dev->dev, "dsp firmware data is null or len is 0"); ++ return -EINVAL; ++ } ++ ++ aw_dev->dsp_fw_len = len; ++ ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_FW_ADDR); ++ ++ return ret; ++} ++ ++static int aw_dev_check_sram(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ ++ /* read dsp_rom_check_reg */ ++ aw_dev_dsp_read(aw_dev, AW88399_DSP_ROM_CHECK_ADDR, ®_val, AW_DSP_16_DATA); ++ if (reg_val != AW88399_DSP_ROM_CHECK_DATA) { ++ dev_err(aw_dev->dev, "check dsp rom failed, read[0x%x] != check[0x%x]", ++ reg_val, AW88399_DSP_ROM_CHECK_DATA); ++ return -EPERM; ++ } ++ ++ /* check dsp_cfg_base_addr */ ++ aw_dev_dsp_write(aw_dev, AW88399_DSP_CFG_ADDR, ++ AW88399_DSP_ODD_NUM_BIT_TEST, AW_DSP_16_DATA); ++ aw_dev_dsp_read(aw_dev, AW88399_DSP_CFG_ADDR, ®_val, AW_DSP_16_DATA); ++ if (reg_val != AW88399_DSP_ODD_NUM_BIT_TEST) { ++ dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", ++ reg_val, AW88399_DSP_ODD_NUM_BIT_TEST); ++ return -EPERM; ++ } ++ ++ return 0; ++} ++ ++static void aw_dev_select_memclk(struct aw_device *aw_dev, unsigned char flag) ++{ ++ int ret; ++ ++ switch (flag) { ++ case AW88399_DEV_MEMCLK_PLL: ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_MEM_CLKSEL_MASK, ++ AW88399_MEM_CLKSEL_DAPHCLK_VALUE); ++ if (ret) ++ dev_err(aw_dev->dev, "memclk select pll failed"); ++ break; ++ case AW88399_DEV_MEMCLK_OSC: ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_MEM_CLKSEL_MASK, ++ AW88399_MEM_CLKSEL_OSCCLK_VALUE); ++ if (ret) ++ dev_err(aw_dev->dev, "memclk select OSC failed"); ++ break; ++ default: ++ dev_err(aw_dev->dev, "unknown memclk config, flag=0x%x", flag); ++ break; ++ } ++} ++ ++static void aw_dev_get_cur_mode_st(struct aw_device *aw_dev) ++{ ++ struct aw_profctrl_desc *profctrl_desc = &aw_dev->profctrl_desc; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL_REG, ®_val); ++ if (ret) { ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++ return; ++ } ++ if ((reg_val & (~AW88399_RCV_MODE_MASK)) == AW88399_RCV_MODE_RECEIVER_VALUE) ++ profctrl_desc->cur_mode = AW88399_RCV_MODE; ++ else ++ profctrl_desc->cur_mode = AW88399_NOT_RCV_MODE; ++} ++ ++static int aw_dev_update_reg_container(struct aw88399 *aw88399, ++ unsigned char *data, unsigned int len) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; ++ u16 read_vol, reg_val; ++ int data_len, i, ret; ++ int16_t *reg_data; ++ u8 reg_addr; ++ ++ reg_data = (int16_t *)data; ++ data_len = len >> 1; ++ ++ if (data_len & 0x1) { ++ dev_err(aw_dev->dev, "data len:%d unsupported", data_len); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < data_len; i += 2) { ++ reg_addr = reg_data[i]; ++ reg_val = reg_data[i + 1]; ++ ++ if (reg_addr == AW88399_DSPVCALB_REG) { ++ aw88399->vcalb_init_val = reg_val; ++ continue; ++ } ++ ++ if (reg_addr == AW88399_SYSCTRL_REG) { ++ if (reg_val & (~AW88399_DSPBY_MASK)) ++ aw_dev->dsp_cfg = AW88399_DEV_DSP_BYPASS; ++ else ++ aw_dev->dsp_cfg = AW88399_DEV_DSP_WORK; ++ ++ reg_val &= (AW88399_HMUTE_MASK | AW88399_PWDN_MASK | ++ AW88399_DSPBY_MASK); ++ reg_val |= (AW88399_HMUTE_ENABLE_VALUE | AW88399_PWDN_POWER_DOWN_VALUE | ++ AW88399_DSPBY_BYPASS_VALUE); ++ } ++ ++ if (reg_addr == AW88399_I2SCTRL3_REG) { ++ reg_val &= AW88399_I2STXEN_MASK; ++ reg_val |= AW88399_I2STXEN_DISABLE_VALUE; ++ } ++ ++ if (reg_addr == AW88399_SYSCTRL2_REG) { ++ read_vol = (reg_val & (~AW88399_VOL_MASK)) >> ++ AW88399_VOL_START_BIT; ++ aw_dev->volume_desc.init_volume = read_vol; ++ } ++ ++ if (reg_addr == AW88399_DBGCTRL_REG) { ++ if ((reg_val & (~AW88399_EF_DBMD_MASK)) == AW88399_EF_DBMD_OR_VALUE) ++ aw88399->check_val = AW_EF_OR_CHECK; ++ else ++ aw88399->check_val = AW_EF_AND_CHECK; ++ ++ aw88399->dither_st = reg_val & (~AW88399_DITHER_EN_MASK); ++ } ++ ++ if (reg_addr == AW88399_CRCCTRL_REG) ++ aw88399->crc_init_val = reg_val; ++ ++ ret = regmap_write(aw_dev->regmap, reg_addr, reg_val); ++ if (ret) ++ return ret; ++ } ++ ++ aw_dev_pwd(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ ++ aw_dev_get_cur_mode_st(aw_dev); ++ ++ if (aw_dev->prof_cur != aw_dev->prof_index) ++ vol_desc->ctl_volume = 0; ++ else ++ aw_dev_set_volume(aw_dev, vol_desc->ctl_volume); ++ ++ return 0; ++} ++ ++static int aw_dev_reg_update(struct aw88399 *aw88399, ++ unsigned char *data, unsigned int len) ++{ ++ int ret; ++ ++ if (!len || !data) { ++ dev_err(aw88399->aw_pa->dev, "reg data is null or len is 0"); ++ return -EINVAL; ++ } ++ ++ ret = aw_dev_update_reg_container(aw88399, data, len); ++ if (ret) ++ dev_err(aw88399->aw_pa->dev, "reg update failed"); ++ ++ return ret; ++} ++ ++int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name) ++{ ++ struct aw_prof_info *prof_info = &aw_dev->prof_info; ++ struct aw_prof_desc *prof_desc; ++ ++ if ((index >= aw_dev->prof_info.count) || (index < 0)) { ++ dev_err(aw_dev->dev, "index[%d] overflow count[%d]", ++ index, aw_dev->prof_info.count); ++ return -EINVAL; ++ } ++ ++ prof_desc = &aw_dev->prof_info.prof_desc[index]; ++ ++ *prof_name = prof_info->prof_name_list[prof_desc->id]; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw88399_dev_get_prof_name); ++ ++static int aw88399_dev_get_prof_data(struct aw_device *aw_dev, int index, ++ struct aw_prof_desc **prof_desc) ++{ ++ if ((index >= aw_dev->prof_info.count) || (index < 0)) { ++ dev_err(aw_dev->dev, "%s: index[%d] overflow count[%d]\n", ++ __func__, index, aw_dev->prof_info.count); ++ return -EINVAL; ++ } ++ ++ *prof_desc = &aw_dev->prof_info.prof_desc[index]; ++ ++ return 0; ++} ++ ++static int aw88399_dev_fw_update(struct aw88399 *aw88399, bool up_dsp_fw_en, bool force_up_en) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ struct aw_prof_desc *prof_index_desc; ++ struct aw_sec_data_desc *sec_desc; ++ char *prof_name; ++ int ret; ++ ++ if ((aw_dev->prof_cur == aw_dev->prof_index) && ++ (force_up_en == AW88399_FORCE_UPDATE_OFF)) { ++ dev_dbg(aw_dev->dev, "scene no change, not update"); ++ return 0; ++ } ++ ++ if (aw_dev->fw_status == AW88399_DEV_FW_FAILED) { ++ dev_err(aw_dev->dev, "fw status[%d] error", aw_dev->fw_status); ++ return -EPERM; ++ } ++ ++ ret = aw88399_dev_get_prof_name(aw_dev, aw_dev->prof_index, &prof_name); ++ if (ret) ++ return ret; ++ ++ dev_dbg(aw_dev->dev, "start update %s", prof_name); ++ ++ ret = aw88399_dev_get_prof_data(aw_dev, aw_dev->prof_index, &prof_index_desc); ++ if (ret) ++ return ret; ++ ++ /* update reg */ ++ sec_desc = prof_index_desc->sec_desc; ++ ret = aw_dev_reg_update(aw88399, sec_desc[AW88395_DATA_TYPE_REG].data, ++ sec_desc[AW88395_DATA_TYPE_REG].len); ++ if (ret) { ++ dev_err(aw_dev->dev, "update reg failed"); ++ return ret; ++ } ++ ++ aw88399_dev_mute(aw_dev, true); ++ ++ if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) ++ aw_dev_dsp_enable(aw_dev, false); ++ ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); ++ ++ ret = aw_dev_check_sram(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "check sram failed"); ++ goto error; ++ } ++ ++ if (up_dsp_fw_en) { ++ dev_dbg(aw_dev->dev, "fw_ver: [%x]", prof_index_desc->fw_ver); ++ ret = aw_dev_dsp_update_fw(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_FW].data, ++ sec_desc[AW88395_DATA_TYPE_DSP_FW].len); ++ if (ret) { ++ dev_err(aw_dev->dev, "update dsp fw failed"); ++ goto error; ++ } ++ } ++ ++ /* update dsp config */ ++ ret = aw_dev_dsp_update_cfg(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_CFG].data, ++ sec_desc[AW88395_DATA_TYPE_DSP_CFG].len); ++ if (ret) { ++ dev_err(aw_dev->dev, "update dsp cfg failed"); ++ goto error; ++ } ++ ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); ++ ++ aw_dev->prof_cur = aw_dev->prof_index; ++ ++ return 0; ++ ++error: ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); ++ return ret; ++} ++ ++static void aw88399_start_pa(struct aw88399 *aw88399) ++{ ++ int ret, i; ++ ++ for (i = 0; i < AW88399_START_RETRIES; i++) { ++ ret = aw88399_dev_start(aw88399); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "aw88399 device start failed. retry = %d", i); ++ ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_ON, true); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "fw update failed"); ++ continue; ++ } ++ } else { ++ dev_dbg(aw88399->aw_pa->dev, "start success\n"); ++ break; ++ } ++ } ++} ++ ++void aw88399_startup_work(struct work_struct *work) ++{ ++ struct aw88399 *aw88399 = ++ container_of(work, struct aw88399, start_work.work); ++ ++ mutex_lock(&aw88399->lock); ++ aw88399_start_pa(aw88399); ++ mutex_unlock(&aw88399->lock); ++} ++EXPORT_SYMBOL_GPL(aw88399_startup_work); ++ ++void aw88399_start(struct aw88399 *aw88399, bool sync_start) ++{ ++ int ret; ++ ++ if (aw88399->aw_pa->fw_status != AW88399_DEV_FW_OK) ++ return; ++ ++ if (aw88399->aw_pa->status == AW88399_DEV_PW_ON) ++ return; ++ ++ ret = aw88399_dev_fw_update(aw88399, aw88399->fw_needs_reload ? ++ AW88399_DSP_FW_UPDATE_ON : AW88399_DSP_FW_UPDATE_OFF, true); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "fw update failed."); ++ return; ++ } ++ ++ aw88399->fw_needs_reload = false; ++ ++ if (sync_start == AW88399_SYNC_START) ++ aw88399_start_pa(aw88399); ++ else ++ queue_delayed_work(system_dfl_wq, ++ &aw88399->start_work, ++ AW88399_START_WORK_DELAY_MS); ++} ++EXPORT_SYMBOL_GPL(aw88399_start); ++ ++static int aw_dev_check_sysint(struct aw_device *aw_dev) ++{ ++ u16 reg_val; ++ ++ aw_dev_get_int_status(aw_dev, ®_val); ++ if (reg_val & AW88399_BIT_SYSINT_CHECK) { ++ dev_err(aw_dev->dev, "pa stop check fail:0x%04x", reg_val); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++int aw88399_stop(struct aw_device *aw_dev) ++{ ++ struct aw_sec_data_desc *dsp_cfg = ++ &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_CFG]; ++ struct aw_sec_data_desc *dsp_fw = ++ &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_FW]; ++ int int_st; ++ ++ if (aw_dev->status == AW88399_DEV_PW_OFF) { ++ dev_dbg(aw_dev->dev, "already power off"); ++ return 0; ++ } ++ ++ aw_dev->status = AW88399_DEV_PW_OFF; ++ ++ aw88399_dev_mute(aw_dev, true); ++ usleep_range(AW88399_4000_US, AW88399_4000_US + 100); ++ ++ aw_dev_i2s_tx_enable(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 100); ++ ++ int_st = aw_dev_check_sysint(aw_dev); ++ ++ aw_dev_dsp_enable(aw_dev, false); ++ ++ aw_dev_amppd(aw_dev, true); ++ ++ if (int_st) { ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); ++ aw_dev_dsp_update_fw(aw_dev, dsp_fw->data, dsp_fw->len); ++ aw_dev_dsp_update_cfg(aw_dev, dsp_cfg->data, dsp_cfg->len); ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); ++ } ++ ++ aw_dev_pwd(aw_dev, true); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw88399_stop); ++ ++static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ int ret; ++ ++ ret = aw88395_dev_cfg_load(aw_dev, aw_cfg); ++ if (ret) { ++ dev_err(aw_dev->dev, "aw_dev acf parse failed"); ++ return -EINVAL; ++ } ++ aw_dev->fade_in_time = AW88399_1000_US / 10; ++ aw_dev->fade_out_time = AW88399_1000_US >> 1; ++ aw_dev->prof_cur = aw_dev->prof_info.prof_desc[0].id; ++ aw_dev->prof_index = aw_dev->prof_info.prof_desc[0].id; ++ ++ ret = aw88399_dev_fw_update(aw88399, AW88399_FORCE_UPDATE_ON, AW88399_DSP_FW_UPDATE_ON); ++ if (ret) { ++ dev_err(aw_dev->dev, "fw update failed ret = %d\n", ret); ++ return ret; ++ } ++ ++ aw88399_dev_mute(aw_dev, true); ++ ++ /* close tx feedback */ ++ aw_dev_i2s_tx_enable(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 100); ++ ++ /* enable amppd */ ++ aw_dev_amppd(aw_dev, true); ++ ++ /* close dsp */ ++ aw_dev_dsp_enable(aw_dev, false); ++ /* set power down */ ++ aw_dev_pwd(aw_dev, true); ++ ++ return 0; ++} ++ ++int aw88399_request_firmware_file(struct aw88399 *aw88399) ++{ ++ const struct firmware *cont __free(firmware) = NULL; ++ int ret; ++ ++ aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; ++ ++ ret = request_firmware(&cont, AW88399_ACF_FILE, aw88399->aw_pa->dev); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "request [%s] failed!", AW88399_ACF_FILE); ++ return ret; ++ } ++ ++ dev_dbg(aw88399->aw_pa->dev, "loaded %s - size: %zu\n", ++ AW88399_ACF_FILE, cont ? cont->size : 0); ++ ++ aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, ++ struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); ++ if (!aw88399->aw_cfg) ++ return -ENOMEM; ++ ++ aw88399->aw_cfg->len = (int)cont->size; ++ memcpy(aw88399->aw_cfg->data, cont->data, cont->size); ++ ++ ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "load [%s] failed!", AW88399_ACF_FILE); ++ return ret; ++ } ++ ++ mutex_lock(&aw88399->lock); ++ /* aw device init */ ++ ret = aw88399_dev_init(aw88399, aw88399->aw_cfg); ++ if (ret) ++ dev_err(aw88399->aw_pa->dev, "dev init failed"); ++ mutex_unlock(&aw88399->lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aw88399_request_firmware_file); ++ ++void aw88399_hw_reset(struct aw88399 *aw88399) ++{ ++ if (aw88399->reset_gpio) { ++ gpiod_set_value_cansleep(aw88399->reset_gpio, 1); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ gpiod_set_value_cansleep(aw88399->reset_gpio, 0); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ gpiod_set_value_cansleep(aw88399->reset_gpio, 1); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ } ++} ++EXPORT_SYMBOL_GPL(aw88399_hw_reset); ++ ++static void aw88399_parse_channel_dt(struct aw_device *aw_dev) ++{ ++ struct device_node *np = aw_dev->dev->of_node; ++ u32 channel_value; ++ int ret; ++ ++ ret = of_property_read_u32(np, "awinic,audio-channel", &channel_value); ++ if (ret) { ++ /* ++ * On ACPI systems, DT properties don't exist. Derive channel ++ * from I2C address: 0x34 -> channel 0 (left), 0x35 -> channel 1 (right) ++ */ ++ aw_dev->channel = aw_dev->i2c->addr - 0x34; ++ dev_dbg(aw_dev->dev, ++ "DT channel property not found, using I2C address-based channel %d (addr 0x%02x)\n", ++ aw_dev->channel, aw_dev->i2c->addr); ++ return; ++ } ++ aw_dev->channel = channel_value; ++} ++ ++int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap) ++{ ++ struct aw_device *aw_dev; ++ unsigned int chip_id; ++ int ret; ++ ++ ret = regmap_read(regmap, AW88399_ID_REG, &chip_id); ++ if (ret) { ++ dev_err(&i2c->dev, "%s read chipid error. ret = %d", __func__, ret); ++ return ret; ++ } ++ if (chip_id != AW88399_CHIP_ID) { ++ dev_err(&i2c->dev, "unsupported device"); ++ return -ENXIO; ++ } ++ dev_dbg(&i2c->dev, "chip id = %x\n", chip_id); ++ ++ aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL); ++ if (!aw_dev) ++ return -ENOMEM; ++ aw88399->aw_pa = aw_dev; ++ ++ aw_dev->i2c = i2c; ++ aw_dev->dev = &i2c->dev; ++ aw_dev->regmap = regmap; ++ mutex_init(&aw_dev->dsp_lock); ++ ++ aw_dev->chip_id = chip_id; ++ aw_dev->acf = NULL; ++ aw_dev->prof_info.prof_desc = NULL; ++ aw_dev->prof_info.count = 0; ++ aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID; ++ aw_dev->channel = AW88399_DEV_DEFAULT_CH; ++ aw_dev->fw_status = AW88399_DEV_FW_FAILED; ++ ++ aw_dev->fade_step = AW88399_VOLUME_STEP_DB; ++ aw_dev->volume_desc.ctl_volume = AW88399_VOL_DEFAULT_VALUE; ++ ++ aw88399_parse_channel_dt(aw_dev); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw88399_init); ++ ++void aw88399_dev_set_channel(struct aw88399 *aw88399, int channel) ++{ ++ aw88399->aw_pa->channel = channel; ++} ++EXPORT_SYMBOL_GPL(aw88399_dev_set_channel); ++ ++MODULE_DESCRIPTION("AW88399 common device library"); ++MODULE_LICENSE("GPL"); +diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c +--- a/sound/soc/codecs/aw88399.c ++++ b/sound/soc/codecs/aw88399.c +@@ -7,1220 +7,16 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +-#include +-#include + #include + #include + #include + #include "aw88399.h" + #include "aw88395/aw88395_device.h" + +-static const struct regmap_config aw88399_remap_config = { +- .val_bits = 16, +- .reg_bits = 8, +- .max_register = AW88399_REG_MAX, +- .reg_format_endian = REGMAP_ENDIAN_LITTLE, +- .val_format_endian = REGMAP_ENDIAN_BIG, +-}; +- +-static void aw_dev_pwd(struct aw_device *aw_dev, bool pwd) +-{ +- int ret; +- +- if (pwd) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_PWDN_MASK, AW88399_PWDN_POWER_DOWN_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_PWDN_MASK, AW88399_PWDN_WORKING_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed", __func__); +-} +- +-static void aw_dev_get_int_status(struct aw_device *aw_dev, unsigned short *int_status) +-{ +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSINT_REG, ®_val); +- if (ret) +- dev_err(aw_dev->dev, "read interrupt reg fail, ret=%d", ret); +- else +- *int_status = reg_val; +- +- dev_dbg(aw_dev->dev, "read interrupt reg=0x%04x", *int_status); +-} +- +-static void aw_dev_clear_int_status(struct aw_device *aw_dev) +-{ +- u16 int_status; +- +- /* read int status and clear */ +- aw_dev_get_int_status(aw_dev, &int_status); +- /* make sure int status is clear */ +- aw_dev_get_int_status(aw_dev, &int_status); +- if (int_status) +- dev_dbg(aw_dev->dev, "int status(%d) is not cleaned.\n", int_status); +-} +- +-static int aw_dev_get_iis_status(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); +- if (ret) +- return ret; +- if ((reg_val & AW88399_BIT_PLL_CHECK) != AW88399_BIT_PLL_CHECK) { +- dev_err(aw_dev->dev, "check pll lock fail, reg_val:0x%04x", reg_val); +- return -EINVAL; +- } +- +- return 0; +-} +- +-static int aw_dev_check_mode1_pll(struct aw_device *aw_dev) +-{ +- int ret, i; +- +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = aw_dev_get_iis_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode1 iis signal check error"); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- return 0; +- } +- } +- +- return -EPERM; +-} +- +-static int aw_dev_check_mode2_pll(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- int ret, i; +- +- ret = regmap_read(aw_dev->regmap, AW88399_PLLCTRL2_REG, ®_val); +- if (ret) +- return ret; +- +- reg_val &= (~AW88399_CCO_MUX_MASK); +- if (reg_val == AW88399_CCO_MUX_DIVIDED_VALUE) { +- dev_dbg(aw_dev->dev, "CCO_MUX is already divider"); +- return -EPERM; +- } +- +- /* change mode2 */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, +- ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_DIVIDED_VALUE); +- if (ret) +- return ret; +- +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = aw_dev_get_iis_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode2 iis signal check error"); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- break; +- } +- } +- +- /* change mode1 */ +- regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, +- ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_BYPASS_VALUE); +- if (ret == 0) { +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = aw_dev_get_iis_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode2 switch to mode1, iis signal check error"); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- break; +- } +- } +- } +- +- return ret; +-} +- +-static int aw_dev_check_syspll(struct aw_device *aw_dev) +-{ +- int ret; +- +- ret = aw_dev_check_mode1_pll(aw_dev); +- if (ret) { +- dev_dbg(aw_dev->dev, "mode1 check iis failed try switch to mode2 check"); +- ret = aw_dev_check_mode2_pll(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode2 check iis failed"); +- return ret; +- } +- } +- +- return 0; +-} +- +-static int aw_dev_check_sysst(struct aw_device *aw_dev) +-{ +- unsigned int check_val; +- unsigned int reg_val; +- int ret, i; +- +- ret = regmap_read(aw_dev->regmap, AW88399_PWMCTRL3_REG, ®_val); +- if (ret) +- return ret; +- +- if (reg_val & (~AW88399_NOISE_GATE_EN_MASK)) +- check_val = AW88399_BIT_SYSST_NOSWS_CHECK; +- else +- check_val = AW88399_BIT_SYSST_SWS_CHECK; +- +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); +- if (ret) +- return ret; +- +- if ((reg_val & (~AW88399_BIT_SYSST_CHECK_MASK) & check_val) != check_val) { +- dev_err(aw_dev->dev, "check sysst fail, cnt=%d, reg_val=0x%04x, check:0x%x", +- i, reg_val, AW88399_BIT_SYSST_NOSWS_CHECK); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- return 0; +- } +- } +- +- return -EPERM; +-} +- +-static void aw_dev_amppd(struct aw_device *aw_dev, bool amppd) +-{ +- int ret; +- +- if (amppd) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_AMPPD_MASK, AW88399_AMPPD_POWER_DOWN_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_AMPPD_MASK, AW88399_AMPPD_WORKING_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed", __func__); +-} +- +-static void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable) +-{ +- int ret; +- +- if (is_enable) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_DSPBY_MASK, AW88399_DSPBY_WORKING_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_DSPBY_MASK, AW88399_DSPBY_BYPASS_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed\n", __func__); +-} +- +-static int aw88399_dev_get_icalk(struct aw88399 *aw88399, int16_t *icalk) +-{ +- uint16_t icalkh_val, icalkl_val, icalk_val; +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRH4_REG, ®_val); +- if (ret) +- return ret; +- icalkh_val = reg_val & (~AW88399_EF_ISN_GESLP_H_MASK); +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRL4_REG, ®_val); +- if (ret) +- return ret; +- icalkl_val = reg_val & (~AW88399_EF_ISN_GESLP_L_MASK); +- +- if (aw88399->check_val == AW_EF_AND_CHECK) +- icalk_val = icalkh_val & icalkl_val; +- else +- icalk_val = icalkh_val | icalkl_val; +- +- if (icalk_val & (~AW88399_EF_ISN_GESLP_SIGN_MASK)) +- icalk_val = icalk_val | AW88399_EF_ISN_GESLP_SIGN_NEG; +- *icalk = (int16_t)icalk_val; +- +- return 0; +-} +- +-static int aw88399_dev_get_vcalk(struct aw88399 *aw88399, int16_t *vcalk) +-{ +- uint16_t vcalkh_val, vcalkl_val, vcalk_val; +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRH3_REG, ®_val); +- if (ret) +- return ret; +- +- vcalkh_val = reg_val & (~AW88399_EF_VSN_GESLP_H_MASK); +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRL3_REG, ®_val); +- if (ret) +- return ret; +- +- vcalkl_val = reg_val & (~AW88399_EF_VSN_GESLP_L_MASK); +- +- if (aw88399->check_val == AW_EF_AND_CHECK) +- vcalk_val = vcalkh_val & vcalkl_val; +- else +- vcalk_val = vcalkh_val | vcalkl_val; +- +- if (vcalk_val & AW88399_EF_VSN_GESLP_SIGN_MASK) +- vcalk_val = vcalk_val | AW88399_EF_VSN_GESLP_SIGN_NEG; +- *vcalk = (int16_t)vcalk_val; +- +- return 0; +-} +- +-static int aw88399_dev_get_internal_vcalk(struct aw88399 *aw88399, int16_t *vcalk) +-{ +- uint16_t vcalkh_val, vcalkl_val, vcalk_val; +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRH2_REG, ®_val); +- if (ret) +- return ret; +- vcalkh_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_H_MASK); +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRL2_REG, ®_val); +- if (ret) +- return ret; +- vcalkl_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_L_MASK); +- +- if (aw88399->check_val == AW_EF_AND_CHECK) +- vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) & +- (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); +- else +- vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) | +- (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); +- +- if (vcalk_val & (~AW88399_TEM4_SIGN_MASK)) +- vcalk_val = vcalk_val | AW88399_TEM4_SIGN_NEG; +- +- *vcalk = (int16_t)vcalk_val; +- +- return 0; +-} +- +-static int aw_dev_set_vcalb(struct aw88399 *aw88399) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int vsense_select, vsense_value; +- int32_t ical_k, vcal_k, vcalb; +- int16_t icalk, vcalk; +- uint16_t reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_VSNCTRL1_REG, &vsense_value); +- if (ret) +- return ret; +- +- vsense_select = vsense_value & (~AW88399_VDSEL_MASK); +- +- ret = aw88399_dev_get_icalk(aw88399, &icalk); +- if (ret) { +- dev_err(aw_dev->dev, "get icalk failed\n"); +- return ret; +- } +- +- ical_k = icalk * AW88399_ICABLK_FACTOR + AW88399_CABL_BASE_VALUE; +- +- switch (vsense_select) { +- case AW88399_DEV_VDSEL_VSENSE: +- ret = aw88399_dev_get_vcalk(aw88399, &vcalk); +- vcal_k = vcalk * AW88399_VCABLK_FACTOR + AW88399_CABL_BASE_VALUE; +- vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_FACTOR / AW88399_ISCAL_FACTOR * +- ical_k / vcal_k * aw88399->vcalb_init_val; +- break; +- case AW88399_DEV_VDSEL_DAC: +- ret = aw88399_dev_get_internal_vcalk(aw88399, &vcalk); +- vcal_k = vcalk * AW88399_VCABLK_DAC_FACTOR + AW88399_CABL_BASE_VALUE; +- vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_DAC_FACTOR / +- AW88399_ISCAL_DAC_FACTOR * ical_k / +- vcal_k * aw88399->vcalb_init_val; +- break; +- default: +- dev_err(aw_dev->dev, "%s: unsupported vsense\n", __func__); +- ret = -EINVAL; +- break; +- } +- if (ret) +- return ret; +- +- vcalb = vcalb >> AW88399_VCALB_ADJ_FACTOR; +- reg_val = (uint32_t)vcalb; +- +- regmap_write(aw_dev->regmap, AW88399_DSPVCALB_REG, reg_val); +- +- return 0; +-} +- +-static int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc) +-{ +- struct aw_device *aw_dev = +- container_of(cali_desc, struct aw_device, cali_desc); +- uint16_t re_lbits, re_hbits; +- u32 cali_re; +- int ret; +- +- if ((aw_dev->cali_desc.cali_re >= AW88399_CALI_RE_MAX) || +- (aw_dev->cali_desc.cali_re <= AW88399_CALI_RE_MIN)) +- return -EINVAL; +- +- cali_re = AW88399_SHOW_RE_TO_DSP_RE((aw_dev->cali_desc.cali_re + +- aw_dev->cali_desc.ra), AW88399_DSP_RE_SHIFT); +- +- re_hbits = (cali_re & (~AW88399_CALI_RE_HBITS_MASK)) >> AW88399_CALI_RE_HBITS_SHIFT; +- re_lbits = (cali_re & (~AW88399_CALI_RE_LBITS_MASK)) >> AW88399_CALI_RE_LBITS_SHIFT; +- +- ret = regmap_write(aw_dev->regmap, AW88399_ACR1_REG, re_hbits); +- if (ret) { +- dev_err(aw_dev->dev, "set cali re error"); +- return ret; +- } +- +- ret = regmap_write(aw_dev->regmap, AW88399_ACR2_REG, re_lbits); +- if (ret) +- dev_err(aw_dev->dev, "set cali re error"); +- +- return ret; +-} +- +-static int aw_dev_fw_crc_check(struct aw_device *aw_dev) +-{ +- uint16_t check_val, fw_len_val; +- unsigned int reg_val; +- int ret; +- +- /* calculate fw_end_addr */ +- fw_len_val = ((aw_dev->dsp_fw_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_FW_BASE_ADDR; +- +- /* write fw_end_addr to crc_end_addr */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_END_ADDR_MASK, fw_len_val); +- if (ret) +- return ret; +- /* enable fw crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_ENABLE_VALUE); +- +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- +- /* read crc check result */ +- regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); +- if (ret) +- return ret; +- +- check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; +- +- /* disable fw crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_DISABLE_VALUE); +- if (ret) +- return ret; +- +- if (check_val != AW88399_CRC_CHECK_PASS_VAL) { +- dev_err(aw_dev->dev, "%s failed, check_val 0x%x != 0x%x", +- __func__, check_val, AW88399_CRC_CHECK_PASS_VAL); +- ret = -EINVAL; +- } +- +- return ret; +-} +- +-static int aw_dev_cfg_crc_check(struct aw_device *aw_dev) +-{ +- uint16_t check_val, cfg_len_val; +- unsigned int reg_val; +- int ret; +- +- /* calculate cfg end addr */ +- cfg_len_val = ((aw_dev->dsp_cfg_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_CFG_BASE_ADDR; +- +- /* write cfg_end_addr to crc_end_addr */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_END_ADDR_MASK, cfg_len_val); +- if (ret) +- return ret; +- +- /* enable cfg crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_ENABLE_VALUE); +- if (ret) +- return ret; +- +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- +- /* read crc check result */ +- ret = regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); +- if (ret) +- return ret; +- +- check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; +- +- /* disable cfg crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_DISABLE_VALUE); +- if (ret) +- return ret; +- +- if (check_val != AW88399_CRC_CHECK_PASS_VAL) { +- dev_err(aw_dev->dev, "crc_check failed, check val 0x%x != 0x%x", +- check_val, AW88399_CRC_CHECK_PASS_VAL); +- ret = -EINVAL; +- } +- +- return ret; +-} +- +-static int aw_dev_hw_crc_check(struct aw88399 *aw88399) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- int ret; +- +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_BYPASS_VALUE); +- if (ret) +- return ret; +- +- ret = aw_dev_fw_crc_check(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "fw_crc_check failed\n"); +- goto crc_check_failed; +- } +- +- ret = aw_dev_cfg_crc_check(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "cfg_crc_check failed\n"); +- goto crc_check_failed; +- } +- +- ret = regmap_write(aw_dev->regmap, AW88399_CRCCTRL_REG, aw88399->crc_init_val); +- if (ret) +- return ret; +- +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); +- +- return ret; +- +-crc_check_failed: +- regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); +- return ret; +-} +- +-static void aw_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag) +-{ +- int ret; +- +- if (flag) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCTRL3_REG, +- ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_ENABLE_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_DISABLE_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed", __func__); +-} +- +-static int aw_dev_get_dsp_status(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_WDT_REG, ®_val); +- if (ret) +- return ret; +- if (!(reg_val & (~AW88399_WDT_CNT_MASK))) +- return -EPERM; +- +- return 0; +-} +- +-static int aw_dev_dsp_check(struct aw_device *aw_dev) +-{ +- int ret, i; +- +- switch (aw_dev->dsp_cfg) { +- case AW88399_DEV_DSP_BYPASS: +- dev_dbg(aw_dev->dev, "dsp bypass"); +- ret = 0; +- break; +- case AW88399_DEV_DSP_WORK: +- aw_dev_dsp_enable(aw_dev, false); +- aw_dev_dsp_enable(aw_dev, true); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- for (i = 0; i < AW88399_DEV_DSP_CHECK_MAX; i++) { +- ret = aw_dev_get_dsp_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "dsp wdt status error=%d", ret); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } +- } +- break; +- default: +- dev_err(aw_dev->dev, "unknown dsp cfg=%d", aw_dev->dsp_cfg); +- ret = -EINVAL; +- break; +- } +- +- return ret; +-} +- +-static int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value) +-{ +- struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; +- unsigned int reg_value; +- u16 real_value; +- int ret; +- +- real_value = min((value + vol_desc->init_volume), (unsigned int)AW88399_MUTE_VOL); +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL2_REG, ®_value); +- if (ret) +- return ret; +- +- dev_dbg(aw_dev->dev, "value 0x%x , reg:0x%x", value, real_value); +- +- real_value = (real_value << AW88399_VOL_START_BIT) | (reg_value & AW88399_VOL_MASK); +- +- ret = regmap_write(aw_dev->regmap, AW88399_SYSCTRL2_REG, real_value); +- +- return ret; +-} +- +-static void aw_dev_fade_in(struct aw_device *aw_dev) +-{ +- struct aw_volume_desc *desc = &aw_dev->volume_desc; +- u16 fade_in_vol = desc->ctl_volume; +- int fade_step = aw_dev->fade_step; +- int i; +- +- if (fade_step == 0 || aw_dev->fade_in_time == 0) { +- aw_dev_set_volume(aw_dev, fade_in_vol); +- return; +- } +- +- for (i = AW88399_MUTE_VOL; i >= fade_in_vol; i -= fade_step) { +- aw_dev_set_volume(aw_dev, i); +- usleep_range(aw_dev->fade_in_time, aw_dev->fade_in_time + 10); +- } +- +- if (i != fade_in_vol) +- aw_dev_set_volume(aw_dev, fade_in_vol); +-} +- +-static void aw_dev_fade_out(struct aw_device *aw_dev) +-{ +- struct aw_volume_desc *desc = &aw_dev->volume_desc; +- int fade_step = aw_dev->fade_step; +- int i; +- +- if (fade_step == 0 || aw_dev->fade_out_time == 0) { +- aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); +- return; +- } +- +- for (i = desc->ctl_volume; i <= AW88399_MUTE_VOL; i += fade_step) { +- aw_dev_set_volume(aw_dev, i); +- usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); +- } +- +- if (i != AW88399_MUTE_VOL) { +- aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); +- usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); +- } +-} +- +-static void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute) +-{ +- if (is_mute) { +- aw_dev_fade_out(aw_dev); +- regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_HMUTE_MASK, AW88399_HMUTE_ENABLE_VALUE); +- } else { +- regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_HMUTE_MASK, AW88399_HMUTE_DISABLE_VALUE); +- aw_dev_fade_in(aw_dev); +- } +-} +- +-static void aw88399_dev_set_dither(struct aw88399 *aw88399, bool dither) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- +- if (dither) +- regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_ENABLE_VALUE); +- else +- regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_DISABLE_VALUE); +-} +- +-static int aw88399_dev_start(struct aw88399 *aw88399) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- int ret; +- +- if (aw_dev->status == AW88399_DEV_PW_ON) { +- dev_dbg(aw_dev->dev, "already power on"); +- return 0; +- } +- +- aw88399_dev_set_dither(aw88399, false); +- +- /* power on */ +- aw_dev_pwd(aw_dev, false); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- +- ret = aw_dev_check_syspll(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "pll check failed cannot start"); +- goto pll_check_fail; +- } +- +- /* amppd on */ +- aw_dev_amppd(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 50); +- +- /* check i2s status */ +- ret = aw_dev_check_sysst(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "sysst check failed"); +- goto sysst_check_fail; +- } +- +- if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) { +- ret = aw_dev_hw_crc_check(aw88399); +- if (ret) { +- dev_err(aw_dev->dev, "dsp crc check failed"); +- goto crc_check_fail; +- } +- aw_dev_dsp_enable(aw_dev, false); +- aw_dev_set_vcalb(aw88399); +- aw_dev_update_cali_re(&aw_dev->cali_desc); +- +- ret = aw_dev_dsp_check(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "dsp status check failed"); +- goto dsp_check_fail; +- } +- } else { +- dev_dbg(aw_dev->dev, "start pa with dsp bypass"); +- } +- +- /* enable tx feedback */ +- aw_dev_i2s_tx_enable(aw_dev, true); +- +- if (aw88399->dither_st == AW88399_DITHER_EN_ENABLE_VALUE) +- aw88399_dev_set_dither(aw88399, true); +- +- /* close mute */ +- aw88399_dev_mute(aw_dev, false); +- /* clear inturrupt */ +- aw_dev_clear_int_status(aw_dev); +- aw_dev->status = AW88399_DEV_PW_ON; +- +- return 0; +- +-dsp_check_fail: +-crc_check_fail: +- aw_dev_dsp_enable(aw_dev, false); +-sysst_check_fail: +- aw_dev_clear_int_status(aw_dev); +- aw_dev_amppd(aw_dev, true); +-pll_check_fail: +- aw_dev_pwd(aw_dev, true); +- aw_dev->status = AW88399_DEV_PW_OFF; +- +- return ret; +-} +- +-static int aw_dev_dsp_update_container(struct aw_device *aw_dev, +- unsigned char *data, unsigned int len, unsigned short base) +-{ +- u32 tmp_len; +- int i, ret; +- +- ret = regmap_write(aw_dev->regmap, AW88399_DSPMADD_REG, base); +- if (ret) +- return ret; +- +- for (i = 0; i < len; i += AW88399_MAX_RAM_WRITE_BYTE_SIZE) { +- tmp_len = min(len - i, AW88399_MAX_RAM_WRITE_BYTE_SIZE); +- ret = regmap_raw_write(aw_dev->regmap, AW88399_DSPMDAT_REG, +- &data[i], tmp_len); +- if (ret) +- return ret; +- } +- +- return 0; +-} +- +-static int aw_dev_get_ra(struct aw_cali_desc *cali_desc) +-{ +- struct aw_device *aw_dev = +- container_of(cali_desc, struct aw_device, cali_desc); +- u32 dsp_ra; +- int ret; +- +- ret = aw_dev_dsp_read(aw_dev, AW88399_DSP_REG_CFG_ADPZ_RA, +- &dsp_ra, AW_DSP_32_DATA); +- if (ret) { +- dev_err(aw_dev->dev, "read ra error"); +- return ret; +- } +- +- cali_desc->ra = AW88399_DSP_RE_TO_SHOW_RE(dsp_ra, +- AW88399_DSP_RE_SHIFT); +- +- return 0; +-} +- +-static int aw_dev_dsp_update_cfg(struct aw_device *aw_dev, +- unsigned char *data, unsigned int len) +-{ +- int ret; +- +- dev_dbg(aw_dev->dev, "dsp config len:%d", len); +- +- if (!len || !data) { +- dev_err(aw_dev->dev, "dsp config data is null or len is 0"); +- return -EINVAL; +- } +- +- ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_CFG_ADDR); +- if (ret) +- return ret; +- +- aw_dev->dsp_cfg_len = len; +- +- ret = aw_dev_get_ra(&aw_dev->cali_desc); +- +- return ret; +-} +- +-static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, +- unsigned char *data, unsigned int len) +-{ +- int ret; +- +- dev_dbg(aw_dev->dev, "dsp firmware len:%d", len); +- +- if (!len || !data) { +- dev_err(aw_dev->dev, "dsp firmware data is null or len is 0"); +- return -EINVAL; +- } +- +- aw_dev->dsp_fw_len = len; +- ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_FW_ADDR); +- +- return ret; +-} +- +-static int aw_dev_check_sram(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- +- /* read dsp_rom_check_reg */ +- aw_dev_dsp_read(aw_dev, AW88399_DSP_ROM_CHECK_ADDR, ®_val, AW_DSP_16_DATA); +- if (reg_val != AW88399_DSP_ROM_CHECK_DATA) { +- dev_err(aw_dev->dev, "check dsp rom failed, read[0x%x] != check[0x%x]", +- reg_val, AW88399_DSP_ROM_CHECK_DATA); +- return -EPERM; +- } +- +- /* check dsp_cfg_base_addr */ +- aw_dev_dsp_write(aw_dev, AW88399_DSP_CFG_ADDR, +- AW88399_DSP_ODD_NUM_BIT_TEST, AW_DSP_16_DATA); +- aw_dev_dsp_read(aw_dev, AW88399_DSP_CFG_ADDR, ®_val, AW_DSP_16_DATA); +- if (reg_val != AW88399_DSP_ODD_NUM_BIT_TEST) { +- dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", +- reg_val, AW88399_DSP_ODD_NUM_BIT_TEST); +- return -EPERM; +- } +- +- return 0; +-} +- +-static void aw_dev_select_memclk(struct aw_device *aw_dev, unsigned char flag) +-{ +- int ret; +- +- switch (flag) { +- case AW88399_DEV_MEMCLK_PLL: +- ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_MEM_CLKSEL_MASK, +- AW88399_MEM_CLKSEL_DAPHCLK_VALUE); +- if (ret) +- dev_err(aw_dev->dev, "memclk select pll failed"); +- break; +- case AW88399_DEV_MEMCLK_OSC: +- ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_MEM_CLKSEL_MASK, +- AW88399_MEM_CLKSEL_OSCCLK_VALUE); +- if (ret) +- dev_err(aw_dev->dev, "memclk select OSC failed"); +- break; +- default: +- dev_err(aw_dev->dev, "unknown memclk config, flag=0x%x", flag); +- break; +- } +-} +- +-static void aw_dev_get_cur_mode_st(struct aw_device *aw_dev) +-{ +- struct aw_profctrl_desc *profctrl_desc = &aw_dev->profctrl_desc; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL_REG, ®_val); +- if (ret) { +- dev_dbg(aw_dev->dev, "%s failed", __func__); +- return; +- } +- if ((reg_val & (~AW88399_RCV_MODE_MASK)) == AW88399_RCV_MODE_RECEIVER_VALUE) +- profctrl_desc->cur_mode = AW88399_RCV_MODE; +- else +- profctrl_desc->cur_mode = AW88399_NOT_RCV_MODE; +-} +- +-static int aw_dev_update_reg_container(struct aw88399 *aw88399, +- unsigned char *data, unsigned int len) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; +- u16 read_vol, reg_val; +- int data_len, i, ret; +- int16_t *reg_data; +- u8 reg_addr; +- +- reg_data = (int16_t *)data; +- data_len = len >> 1; +- +- if (data_len & 0x1) { +- dev_err(aw_dev->dev, "data len:%d unsupported", data_len); +- return -EINVAL; +- } +- +- for (i = 0; i < data_len; i += 2) { +- reg_addr = reg_data[i]; +- reg_val = reg_data[i + 1]; +- +- if (reg_addr == AW88399_DSPVCALB_REG) { +- aw88399->vcalb_init_val = reg_val; +- continue; +- } +- +- if (reg_addr == AW88399_SYSCTRL_REG) { +- if (reg_val & (~AW88399_DSPBY_MASK)) +- aw_dev->dsp_cfg = AW88399_DEV_DSP_BYPASS; +- else +- aw_dev->dsp_cfg = AW88399_DEV_DSP_WORK; +- +- reg_val &= (AW88399_HMUTE_MASK | AW88399_PWDN_MASK | +- AW88399_DSPBY_MASK); +- reg_val |= (AW88399_HMUTE_ENABLE_VALUE | AW88399_PWDN_POWER_DOWN_VALUE | +- AW88399_DSPBY_BYPASS_VALUE); +- } +- +- if (reg_addr == AW88399_I2SCTRL3_REG) { +- reg_val &= AW88399_I2STXEN_MASK; +- reg_val |= AW88399_I2STXEN_DISABLE_VALUE; +- } +- +- if (reg_addr == AW88399_SYSCTRL2_REG) { +- read_vol = (reg_val & (~AW88399_VOL_MASK)) >> +- AW88399_VOL_START_BIT; +- aw_dev->volume_desc.init_volume = read_vol; +- } +- +- if (reg_addr == AW88399_DBGCTRL_REG) { +- if ((reg_val & (~AW88399_EF_DBMD_MASK)) == AW88399_EF_DBMD_OR_VALUE) +- aw88399->check_val = AW_EF_OR_CHECK; +- else +- aw88399->check_val = AW_EF_AND_CHECK; +- +- aw88399->dither_st = reg_val & (~AW88399_DITHER_EN_MASK); +- } +- +- if (reg_addr == AW88399_CRCCTRL_REG) +- aw88399->crc_init_val = reg_val; +- +- ret = regmap_write(aw_dev->regmap, reg_addr, reg_val); +- if (ret) +- return ret; +- } +- +- aw_dev_pwd(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- +- aw_dev_get_cur_mode_st(aw_dev); +- +- if (aw_dev->prof_cur != aw_dev->prof_index) +- vol_desc->ctl_volume = 0; +- else +- aw_dev_set_volume(aw_dev, vol_desc->ctl_volume); +- +- return 0; +-} +- +-static int aw_dev_reg_update(struct aw88399 *aw88399, +- unsigned char *data, unsigned int len) +-{ +- int ret; +- +- if (!len || !data) { +- dev_err(aw88399->aw_pa->dev, "reg data is null or len is 0"); +- return -EINVAL; +- } +- +- ret = aw_dev_update_reg_container(aw88399, data, len); +- if (ret) +- dev_err(aw88399->aw_pa->dev, "reg update failed"); +- +- return ret; +-} +- +-static int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name) +-{ +- struct aw_prof_info *prof_info = &aw_dev->prof_info; +- struct aw_prof_desc *prof_desc; +- +- if ((index >= aw_dev->prof_info.count) || (index < 0)) { +- dev_err(aw_dev->dev, "index[%d] overflow count[%d]", +- index, aw_dev->prof_info.count); +- return -EINVAL; +- } +- +- prof_desc = &aw_dev->prof_info.prof_desc[index]; +- +- *prof_name = prof_info->prof_name_list[prof_desc->id]; +- +- return 0; +-} +- +-static int aw88399_dev_get_prof_data(struct aw_device *aw_dev, int index, +- struct aw_prof_desc **prof_desc) +-{ +- if ((index >= aw_dev->prof_info.count) || (index < 0)) { +- dev_err(aw_dev->dev, "%s: index[%d] overflow count[%d]\n", +- __func__, index, aw_dev->prof_info.count); +- return -EINVAL; +- } +- +- *prof_desc = &aw_dev->prof_info.prof_desc[index]; +- +- return 0; +-} +- +-static int aw88399_dev_fw_update(struct aw88399 *aw88399, bool up_dsp_fw_en, bool force_up_en) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- struct aw_prof_desc *prof_index_desc; +- struct aw_sec_data_desc *sec_desc; +- char *prof_name; +- int ret; +- +- if ((aw_dev->prof_cur == aw_dev->prof_index) && +- (force_up_en == AW88399_FORCE_UPDATE_OFF)) { +- dev_dbg(aw_dev->dev, "scene no change, not update"); +- return 0; +- } +- +- if (aw_dev->fw_status == AW88399_DEV_FW_FAILED) { +- dev_err(aw_dev->dev, "fw status[%d] error", aw_dev->fw_status); +- return -EPERM; +- } +- +- ret = aw88399_dev_get_prof_name(aw_dev, aw_dev->prof_index, &prof_name); +- if (ret) +- return ret; +- +- dev_dbg(aw_dev->dev, "start update %s", prof_name); +- +- ret = aw88399_dev_get_prof_data(aw_dev, aw_dev->prof_index, &prof_index_desc); +- if (ret) +- return ret; +- +- /* update reg */ +- sec_desc = prof_index_desc->sec_desc; +- ret = aw_dev_reg_update(aw88399, sec_desc[AW88395_DATA_TYPE_REG].data, +- sec_desc[AW88395_DATA_TYPE_REG].len); +- if (ret) { +- dev_err(aw_dev->dev, "update reg failed"); +- return ret; +- } +- +- aw88399_dev_mute(aw_dev, true); +- +- if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) +- aw_dev_dsp_enable(aw_dev, false); +- +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); +- +- ret = aw_dev_check_sram(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "check sram failed"); +- goto error; +- } +- +- if (up_dsp_fw_en) { +- dev_dbg(aw_dev->dev, "fw_ver: [%x]", prof_index_desc->fw_ver); +- ret = aw_dev_dsp_update_fw(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_FW].data, +- sec_desc[AW88395_DATA_TYPE_DSP_FW].len); +- if (ret) { +- dev_err(aw_dev->dev, "update dsp fw failed"); +- goto error; +- } +- } +- +- /* update dsp config */ +- ret = aw_dev_dsp_update_cfg(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_CFG].data, +- sec_desc[AW88395_DATA_TYPE_DSP_CFG].len); +- if (ret) { +- dev_err(aw_dev->dev, "update dsp cfg failed"); +- goto error; +- } +- +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); +- +- aw_dev->prof_cur = aw_dev->prof_index; +- +- return 0; +- +-error: +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); +- return ret; +-} +- +-static void aw88399_start_pa(struct aw88399 *aw88399) +-{ +- int ret, i; +- +- for (i = 0; i < AW88399_START_RETRIES; i++) { +- ret = aw88399_dev_start(aw88399); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "aw88399 device start failed. retry = %d", i); +- ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_ON, true); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "fw update failed"); +- continue; +- } +- } else { +- dev_dbg(aw88399->aw_pa->dev, "start success\n"); +- break; +- } +- } +-} +- +-static void aw88399_startup_work(struct work_struct *work) +-{ +- struct aw88399 *aw88399 = +- container_of(work, struct aw88399, start_work.work); +- +- mutex_lock(&aw88399->lock); +- aw88399_start_pa(aw88399); +- mutex_unlock(&aw88399->lock); +-} +- +-static void aw88399_start(struct aw88399 *aw88399, bool sync_start) +-{ +- int ret; +- +- if (aw88399->aw_pa->fw_status != AW88399_DEV_FW_OK) +- return; +- +- if (aw88399->aw_pa->status == AW88399_DEV_PW_ON) +- return; +- +- ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_OFF, true); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "fw update failed."); +- return; +- } +- +- if (sync_start == AW88399_SYNC_START) +- aw88399_start_pa(aw88399); +- else +- queue_delayed_work(system_dfl_wq, +- &aw88399->start_work, +- AW88399_START_WORK_DELAY_MS); +-} +- +-static int aw_dev_check_sysint(struct aw_device *aw_dev) +-{ +- u16 reg_val; +- +- aw_dev_get_int_status(aw_dev, ®_val); +- if (reg_val & AW88399_BIT_SYSINT_CHECK) { +- dev_err(aw_dev->dev, "pa stop check fail:0x%04x", reg_val); +- return -EINVAL; +- } +- +- return 0; +-} +- +-static int aw88399_stop(struct aw_device *aw_dev) +-{ +- struct aw_sec_data_desc *dsp_cfg = +- &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_CFG]; +- struct aw_sec_data_desc *dsp_fw = +- &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_FW]; +- int int_st; +- +- if (aw_dev->status == AW88399_DEV_PW_OFF) { +- dev_dbg(aw_dev->dev, "already power off"); +- return 0; +- } +- +- aw_dev->status = AW88399_DEV_PW_OFF; +- +- aw88399_dev_mute(aw_dev, true); +- usleep_range(AW88399_4000_US, AW88399_4000_US + 100); +- +- aw_dev_i2s_tx_enable(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 100); +- +- int_st = aw_dev_check_sysint(aw_dev); +- +- aw_dev_dsp_enable(aw_dev, false); +- +- aw_dev_amppd(aw_dev, true); +- +- if (int_st) { +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); +- aw_dev_dsp_update_fw(aw_dev, dsp_fw->data, dsp_fw->len); +- aw_dev_dsp_update_cfg(aw_dev, dsp_cfg->data, dsp_cfg->len); +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); +- } +- +- aw_dev_pwd(aw_dev, true); +- +- return 0; +-} +- + static struct snd_soc_dai_driver aw88399_dai[] = { + { + .name = "aw88399-aif", +@@ -1702,11 +498,10 @@ static int aw88399_profile_set(struct snd_kcontrol *kcontrol, + struct aw88399 *aw88399 = snd_soc_component_get_drvdata(codec); + int ret; + +- mutex_lock(&aw88399->lock); ++ guard(mutex)(&aw88399->lock); + ret = aw88399_dev_set_profile_index(aw88399->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88399->lock); + return 0; + } + +@@ -1715,8 +510,6 @@ static int aw88399_profile_set(struct snd_kcontrol *kcontrol, + aw88399_start(aw88399, AW88399_SYNC_START); + } + +- mutex_unlock(&aw88399->lock); +- + return 1; + } + +@@ -1869,86 +662,6 @@ static int aw88399_calib_set(struct snd_kcontrol *kcontrol, + return 0; + } + +-static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- int ret; +- +- ret = aw88395_dev_cfg_load(aw_dev, aw_cfg); +- if (ret) { +- dev_err(aw_dev->dev, "aw_dev acf parse failed"); +- return -EINVAL; +- } +- aw_dev->fade_in_time = AW88399_1000_US / 10; +- aw_dev->fade_out_time = AW88399_1000_US >> 1; +- aw_dev->prof_cur = aw_dev->prof_info.prof_desc[0].id; +- aw_dev->prof_index = aw_dev->prof_info.prof_desc[0].id; +- +- ret = aw88399_dev_fw_update(aw88399, AW88399_FORCE_UPDATE_ON, AW88399_DSP_FW_UPDATE_ON); +- if (ret) { +- dev_err(aw_dev->dev, "fw update failed ret = %d\n", ret); +- return ret; +- } +- +- aw88399_dev_mute(aw_dev, true); +- +- /* close tx feedback */ +- aw_dev_i2s_tx_enable(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 100); +- +- /* enable amppd */ +- aw_dev_amppd(aw_dev, true); +- +- /* close dsp */ +- aw_dev_dsp_enable(aw_dev, false); +- /* set power down */ +- aw_dev_pwd(aw_dev, true); +- +- return 0; +-} +- +-static int aw88399_request_firmware_file(struct aw88399 *aw88399) +-{ +- const struct firmware *cont = NULL; +- int ret; +- +- aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; +- +- ret = request_firmware(&cont, AW88399_ACF_FILE, aw88399->aw_pa->dev); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "request [%s] failed!", AW88399_ACF_FILE); +- return ret; +- } +- +- dev_dbg(aw88399->aw_pa->dev, "loaded %s - size: %zu\n", +- AW88399_ACF_FILE, cont ? cont->size : 0); +- +- aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, +- struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw88399->aw_cfg) { +- release_firmware(cont); +- return -ENOMEM; +- } +- aw88399->aw_cfg->len = (int)cont->size; +- memcpy(aw88399->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); +- +- ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "load [%s] failed!", AW88399_ACF_FILE); +- return ret; +- } +- +- mutex_lock(&aw88399->lock); +- /* aw device init */ +- ret = aw88399_dev_init(aw88399, aw88399->aw_cfg); +- if (ret) +- dev_err(aw88399->aw_pa->dev, "dev init failed"); +- mutex_unlock(&aw88399->lock); +- +- return ret; +-} +- + static const struct snd_kcontrol_new aw88399_controls[] = { + SOC_SINGLE_EXT("PCM Playback Volume", AW88399_SYSCTRL2_REG, + 6, AW88399_MUTE_VOL, 0, aw88399_volume_get, +@@ -1975,7 +688,7 @@ static int aw88399_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88399 *aw88399 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88399->lock); ++ guard(mutex)(&aw88399->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88399_start(aw88399, AW88399_ASYNC_START); +@@ -1986,7 +699,6 @@ static int aw88399_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88399->lock); + + return 0; + } +@@ -2040,70 +752,6 @@ static const struct snd_soc_component_driver soc_codec_dev_aw88399 = { + .num_controls = ARRAY_SIZE(aw88399_controls), + }; + +-static void aw88399_hw_reset(struct aw88399 *aw88399) +-{ +- if (aw88399->reset_gpio) { +- gpiod_set_value_cansleep(aw88399->reset_gpio, 1); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- gpiod_set_value_cansleep(aw88399->reset_gpio, 0); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- gpiod_set_value_cansleep(aw88399->reset_gpio, 1); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- } +-} +- +-static void aw88399_parse_channel_dt(struct aw_device *aw_dev) +-{ +- struct device_node *np = aw_dev->dev->of_node; +- u32 channel_value; +- +- of_property_read_u32(np, "awinic,audio-channel", &channel_value); +- aw_dev->channel = channel_value; +-} +- +-static int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap) +-{ +- struct aw_device *aw_dev; +- unsigned int chip_id; +- int ret; +- +- ret = regmap_read(regmap, AW88399_ID_REG, &chip_id); +- if (ret) { +- dev_err(&i2c->dev, "%s read chipid error. ret = %d", __func__, ret); +- return ret; +- } +- if (chip_id != AW88399_CHIP_ID) { +- dev_err(&i2c->dev, "unsupported device"); +- return -ENXIO; +- } +- dev_dbg(&i2c->dev, "chip id = %x\n", chip_id); +- +- aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL); +- if (!aw_dev) +- return -ENOMEM; +- aw88399->aw_pa = aw_dev; +- +- aw_dev->i2c = i2c; +- aw_dev->dev = &i2c->dev; +- aw_dev->regmap = regmap; +- mutex_init(&aw_dev->dsp_lock); +- +- aw_dev->chip_id = chip_id; +- aw_dev->acf = NULL; +- aw_dev->prof_info.prof_desc = NULL; +- aw_dev->prof_info.count = 0; +- aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID; +- aw_dev->channel = AW88399_DEV_DEFAULT_CH; +- aw_dev->fw_status = AW88399_DEV_FW_FAILED; +- +- aw_dev->fade_step = AW88399_VOLUME_STEP_DB; +- aw_dev->volume_desc.ctl_volume = AW88399_VOL_DEFAULT_VALUE; +- +- aw88399_parse_channel_dt(aw_dev); +- +- return 0; +-} +- + static int aw88399_i2c_probe(struct i2c_client *i2c) + { + struct aw88399 *aw88399; +diff --git a/sound/soc/codecs/aw88399.h b/sound/soc/codecs/aw88399.h +--- a/sound/soc/codecs/aw88399.h ++++ b/sound/soc/codecs/aw88399.h +@@ -10,512 +10,10 @@ + #ifndef __AW88399_H__ + #define __AW88399_H__ + +-/* registers list */ +-#define AW88399_ID_REG (0x00) +-#define AW88399_SYSST_REG (0x01) +-#define AW88399_SYSINT_REG (0x02) +-#define AW88399_SYSINTM_REG (0x03) +-#define AW88399_SYSCTRL_REG (0x04) +-#define AW88399_SYSCTRL2_REG (0x05) +-#define AW88399_I2SCTRL1_REG (0x06) +-#define AW88399_I2SCTRL2_REG (0x07) +-#define AW88399_I2SCTRL3_REG (0x08) +-#define AW88399_DACCFG1_REG (0x09) +-#define AW88399_DACCFG2_REG (0x0A) +-#define AW88399_DACCFG3_REG (0x0B) +-#define AW88399_DACCFG4_REG (0x0C) +-#define AW88399_DACCFG5_REG (0x0D) +-#define AW88399_DACCFG6_REG (0x0E) +-#define AW88399_DACCFG7_REG (0x0F) +-#define AW88399_MPDCFG1_REG (0x10) +-#define AW88399_MPDCFG2_REG (0x11) +-#define AW88399_MPDCFG3_REG (0x12) +-#define AW88399_MPDCFG4_REG (0x13) +-#define AW88399_PWMCTRL1_REG (0x14) +-#define AW88399_PWMCTRL2_REG (0x15) +-#define AW88399_PWMCTRL3_REG (0x16) +-#define AW88399_I2SCFG1_REG (0x17) +-#define AW88399_DBGCTRL_REG (0x18) +-#define AW88399_HAGCST_REG (0x20) +-#define AW88399_VBAT_REG (0x21) +-#define AW88399_TEMP_REG (0x22) +-#define AW88399_PVDD_REG (0x23) +-#define AW88399_ISNDAT_REG (0x24) +-#define AW88399_VSNDAT_REG (0x25) +-#define AW88399_I2SINT_REG (0x26) +-#define AW88399_I2SCAPCNT_REG (0x27) +-#define AW88399_ANASTA1_REG (0x28) +-#define AW88399_ANASTA2_REG (0x29) +-#define AW88399_ANASTA3_REG (0x2A) +-#define AW88399_TESTDET_REG (0x2B) +-#define AW88399_DSMCFG1_REG (0x30) +-#define AW88399_DSMCFG2_REG (0x31) +-#define AW88399_DSMCFG3_REG (0x32) +-#define AW88399_DSMCFG4_REG (0x33) +-#define AW88399_DSMCFG5_REG (0x34) +-#define AW88399_DSMCFG6_REG (0x35) +-#define AW88399_DSMCFG7_REG (0x36) +-#define AW88399_DSMCFG8_REG (0x37) +-#define AW88399_TESTIN_REG (0x38) +-#define AW88399_TESTOUT_REG (0x39) +-#define AW88399_MEMTEST_REG (0x3A) +-#define AW88399_VSNCTRL1_REG (0x3B) +-#define AW88399_ISNCTRL1_REG (0x3C) +-#define AW88399_ISNCTRL2_REG (0x3D) +-#define AW88399_DSPMADD_REG (0x40) +-#define AW88399_DSPMDAT_REG (0x41) +-#define AW88399_WDT_REG (0x42) +-#define AW88399_ACR1_REG (0x43) +-#define AW88399_ACR2_REG (0x44) +-#define AW88399_ASR1_REG (0x45) +-#define AW88399_ASR2_REG (0x46) +-#define AW88399_DSPCFG_REG (0x47) +-#define AW88399_ASR3_REG (0x48) +-#define AW88399_ASR4_REG (0x49) +-#define AW88399_DSPVCALB_REG (0x4A) +-#define AW88399_CRCCTRL_REG (0x4B) +-#define AW88399_DSPDBG1_REG (0x4C) +-#define AW88399_DSPDBG2_REG (0x4D) +-#define AW88399_DSPDBG3_REG (0x4E) +-#define AW88399_PLLCTRL1_REG (0x50) +-#define AW88399_PLLCTRL2_REG (0x51) +-#define AW88399_PLLCTRL3_REG (0x52) +-#define AW88399_CDACTRL1_REG (0x53) +-#define AW88399_CDACTRL2_REG (0x54) +-#define AW88399_CDACTRL3_REG (0x55) +-#define AW88399_SADCCTRL1_REG (0x56) +-#define AW88399_SADCCTRL2_REG (0x57) +-#define AW88399_BOPCTRL1_REG (0x58) +-#define AW88399_BOPCTRL2_REG (0x5A) +-#define AW88399_BOPCTRL3_REG (0x5B) +-#define AW88399_BOPCTRL4_REG (0x5C) +-#define AW88399_BOPCTRL5_REG (0x5D) +-#define AW88399_BOPCTRL6_REG (0x5E) +-#define AW88399_BOPCTRL7_REG (0x5F) +-#define AW88399_BSTCTRL1_REG (0x60) +-#define AW88399_BSTCTRL2_REG (0x61) +-#define AW88399_BSTCTRL3_REG (0x62) +-#define AW88399_BSTCTRL4_REG (0x63) +-#define AW88399_BSTCTRL5_REG (0x64) +-#define AW88399_BSTCTRL6_REG (0x65) +-#define AW88399_BSTCTRL7_REG (0x66) +-#define AW88399_BSTCTRL8_REG (0x67) +-#define AW88399_BSTCTRL9_REG (0x68) +-#define AW88399_BSTCTRL10_REG (0x69) +-#define AW88399_CPCTRL_REG (0x6A) +-#define AW88399_EFWH_REG (0x6C) +-#define AW88399_EFWM2_REG (0x6D) +-#define AW88399_EFWM1_REG (0x6E) +-#define AW88399_EFWL_REG (0x6F) +-#define AW88399_TESTCTRL1_REG (0x70) +-#define AW88399_TESTCTRL2_REG (0x71) +-#define AW88399_EFCTRL1_REG (0x72) +-#define AW88399_EFCTRL2_REG (0x73) +-#define AW88399_EFRH4_REG (0x74) +-#define AW88399_EFRH3_REG (0x75) +-#define AW88399_EFRH2_REG (0x76) +-#define AW88399_EFRH1_REG (0x77) +-#define AW88399_EFRL4_REG (0x78) +-#define AW88399_EFRL3_REG (0x79) +-#define AW88399_EFRL2_REG (0x7A) +-#define AW88399_EFRL1_REG (0x7B) +-#define AW88399_TM_REG (0x7C) +-#define AW88399_TM2_REG (0x7D) +- +-#define AW88399_REG_MAX (0x7E) +-#define AW88399_MUTE_VOL (1023) +- +-#define AW88399_DSP_CFG_ADDR (0x9B00) +-#define AW88399_DSP_REG_CFG_ADPZ_RA (0x9B68) +-#define AW88399_DSP_FW_ADDR (0x8980) +-#define AW88399_DSP_ROM_CHECK_ADDR (0x1F40) +-#define AW88399_DSP_ROM_CHECK_DATA (0x4638) +- +-#define AW88399_CALI_RE_HBITS_MASK (~(0xFFFF0000)) +-#define AW88399_CALI_RE_HBITS_SHIFT (16) +- +-#define AW88399_CALI_RE_LBITS_MASK (~(0xFFFF)) +-#define AW88399_CALI_RE_LBITS_SHIFT (0) +- +-#define AW88399_I2STXEN_START_BIT (9) +-#define AW88399_I2STXEN_BITS_LEN (1) +-#define AW88399_I2STXEN_MASK \ +- (~(((1<> (shift)) +-#define AW88399_SHOW_RE_TO_DSP_RE(re, shift) (((re) << shift) / (1000)) +-#define AW88399_CRC_CHECK_PASS_VAL (0x4) +- +-#define AW88399_CRC_CFG_BASE_ADDR (0xD80) +-#define AW88399_CRC_FW_BASE_ADDR (0x4C0) +-#define AW88399_ACF_FILE "aw88399_acf.bin" +-#define AW88399_DEV_SYSST_CHECK_MAX (10) +-#define AW88399_CHIP_ID 0x2183 ++#include + + #define AW88399_I2C_NAME "aw88399" + +-#define AW88399_START_RETRIES (5) +-#define AW88399_START_WORK_DELAY_MS (0) +- + #define AW88399_RATES (SNDRV_PCM_RATE_8000_48000 | \ + SNDRV_PCM_RATE_96000) + #define AW88399_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ +@@ -550,80 +48,4 @@ + .put = profile_set, \ + } + +-enum { +- AW_EF_AND_CHECK = 0, +- AW_EF_OR_CHECK, +-}; +- +-enum { +- AW88399_DEV_VDSEL_DAC = 0, +- AW88399_DEV_VDSEL_VSENSE = 32, +-}; +- +-enum { +- AW88399_DSP_CRC_NA = 0, +- AW88399_DSP_CRC_OK = 1, +-}; +- +-enum { +- AW88399_DSP_FW_UPDATE_OFF = 0, +- AW88399_DSP_FW_UPDATE_ON = 1, +-}; +- +-enum { +- AW88399_FORCE_UPDATE_OFF = 0, +- AW88399_FORCE_UPDATE_ON = 1, +-}; +- +-enum { +- AW88399_1000_US = 1000, +- AW88399_2000_US = 2000, +- AW88399_3000_US = 3000, +- AW88399_4000_US = 4000, +-}; +- +-enum AW88399_DEV_STATUS { +- AW88399_DEV_PW_OFF = 0, +- AW88399_DEV_PW_ON, +-}; +- +-enum AW88399_DEV_FW_STATUS { +- AW88399_DEV_FW_FAILED = 0, +- AW88399_DEV_FW_OK, +-}; +- +-enum AW88399_DEV_MEMCLK { +- AW88399_DEV_MEMCLK_OSC = 0, +- AW88399_DEV_MEMCLK_PLL = 1, +-}; +- +-enum AW88399_DEV_DSP_CFG { +- AW88399_DEV_DSP_WORK = 0, +- AW88399_DEV_DSP_BYPASS = 1, +-}; +- +-enum { +- AW88399_NOT_RCV_MODE = 0, +- AW88399_RCV_MODE = 1, +-}; +- +-enum { +- AW88399_SYNC_START = 0, +- AW88399_ASYNC_START, +-}; +- +-struct aw88399 { +- struct aw_device *aw_pa; +- struct mutex lock; +- struct gpio_desc *reset_gpio; +- struct delayed_work start_work; +- struct regmap *regmap; +- struct aw_container *aw_cfg; +- +- unsigned int check_val; +- unsigned int crc_init_val; +- unsigned int vcalb_init_val; +- unsigned int dither_st; +-}; +- +-#endif ++#endif /* __AW88399_H__ */ +diff --git a/sound/soc/codecs/bt-sco.c b/sound/soc/codecs/bt-sco.c +--- a/sound/soc/codecs/bt-sco.c ++++ b/sound/soc/codecs/bt-sco.c +@@ -85,13 +85,9 @@ static int bt_sco_probe(struct platform_device *pdev) + } + + static const struct platform_device_id bt_sco_driver_ids[] = { +- { +- .name = "dfbmcs320", +- }, +- { +- .name = "bt-sco", +- }, +- {}, ++ { .name = "dfbmcs320" }, ++ { .name = "bt-sco" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, bt_sco_driver_ids); + +diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c +--- a/sound/soc/codecs/cpcap.c ++++ b/sound/soc/codecs/cpcap.c +@@ -26,14 +26,6 @@ + /* Register 9 - CPCAP_REG_INTS2 --- Interrupt Sense 2 */ + #define CPCAP_BIT_PTT_S 11 /* Push To Talk */ + +-/* Register 512 CPCAP_REG_VAUDIOC --- Audio Regulator and Bias Voltage */ +-#define CPCAP_BIT_AUDIO_LOW_PWR 6 +-#define CPCAP_BIT_AUD_LOWPWR_SPEED 5 +-#define CPCAP_BIT_VAUDIOPRISTBY 4 +-#define CPCAP_BIT_VAUDIO_MODE1 2 +-#define CPCAP_BIT_VAUDIO_MODE0 1 +-#define CPCAP_BIT_V_AUDIO_EN 0 +- + /* Register 513 CPCAP_REG_CC --- CODEC */ + #define CPCAP_BIT_CDC_CLK2 15 + #define CPCAP_BIT_CDC_CLK1 14 +@@ -239,7 +231,6 @@ struct cpcap_reg_info { + }; + + static const struct cpcap_reg_info cpcap_default_regs[] = { +- { CPCAP_REG_VAUDIOC, 0x003F, 0x0000 }, + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CDI, 0xBFFF, 0x0000 }, +@@ -1265,6 +1256,7 @@ static int cpcap_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_component *component = dai->component; + struct device *dev = component->dev; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); +@@ -1298,7 +1290,7 @@ static int cpcap_voice_hw_params(struct snd_pcm_substream *substream, + return err; + } + +- return 0; ++ return snd_soc_runtime_set_dai_fmt(rtd, rtd->dai_link->dai_fmt); + } + + static int cpcap_voice_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, +@@ -1391,121 +1383,8 @@ static int cpcap_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, + return 0; + } + +- +-/* +- * Configure codec for voice call if requested. +- * +- * We can configure most with snd_soc_dai_set_sysclk(), snd_soc_dai_set_fmt() +- * and snd_soc_dai_set_tdm_slot(). This function configures the rest of the +- * cpcap related hardware as CPU is not involved in the voice call. +- */ +-static int cpcap_voice_call(struct cpcap_audio *cpcap, struct snd_soc_dai *dai, +- bool voice_call) +-{ +- int mask, err; +- +- /* Modem to codec VAUDIO_MODE1 */ +- mask = BIT(CPCAP_BIT_VAUDIO_MODE1); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_VAUDIOC, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Clear MIC1_MUX for call */ +- mask = BIT(CPCAP_BIT_MIC1_MUX); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, +- mask, voice_call ? 0 : mask); +- if (err) +- return err; +- +- /* Set MIC2_MUX for call */ +- mask = BIT(CPCAP_BIT_MB_ON1L) | BIT(CPCAP_BIT_MB_ON1R) | +- BIT(CPCAP_BIT_MIC2_MUX) | BIT(CPCAP_BIT_MIC2_PGA_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Enable LDSP for call */ +- mask = BIT(CPCAP_BIT_A2_LDSP_L_EN) | BIT(CPCAP_BIT_A2_LDSP_R_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXOA, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Enable CPCAP_BIT_PGA_CDC_EN for call */ +- mask = BIT(CPCAP_BIT_PGA_CDC_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXCOA, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Unmute voice for call */ +- if (dai) { +- err = snd_soc_dai_digital_mute(dai, !voice_call, +- SNDRV_PCM_STREAM_PLAYBACK); +- if (err) +- return err; +- } +- +- /* Set modem to codec mic CDC and HPF for call */ +- mask = BIT(CPCAP_BIT_MIC2_CDC_EN) | BIT(CPCAP_BIT_CDC_EN_RX) | +- BIT(CPCAP_BIT_AUDOHPF_1) | BIT(CPCAP_BIT_AUDOHPF_0) | +- BIT(CPCAP_BIT_AUDIHPF_1) | BIT(CPCAP_BIT_AUDIHPF_0); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CC, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Enable modem to codec CDC for call*/ +- mask = BIT(CPCAP_BIT_CDC_CLK_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, +- mask, voice_call ? mask : 0); +- +- return err; +-} +- +-static int cpcap_voice_set_tdm_slot(struct snd_soc_dai *dai, +- unsigned int tx_mask, unsigned int rx_mask, +- int slots, int slot_width) +-{ +- struct snd_soc_component *component = dai->component; +- struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); +- int err, ts_mask, mask; +- bool voice_call; +- +- /* +- * Primitive test for voice call, probably needs more checks +- * later on for 16-bit calls detected, Bluetooth headset etc. +- */ +- if (tx_mask == 0 && rx_mask == 1 && slot_width == 8) +- voice_call = true; +- else +- voice_call = false; +- +- ts_mask = 0x7 << CPCAP_BIT_MIC2_TIMESLOT0; +- ts_mask |= 0x7 << CPCAP_BIT_MIC1_RX_TIMESLOT0; +- +- mask = (tx_mask & 0x7) << CPCAP_BIT_MIC2_TIMESLOT0; +- mask |= (rx_mask & 0x7) << CPCAP_BIT_MIC1_RX_TIMESLOT0; +- +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, +- ts_mask, mask); +- if (err) +- return err; +- +- err = cpcap_set_samprate(cpcap, CPCAP_DAI_VOICE, slot_width * 1000); +- if (err) +- return err; +- +- err = cpcap_voice_call(cpcap, dai, voice_call); +- if (err) +- return err; +- +- return 0; +-} +- +-static int cpcap_voice_set_mute(struct snd_soc_dai *dai, int mute, int direction) ++static int cpcap_voice_set_mute(struct snd_soc_dai *dai, ++ int mute, int direction) + { + struct snd_soc_component *component = dai->component; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); +@@ -1526,7 +1405,6 @@ static const struct snd_soc_dai_ops cpcap_dai_voice_ops = { + .hw_params = cpcap_voice_hw_params, + .set_sysclk = cpcap_voice_set_dai_sysclk, + .set_fmt = cpcap_voice_set_dai_fmt, +- .set_tdm_slot = cpcap_voice_set_tdm_slot, + .mute_stream = cpcap_voice_set_mute, + .no_capture_mute = 1, + }; +diff --git a/sound/soc/codecs/cros_ec_codec.c b/sound/soc/codecs/cros_ec_codec.c +--- a/sound/soc/codecs/cros_ec_codec.c ++++ b/sound/soc/codecs/cros_ec_codec.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -319,10 +320,18 @@ static int i2s_rx_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + (uint8_t *)&p, sizeof(p), NULL, 0); + } + ++static const u64 i2s_rx_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops i2s_rx_dai_ops = { + .hw_params = i2s_rx_hw_params, + .set_fmt = i2s_rx_set_fmt, + .set_bclk_ratio = i2s_rx_set_bclk_ratio, ++ .auto_selectable_formats = &i2s_rx_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static int i2s_rx_event(struct snd_soc_dapm_widget *w, +@@ -608,10 +617,10 @@ static void wov_copy_work(struct work_struct *w) + container_of(w, struct cros_ec_codec_priv, wov_copy_work.work); + int ret; + +- mutex_lock(&priv->wov_dma_lock); ++ guard(mutex)(&priv->wov_dma_lock); + if (!priv->wov_substream) { + dev_warn(priv->dev, "no pcm substream\n"); +- goto leave; ++ return; + } + + if (ec_codec_capable(priv, EC_CODEC_CAP_WOV_AUDIO_SHM)) +@@ -624,8 +633,6 @@ static void wov_copy_work(struct work_struct *w) + msecs_to_jiffies(10)); + else if (ret) + dev_err(priv->dev, "failed to read audio data\n"); +-leave: +- mutex_unlock(&priv->wov_dma_lock); + } + + static int wov_enable_get(struct snd_kcontrol *kcontrol, +@@ -895,12 +902,11 @@ static int wov_pcm_hw_params(struct snd_soc_component *component, + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->wov_dma_lock); ++ guard(mutex)(&priv->wov_dma_lock); + priv->wov_substream = substream; + priv->wov_rp = priv->wov_wp = 0; + priv->wov_dma_offset = 0; + priv->wov_burst_read = true; +- mutex_unlock(&priv->wov_dma_lock); + + return 0; + } +@@ -911,10 +917,10 @@ static int wov_pcm_hw_free(struct snd_soc_component *component, + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->wov_dma_lock); +- wov_queue_dequeue(priv, wov_queue_size(priv)); +- priv->wov_substream = NULL; +- mutex_unlock(&priv->wov_dma_lock); ++ scoped_guard(mutex, &priv->wov_dma_lock) { ++ wov_queue_dequeue(priv, wov_queue_size(priv)); ++ priv->wov_substream = NULL; ++ } + + cancel_delayed_work_sync(&priv->wov_copy_work); + +diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c +--- a/sound/soc/codecs/cs-amp-lib.c ++++ b/sound/soc/codecs/cs-amp-lib.c +@@ -7,6 +7,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -83,10 +84,12 @@ static int cs_amp_write_cal_coeff(struct cs_dsp *dsp, + KUNIT_STATIC_STUB_REDIRECT(cs_amp_write_cal_coeff, dsp, controls, ctl_name, val); + + if (IS_REACHABLE(CONFIG_FW_CS_DSP)) { +- mutex_lock(&dsp->pwr_lock); +- cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id); +- ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval)); +- mutex_unlock(&dsp->pwr_lock); ++ scoped_guard(mutex, &dsp->pwr_lock) { ++ cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, ++ controls->mem_region, ++ controls->alg_id); ++ ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval)); ++ } + + if (ret < 0) { + dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret); +diff --git a/sound/soc/codecs/cs35l32.c b/sound/soc/codecs/cs35l32.c +--- a/sound/soc/codecs/cs35l32.c ++++ b/sound/soc/codecs/cs35l32.c +@@ -538,7 +538,15 @@ static int cs35l32_runtime_resume(struct device *dev) + gpiod_set_value_cansleep(cs35l32->reset_gpio, 1); + + regcache_cache_only(cs35l32->regmap, false); +- regcache_sync(cs35l32->regmap); ++ ret = regcache_sync(cs35l32->regmap); ++ if (ret) { ++ regcache_cache_only(cs35l32->regmap, true); ++ regcache_mark_dirty(cs35l32->regmap); ++ gpiod_set_value_cansleep(cs35l32->reset_gpio, 0); ++ regulator_bulk_disable(ARRAY_SIZE(cs35l32->supplies), ++ cs35l32->supplies); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/cs35l56-i2c.c b/sound/soc/codecs/cs35l56-i2c.c +--- a/sound/soc/codecs/cs35l56-i2c.c ++++ b/sound/soc/codecs/cs35l56-i2c.c +@@ -51,15 +51,7 @@ static int cs35l56_i2c_probe(struct i2c_client *client) + return dev_err_probe(cs35l56->base.dev, ret, "Failed to allocate register map\n"); + } + +- ret = cs35l56_common_probe(cs35l56); +- if (ret != 0) +- return ret; +- +- ret = cs35l56_irq_request(&cs35l56->base, client->irq); +- if (ret < 0) +- cs35l56_remove(cs35l56); +- +- return ret; ++ return cs35l56_common_probe(cs35l56, client->irq); + } + + static void cs35l56_i2c_remove(struct i2c_client *client) +diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c +--- a/sound/soc/codecs/cs35l56-sdw.c ++++ b/sound/soc/codecs/cs35l56-sdw.c +@@ -231,7 +231,7 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral) + * a soft reset. + */ + if (cs35l56->base.init_done) +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); ++ cs35l56_unmask_soundwire_interrupts(cs35l56); + + out: + pm_runtime_put_autosuspend(cs35l56->base.dev); +@@ -240,47 +240,17 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral) + static int cs35l56_sdw_interrupt(struct sdw_slave *peripheral, + struct sdw_slave_intr_status *status) + { +- struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); +- +- /* SoundWire core holds our pm_runtime when calling this function. */ +- +- dev_dbg(cs35l56->base.dev, "int control_port=%#x\n", status->control_port); +- +- if ((status->control_port & SDW_SCP_INT1_IMPL_DEF) == 0) +- return 0; +- + /* +- * Prevent bus manager suspending and possibly issuing a +- * bus-reset before the queued work has run. ++ * The IRQ itself was handled through the regmap_irq handler, this is ++ * just clearing up the additional Cirrus SoundWire registers that are ++ * not covered by the SoundWire framework or the IRQ handler itself. + */ +- pm_runtime_get_noresume(cs35l56->base.dev); +- +- /* +- * Mask and clear until it has been handled. +- * None of the interrupts are time-critical so use the +- * power-efficient queue. +- */ +- cs35l56_mask_soundwire_interrupts(peripheral); +- queue_work(system_power_efficient_wq, &cs35l56->sdw_irq_work); ++ sdw_read_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1); ++ sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); + + return 0; + } + +-static void cs35l56_sdw_irq_work(struct work_struct *work) +-{ +- struct cs35l56_private *cs35l56 = container_of(work, +- struct cs35l56_private, +- sdw_irq_work); +- +- cs35l56_irq(-1, &cs35l56->base); +- +- /* unmask interrupts */ +- if (!cs35l56->sdw_irq_no_unmask) +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); +- +- pm_runtime_put_autosuspend(cs35l56->base.dev); +-} +- + static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) + { + struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); +@@ -302,6 +272,7 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) + prop->source_ports = BIT(CS35L56_SDW1_CAPTURE_PORT); + prop->sink_ports = BIT(CS35L56_SDW1_PLAYBACK_PORT); + prop->paging_support = true; ++ prop->use_domain_irq = true; + prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; + prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY | SDW_SCP_INT1_IMPL_DEF; + +@@ -406,7 +377,7 @@ static int __maybe_unused cs35l56_sdw_runtime_resume(struct device *dev) + if (ret) + return ret; + +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); ++ cs35l56_unmask_soundwire_interrupts(cs35l56); + + return 0; + } +@@ -418,21 +389,12 @@ static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev) + if (!cs35l56->base.init_done) + return 0; + +- cs35l56_disable_sdw_interrupts(cs35l56); ++ /* runtime_resume unmasks the interrupt */ ++ cs35l56_mask_soundwire_interrupts(cs35l56); + + return cs35l56_system_suspend(dev); + } + +-static int __maybe_unused cs35l56_sdw_system_resume(struct device *dev) +-{ +- struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); +- +- cs35l56->sdw_irq_no_unmask = false; +- /* runtime_resume re-enables the interrupt */ +- +- return cs35l56_system_resume(dev); +-} +- + static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_device_id *id) + { + struct device *dev = &peripheral->dev; +@@ -447,7 +409,6 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi + cs35l56->base.dev = dev; + cs35l56->sdw_peripheral = peripheral; + cs35l56->sdw_link_num = peripheral->bus->link_id; +- INIT_WORK(&cs35l56->sdw_irq_work, cs35l56_sdw_irq_work); + + dev_set_drvdata(dev, cs35l56); + +@@ -457,6 +418,7 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi + regmap_config = &cs35l56_regmap_sdw; + break; + case 0x3563: ++ case 0x3562: + regmap_config = &cs35l63_regmap_sdw; + break; + default: +@@ -483,25 +445,21 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi + /* Start in cache-only until device is enumerated */ + regcache_cache_only(cs35l56->base.regmap, true); + +- ret = cs35l56_common_probe(cs35l56); +- if (ret != 0) +- return ret; +- +- return 0; ++ return cs35l56_common_probe(cs35l56, peripheral->irq); + } + + static void cs35l56_sdw_remove(struct sdw_slave *peripheral) + { + struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); + +- cs35l56_disable_sdw_interrupts(cs35l56); ++ cs35l56_mask_soundwire_interrupts(cs35l56); + + cs35l56_remove(cs35l56); + } + + static const struct dev_pm_ops cs35l56_sdw_pm = { + SET_RUNTIME_PM_OPS(cs35l56_sdw_runtime_suspend, cs35l56_sdw_runtime_resume, NULL) +- SYSTEM_SLEEP_PM_OPS(cs35l56_sdw_system_suspend, cs35l56_sdw_system_resume) ++ SYSTEM_SLEEP_PM_OPS(cs35l56_sdw_system_suspend, cs35l56_system_resume) + LATE_SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend_late, cs35l56_system_resume_early) + /* NOIRQ stage not needed, SoundWire doesn't use a hard IRQ */ + }; +@@ -509,6 +467,7 @@ static const struct dev_pm_ops cs35l56_sdw_pm = { + static const struct sdw_device_id cs35l56_sdw_id[] = { + SDW_SLAVE_ENTRY(0x01FA, 0x3556, 0x3556), + SDW_SLAVE_ENTRY(0x01FA, 0x3557, 0x3557), ++ SDW_SLAVE_ENTRY(0x01FA, 0x3562, 0x3562), + SDW_SLAVE_ENTRY(0x01FA, 0x3563, 0x3563), + {}, + }; +diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c +--- a/sound/soc/codecs/cs35l56-shared.c ++++ b/sound/soc/codecs/cs35l56-shared.c +@@ -96,6 +96,7 @@ int cs35l56_set_patch(struct cs35l56_base *cs35l56_base) + ARRAY_SIZE(cs35l56_patch_fw)); + break; + case 0x63: ++ case 0x62: + ret = regmap_register_patch(cs35l56_base->regmap, cs35l63_patch_fw, + ARRAY_SIZE(cs35l63_patch_fw)); + break; +@@ -389,6 +390,7 @@ static void cs35l56_set_fw_reg_table(struct cs35l56_base *cs35l56_base) + } + break; + case 0x63: ++ case 0x62: + cs35l56_base->fw_reg = &cs35l63_fw_reg; + break; + } +@@ -595,6 +597,7 @@ void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire) + } + break; + case 0x63: ++ case 0x62: + regmap_multi_reg_write_bypassed(cs35l56_base->regmap, + cs35l63_system_reset_seq, + ARRAY_SIZE(cs35l63_system_reset_seq)); +@@ -613,26 +616,7 @@ void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire) + } + EXPORT_SYMBOL_NS_GPL(cs35l56_system_reset, "SND_SOC_CS35L56_SHARED"); + +-int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq) +-{ +- int ret; +- +- if (irq < 1) +- return 0; +- +- ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq, +- IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW, +- "cs35l56", cs35l56_base); +- if (!ret) +- cs35l56_base->irq = irq; +- else +- dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret); +- +- return ret; +-} +-EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, "SND_SOC_CS35L56_SHARED"); +- +-irqreturn_t cs35l56_irq(int irq, void *data) ++static irqreturn_t cs35l56_irq(int irq, void *data) + { + struct cs35l56_base *cs35l56_base = data; + unsigned int status1 = 0, status8 = 0, status20 = 0; +@@ -640,23 +624,22 @@ irqreturn_t cs35l56_irq(int irq, void *data) + unsigned int val; + int rv; + +- irqreturn_t ret = IRQ_NONE; +- + if (!cs35l56_base->init_done) + return IRQ_NONE; + +- mutex_lock(&cs35l56_base->irq_lock); ++ guard(mutex)(&cs35l56_base->irq_lock); + +- rv = pm_runtime_resume_and_get(cs35l56_base->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED(cs35l56_base->dev, pm); ++ rv = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (rv < 0) { + dev_err(cs35l56_base->dev, "irq: failed to get pm_runtime: %d\n", rv); +- goto err_unlock; ++ return IRQ_NONE; + } + + regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_STATUS, &val); + if ((val & CS35L56_IRQ1_STS_MASK) == 0) { + dev_dbg(cs35l56_base->dev, "Spurious IRQ: no pending interrupt\n"); +- goto err; ++ return IRQ_NONE; + } + + /* Ack interrupts */ +@@ -680,7 +663,7 @@ irqreturn_t cs35l56_irq(int irq, void *data) + + /* Check to see if unmasked bits are active */ + if (!status1 && !status8 && !status20) +- goto err; ++ return IRQ_NONE; + + if (status1 & CS35L56_AMP_SHORT_ERR_EINT1_MASK) + dev_crit(cs35l56_base->dev, "Amp short error\n"); +@@ -688,16 +671,27 @@ irqreturn_t cs35l56_irq(int irq, void *data) + if (status8 & CS35L56_TEMP_ERR_EINT1_MASK) + dev_crit(cs35l56_base->dev, "Overtemp error\n"); + +- ret = IRQ_HANDLED; ++ return IRQ_HANDLED; ++} + +-err: +- pm_runtime_put(cs35l56_base->dev); +-err_unlock: +- mutex_unlock(&cs35l56_base->irq_lock); ++int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq) ++{ ++ int ret; ++ ++ if (irq < 1) ++ return 0; ++ ++ ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq, ++ IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW, ++ "cs35l56", cs35l56_base); ++ if (!ret) ++ cs35l56_base->irq = irq; ++ else ++ dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret); + + return ret; + } +-EXPORT_SYMBOL_NS_GPL(cs35l56_irq, "SND_SOC_CS35L56_SHARED"); ++EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, "SND_SOC_CS35L56_SHARED"); + + int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base) + { +@@ -816,14 +810,10 @@ int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_sou + if (!cs35l56_base->init_done) + return 0; + +- if (!cs35l56_base->can_hibernate) +- goto out_sync; +- +- /* Must be done before releasing cache-only */ +- if (!is_soundwire) ++ /* Hibernate wake must be done before releasing cache-only */ ++ if (cs35l56_base->can_hibernate && !is_soundwire) + cs35l56_issue_wake_event(cs35l56_base); + +-out_sync: + ret = cs35l56_wait_for_firmware_boot(cs35l56_base); + if (ret) { + dev_err(cs35l56_base->dev, "Hibernate wake failed: %d\n", ret); +@@ -1470,6 +1460,7 @@ int cs35l56_hw_init(struct cs35l56_base *cs35l56_base) + cs35l56_base->calibration_controls = &cs35l56_calibration_controls; + break; + case 0x35A630: ++ case 0x35A620: + cs35l56_base->calibration_controls = &cs35l63_calibration_controls; + devid = devid >> 4; + break; +diff --git a/sound/soc/codecs/cs35l56-spi.c b/sound/soc/codecs/cs35l56-spi.c +--- a/sound/soc/codecs/cs35l56-spi.c ++++ b/sound/soc/codecs/cs35l56-spi.c +@@ -40,15 +40,7 @@ static int cs35l56_spi_probe(struct spi_device *spi) + if (ret) + return ret; + +- ret = cs35l56_common_probe(cs35l56); +- if (ret != 0) +- return ret; +- +- ret = cs35l56_irq_request(&cs35l56->base, spi->irq); +- if (ret < 0) +- cs35l56_remove(cs35l56); +- +- return ret; ++ return cs35l56_common_probe(cs35l56, spi->irq); + } + + static void cs35l56_spi_remove(struct spi_device *spi) +diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c +--- a/sound/soc/codecs/cs35l56.c ++++ b/sound/soc/codecs/cs35l56.c +@@ -37,48 +37,49 @@ + #include "wm_adsp.h" + #include "cs35l56.h" + +-void cs35l56_mask_soundwire_interrupts(struct sdw_slave *peripheral) ++void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56) + { + /* ++ * Mask unconditionally. ++ * + * The read of GEN_INT_STAT_1 is required as per the SoundWire spec + * for interrupt status bits to clear. + * GEN_INT_MASK_1 masks the _inputs_ to GEN_INT_STAT1. + */ +- sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0); +- sdw_read_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1); +- sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); ++ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0); ++ sdw_read_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1); ++ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); + } + EXPORT_SYMBOL_NS_GPL(cs35l56_mask_soundwire_interrupts, "SND_SOC_CS35L56_CORE"); + +-void cs35l56_unmask_soundwire_interrupts(struct sdw_slave *peripheral) ++void cs35l56_unmask_soundwire_interrupts(struct cs35l56_private *cs35l56) + { +- sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1, CS35L56_SDW_INT_MASK_CODEC_IRQ); ++ if (!cs35l56->base.irq) ++ return; ++ ++ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, ++ CS35L56_SDW_INT_MASK_CODEC_IRQ); + } + EXPORT_SYMBOL_NS_GPL(cs35l56_unmask_soundwire_interrupts, "SND_SOC_CS35L56_CORE"); + +-void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56) ++static void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56) + { + if (!cs35l56->sdw_peripheral) + return; + +- cs35l56->sdw_irq_no_unmask = true; +- flush_work(&cs35l56->sdw_irq_work); +- +- /* Mask interrupts and flush in case sdw_irq_work was queued again */ +- cs35l56_mask_soundwire_interrupts(cs35l56->sdw_peripheral); +- flush_work(&cs35l56->sdw_irq_work); ++ cs35l56_mask_soundwire_interrupts(cs35l56); ++ if (cs35l56->base.irq) ++ disable_irq(cs35l56->base.irq); + } +-EXPORT_SYMBOL_NS_GPL(cs35l56_disable_sdw_interrupts, "SND_SOC_CS35L56_CORE"); + +-void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56) ++static void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56) + { +- if (!cs35l56->sdw_peripheral) ++ if (!cs35l56->sdw_peripheral || !cs35l56->base.irq) + return; + +- cs35l56->sdw_irq_no_unmask = false; +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); ++ enable_irq(cs35l56->base.irq); ++ cs35l56_unmask_soundwire_interrupts(cs35l56); + } +-EXPORT_SYMBOL_NS_GPL(cs35l56_enable_sdw_interrupts, "SND_SOC_CS35L56_CORE"); + + static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); +@@ -828,11 +829,7 @@ static void cs35l56_patch(struct cs35l56_private *cs35l56, bool firmware_missing + { + int ret; + +- /* +- * Disable SoundWire interrupts to prevent race with IRQ work. +- * Setting sdw_irq_no_unmask prevents the handler re-enabling +- * the SoundWire interrupt. +- */ ++ /* Disable SoundWire interrupts to prevent race with IRQ handler thread */ + cs35l56_disable_sdw_interrupts(cs35l56); + + ret = cs35l56_firmware_shutdown(&cs35l56->base); +@@ -1402,6 +1399,7 @@ static int _cs35l56_component_probe(struct snd_soc_component *component) + ARRAY_SIZE(cs35l56_controls)); + break; + case 0x63: ++ case 0x62: + ret = snd_soc_add_component_controls(component, cs35l63_controls, + ARRAY_SIZE(cs35l63_controls)); + break; +@@ -1941,7 +1939,7 @@ static int cs35l56_try_get_broken_sdca_spkid_gpio(struct cs35l56_private *cs35l5 + return ret; + } + +-int cs35l56_common_probe(struct cs35l56_private *cs35l56) ++int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq) + { + int ret; + +@@ -2018,16 +2016,24 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56) + goto err_remove_wm_adsp; + } + ++ ret = cs35l56_irq_request(&cs35l56->base, irq); ++ if (ret) ++ goto err_remove_wm_adsp; ++ + ret = snd_soc_register_component(cs35l56->base.dev, + &soc_component_dev_cs35l56, + cs35l56_dai, ARRAY_SIZE(cs35l56_dai)); + if (ret < 0) { + dev_err_probe(cs35l56->base.dev, ret, "Register codec failed\n"); +- goto err_remove_wm_adsp; ++ goto err_free_irq; + } + + return 0; + ++err_free_irq: ++ if (cs35l56->base.irq) ++ devm_free_irq(cs35l56->base.dev, cs35l56->base.irq, &cs35l56->base); ++ + err_remove_wm_adsp: + wm_adsp2_remove(&cs35l56->dsp); + +diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h +--- a/sound/soc/codecs/cs35l56.h ++++ b/sound/soc/codecs/cs35l56.h +@@ -39,8 +39,6 @@ struct cs35l56_private { + struct sdw_slave *sdw_peripheral; + struct regmap *sdw_bus_regmap; + const char *fallback_fw_suffix; +- struct work_struct sdw_irq_work; +- bool sdw_irq_no_unmask; + bool soft_resetting; + bool sdw_attached; + struct completion init_completion; +@@ -65,10 +63,8 @@ static inline struct cs35l56_private *cs35l56_private_from_base(struct cs35l56_b + + extern const struct dev_pm_ops cs35l56_pm_ops_i2c_spi; + +-void cs35l56_mask_soundwire_interrupts(struct sdw_slave *peripheral); +-void cs35l56_unmask_soundwire_interrupts(struct sdw_slave *peripheral); +-void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56); +-void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56); ++void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56); ++void cs35l56_unmask_soundwire_interrupts(struct cs35l56_private *cs35l56); + + int cs35l56_system_suspend(struct device *dev); + int cs35l56_system_suspend_late(struct device *dev); +@@ -76,9 +72,8 @@ int cs35l56_system_suspend_no_irq(struct device *dev); + int cs35l56_system_resume_no_irq(struct device *dev); + int cs35l56_system_resume_early(struct device *dev); + int cs35l56_system_resume(struct device *dev); +-irqreturn_t cs35l56_irq(int irq, void *data); + int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq); +-int cs35l56_common_probe(struct cs35l56_private *cs35l56); ++int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq); + int cs35l56_init(struct cs35l56_private *cs35l56); + void cs35l56_remove(struct cs35l56_private *cs35l56); + +diff --git a/sound/soc/codecs/cs40l50-codec.c b/sound/soc/codecs/cs40l50-codec.c +--- a/sound/soc/codecs/cs40l50-codec.c ++++ b/sound/soc/codecs/cs40l50-codec.c +@@ -288,8 +288,8 @@ static int cs40l50_codec_driver_probe(struct platform_device *pdev) + } + + static const struct platform_device_id cs40l50_id[] = { +- { "cs40l50-codec", }, +- {} ++ { .name = "cs40l50-codec" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, cs40l50_id); + +diff --git a/sound/soc/codecs/cs42l42-sdw.c b/sound/soc/codecs/cs42l42-sdw.c +--- a/sound/soc/codecs/cs42l42-sdw.c ++++ b/sound/soc/codecs/cs42l42-sdw.c +@@ -481,10 +481,20 @@ static int cs42l42_sdw_runtime_resume(struct device *dev) + regcache_cache_only(cs42l42->regmap, false); + + /* Sync LATCH_TO_VP first so the VP domain registers sync correctly */ +- regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); +- regcache_sync(cs42l42->regmap); ++ ret = regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); ++ if (ret) ++ goto err_sync; ++ ++ ret = regcache_sync(cs42l42->regmap); ++ if (ret) ++ goto err_sync; + + return 0; ++ ++err_sync: ++ regcache_cache_only(cs42l42->regmap, true); ++ regcache_mark_dirty(cs42l42->regmap); ++ return ret; + } + + static int cs42l42_sdw_resume(struct device *dev) +diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c +--- a/sound/soc/codecs/cs42l42.c ++++ b/sound/soc/codecs/cs42l42.c +@@ -9,6 +9,7 @@ + * Author: Michael White + */ + ++#include + #include + #include + #include +@@ -565,7 +566,7 @@ static int cs42l42_set_jack(struct snd_soc_component *component, struct snd_soc_ + struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); + + /* Prevent race with interrupt handler */ +- mutex_lock(&cs42l42->irq_lock); ++ guard(mutex)(&cs42l42->irq_lock); + cs42l42->jack = jk; + + if (jk) { +@@ -581,7 +582,6 @@ static int cs42l42_set_jack(struct snd_soc_component *component, struct snd_soc_ + break; + } + } +- mutex_unlock(&cs42l42->irq_lock); + + return 0; + } +@@ -1668,13 +1668,10 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) + unsigned int current_button_status; + unsigned int i; + +- pm_runtime_get_sync(cs42l42->dev); +- mutex_lock(&cs42l42->irq_lock); +- if (cs42l42->suspended || !cs42l42->init_done) { +- mutex_unlock(&cs42l42->irq_lock); +- pm_runtime_put_autosuspend(cs42l42->dev); ++ guard(pm_runtime_active_auto)(cs42l42->dev); ++ guard(mutex)(&cs42l42->irq_lock); ++ if (cs42l42->suspended || !cs42l42->init_done) + return IRQ_NONE; +- } + + /* Read sticky registers to clear interurpt */ + for (i = 0; i < ARRAY_SIZE(stickies); i++) { +@@ -1774,9 +1771,6 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) + } + } + +- mutex_unlock(&cs42l42->irq_lock); +- pm_runtime_put_autosuspend(cs42l42->dev); +- + return IRQ_HANDLED; + } + EXPORT_SYMBOL_NS_GPL(cs42l42_irq_thread, "SND_SOC_CS42L42_CORE"); +@@ -2163,23 +2157,23 @@ int cs42l42_suspend(struct device *dev) + * future interrupts. This ensures a safe disable if the interrupt + * is shared. + */ +- mutex_lock(&cs42l42->irq_lock); +- cs42l42->suspended = true; ++ scoped_guard(mutex, &cs42l42->irq_lock) { ++ cs42l42->suspended = true; + +- /* Save register values that will be overwritten by shutdown sequence */ +- for (i = 0; i < ARRAY_SIZE(cs42l42_shutdown_seq); ++i) { +- regmap_read(cs42l42->regmap, cs42l42_shutdown_seq[i].reg, ®); +- save_regs[i] = (u8)reg; ++ /* Save register values that will be overwritten by shutdown sequence */ ++ for (i = 0; i < ARRAY_SIZE(cs42l42_shutdown_seq); ++i) { ++ regmap_read(cs42l42->regmap, cs42l42_shutdown_seq[i].reg, ®); ++ save_regs[i] = (u8)reg; ++ } ++ ++ /* Shutdown codec */ ++ regmap_multi_reg_write(cs42l42->regmap, ++ cs42l42_shutdown_seq, ++ ARRAY_SIZE(cs42l42_shutdown_seq)); ++ ++ /* All interrupt sources are now disabled */ + } + +- /* Shutdown codec */ +- regmap_multi_reg_write(cs42l42->regmap, +- cs42l42_shutdown_seq, +- ARRAY_SIZE(cs42l42_shutdown_seq)); +- +- /* All interrupt sources are now disabled */ +- mutex_unlock(&cs42l42->irq_lock); +- + /* Wait for power-down complete */ + msleep(CS42L42_PDN_DONE_TIME_MS); + ret = regmap_read_poll_timeout(cs42l42->regmap, +@@ -2250,13 +2244,13 @@ void cs42l42_resume_restore(struct device *dev) + regcache_cache_only(cs42l42->regmap, false); + regcache_mark_dirty(cs42l42->regmap); + +- mutex_lock(&cs42l42->irq_lock); +- /* Sync LATCH_TO_VP first so the VP domain registers sync correctly */ +- regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); +- regcache_sync(cs42l42->regmap); ++ scoped_guard(mutex, &cs42l42->irq_lock) { ++ /* Sync LATCH_TO_VP first so the VP domain registers sync correctly */ ++ regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); ++ regcache_sync(cs42l42->regmap); + +- cs42l42->suspended = false; +- mutex_unlock(&cs42l42->irq_lock); ++ cs42l42->suspended = false; ++ } + + dev_dbg(dev, "System resumed\n"); + } +diff --git a/sound/soc/codecs/cs42l43-jack.c b/sound/soc/codecs/cs42l43-jack.c +--- a/sound/soc/codecs/cs42l43-jack.c ++++ b/sound/soc/codecs/cs42l43-jack.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + + #include ++#include + #include + #include + #include +@@ -67,6 +68,21 @@ static int cs42l43_find_index(struct cs42l43_codec *priv, const char * const pro + return -EINVAL; + } + ++static void cs42l43_apply_accdet_config(struct cs42l43_codec *priv, ++ unsigned int autocontrol, ++ unsigned int pdncntl) ++{ ++ struct cs42l43 *cs42l43 = priv->core; ++ ++ regmap_update_bits(cs42l43->regmap, CS42L43_HS_BIAS_SENSE_AND_CLAMP_AUTOCONTROL, ++ CS42L43_JACKDET_MODE_MASK | CS42L43_S0_AUTO_ADCMUTE_DISABLE_MASK | ++ CS42L43_HSBIAS_SENSE_TRIP_MASK, autocontrol); ++ regmap_update_bits(cs42l43->regmap, CS42L43_PDNCNTL, ++ CS42L43_RING_SENSE_EN_MASK, pdncntl); ++ ++ dev_dbg(priv->dev, "Successfully configured accessory detect\n"); ++} ++ + int cs42l43_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, void *d) + { +@@ -80,31 +96,34 @@ int cs42l43_set_jack(struct snd_soc_component *component, + + dev_dbg(priv->dev, "Configure accessory detect\n"); + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for jack config: %d\n", ret); + return ret; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + + priv->jack_hp = jack; + +- if (!jack) +- goto done; ++ if (!jack) { ++ cs42l43_apply_accdet_config(priv, autocontrol, pdncntl); ++ return 0; ++ } + + ret = device_property_count_u32(cs42l43->dev, "cirrus,buttons-ohms"); + if (ret != -EINVAL) { + if (ret < 0) { + dev_err(priv->dev, "Property cirrus,buttons-ohms malformed: %d\n", + ret); +- goto error; ++ return ret; + } + + if (ret > CS42L43_N_BUTTONS) { + ret = -EINVAL; + dev_err(priv->dev, "Property cirrus,buttons-ohms too many entries\n"); +- goto error; ++ return ret; + } + + ret = device_property_read_u32_array(cs42l43->dev, "cirrus,buttons-ohms", +@@ -112,7 +131,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + if (ret < 0) { + dev_err(priv->dev, "Property cirrus,button-ohms malformed: %d\n", + ret); +- goto error; ++ return ret; + } + } else { + priv->buttons[0] = 70; +@@ -124,7 +143,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + ret = cs42l43_find_index(priv, "cirrus,detect-us", 50000, &priv->detect_us, + cs42l43_accdet_us, ARRAY_SIZE(cs42l43_accdet_us)); + if (ret < 0) +- goto error; ++ return ret; + + hs2 |= ret << CS42L43_AUTO_HSDET_TIME_SHIFT; + +@@ -134,7 +153,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->bias_ramp_ms, cs42l43_accdet_ramp_ms, + ARRAY_SIZE(cs42l43_accdet_ramp_ms)); + if (ret < 0) +- goto error; ++ return ret; + + hs2 |= ret << CS42L43_HSBIAS_RAMP_SHIFT; + +@@ -142,7 +161,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->bias_sense_ua, cs42l43_accdet_bias_sense, + ARRAY_SIZE(cs42l43_accdet_bias_sense)); + if (ret < 0) +- goto error; ++ return ret; + + if (priv->bias_sense_ua) + autocontrol |= ret << CS42L43_HSBIAS_SENSE_TRIP_SHIFT; +@@ -154,7 +173,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->tip_debounce_ms); + if (ret < 0 && ret != -EINVAL) { + dev_err(priv->dev, "Property cirrus,tip-debounce-ms malformed: %d\n", ret); +- goto error; ++ return ret; + } + + /* This tip sense invert is set normally, as TIPSENSE_INV already inverted */ +@@ -170,7 +189,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->tip_fall_db_ms, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + tip_deb |= ret << CS42L43_TIPSENSE_FALLING_DB_TIME_SHIFT; + +@@ -178,7 +197,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->tip_rise_db_ms, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + tip_deb |= ret << CS42L43_TIPSENSE_RISING_DB_TIME_SHIFT; + +@@ -199,7 +218,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + NULL, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + ring_deb |= ret << CS42L43_RINGSENSE_FALLING_DB_TIME_SHIFT; + +@@ -207,7 +226,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + NULL, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + ring_deb |= ret << CS42L43_RINGSENSE_RISING_DB_TIME_SHIFT; + pdncntl |= CS42L43_RING_SENSE_EN_MASK; +@@ -228,23 +247,9 @@ int cs42l43_set_jack(struct snd_soc_component *component, + CS42L43_HSBIAS_RAMP_MASK | CS42L43_HSDET_MODE_MASK | + CS42L43_AUTO_HSDET_TIME_MASK, hs2); + +-done: +- ret = 0; ++ cs42l43_apply_accdet_config(priv, autocontrol, pdncntl); + +- regmap_update_bits(cs42l43->regmap, CS42L43_HS_BIAS_SENSE_AND_CLAMP_AUTOCONTROL, +- CS42L43_JACKDET_MODE_MASK | CS42L43_S0_AUTO_ADCMUTE_DISABLE_MASK | +- CS42L43_HSBIAS_SENSE_TRIP_MASK, autocontrol); +- regmap_update_bits(cs42l43->regmap, CS42L43_PDNCNTL, +- CS42L43_RING_SENSE_EN_MASK, pdncntl); +- +- dev_dbg(priv->dev, "Successfully configured accessory detect\n"); +- +-error: +- mutex_unlock(&priv->jack_lock); +- +- pm_runtime_put_autosuspend(priv->dev); +- +- return ret; ++ return 0; + } + + static void cs42l43_start_hs_bias(struct cs42l43_codec *priv, bool type_detect) +@@ -370,22 +375,22 @@ irqreturn_t cs42l43_button_press(int irq, void *data) + { + struct cs42l43_codec *priv = data; + struct cs42l43 *cs42l43 = priv->core; +- irqreturn_t iret = IRQ_NONE; + unsigned int buttons = 0; + unsigned int val = 0; + int i, ret; + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for button press: %d\n", ret); +- return iret; ++ return IRQ_NONE; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + + if (!priv->button_detect_running) { + dev_dbg(priv->dev, "Spurious button press IRQ\n"); +- goto error; ++ return IRQ_NONE; + } + + // Wait for 2 full cycles of comb filter to ensure good reading +@@ -396,12 +401,12 @@ irqreturn_t cs42l43_button_press(int irq, void *data) + /* Bail if jack removed, the button is irrelevant and likely invalid */ + if (!cs42l43_jack_present(priv)) { + dev_dbg(priv->dev, "Button ignored due to removal\n"); +- goto error; ++ return IRQ_NONE; + } + + if (val & CS42L43_HSBIAS_CLAMP_STS_MASK) { + dev_dbg(priv->dev, "Button ignored due to bias sense\n"); +- goto error; ++ return IRQ_NONE; + } + + val = (val & CS42L43_HSDET_DC_STS_MASK) >> CS42L43_HSDET_DC_STS_SHIFT; +@@ -424,45 +429,32 @@ irqreturn_t cs42l43_button_press(int irq, void *data) + + snd_soc_jack_report(priv->jack_hp, buttons, CS42L43_JACK_BUTTONS); + +- iret = IRQ_HANDLED; +- +-error: +- mutex_unlock(&priv->jack_lock); +- +- pm_runtime_put_autosuspend(priv->dev); +- +- return iret; ++ return IRQ_HANDLED; + } + + irqreturn_t cs42l43_button_release(int irq, void *data) + { + struct cs42l43_codec *priv = data; +- irqreturn_t iret = IRQ_NONE; + int ret; + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for button release: %d\n", ret); +- return iret; ++ return IRQ_NONE; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + +- if (priv->button_detect_running) { +- dev_dbg(priv->dev, "Button release IRQ\n"); +- +- snd_soc_jack_report(priv->jack_hp, 0, CS42L43_JACK_BUTTONS); +- +- iret = IRQ_HANDLED; +- } else { ++ if (!priv->button_detect_running) { + dev_dbg(priv->dev, "Spurious button release IRQ\n"); ++ return IRQ_NONE; + } + +- mutex_unlock(&priv->jack_lock); ++ dev_dbg(priv->dev, "Button release IRQ\n"); ++ snd_soc_jack_report(priv->jack_hp, 0, CS42L43_JACK_BUTTONS); + +- pm_runtime_put_autosuspend(priv->dev); +- +- return iret; ++ return IRQ_HANDLED; + } + + void cs42l43_bias_sense_timeout(struct work_struct *work) +@@ -472,13 +464,14 @@ void cs42l43_bias_sense_timeout(struct work_struct *work) + struct cs42l43 *cs42l43 = priv->core; + int ret; + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for bias sense: %d\n", ret); + return; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + + if (cs42l43_jack_present(priv) && priv->button_detect_running) { + dev_dbg(priv->dev, "Bias sense timeout out, restore bias\n"); +@@ -491,10 +484,6 @@ void cs42l43_bias_sense_timeout(struct work_struct *work) + CS42L43_AUTO_HSBIAS_CLAMP_EN_MASK, + CS42L43_AUTO_HSBIAS_CLAMP_EN_MASK); + } +- +- mutex_unlock(&priv->jack_lock); +- +- pm_runtime_put_autosuspend(priv->dev); + } + + static const struct reg_sequence cs42l43_3pole_patch[] = { +@@ -896,9 +885,8 @@ int cs42l43_jack_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct cs42l43_codec *priv = snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + ucontrol->value.integer.value[0] = priv->jack_override; +- mutex_unlock(&priv->jack_lock); + + return 0; + } +@@ -914,17 +902,13 @@ int cs42l43_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u + if (override >= e->items) + return -EINVAL; + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + +- if (!cs42l43_jack_present(priv)) { +- mutex_unlock(&priv->jack_lock); ++ if (!cs42l43_jack_present(priv)) + return -EBUSY; +- } + +- if (override == priv->jack_override) { +- mutex_unlock(&priv->jack_lock); ++ if (override == priv->jack_override) + return 0; +- } + + priv->jack_override = override; + +@@ -984,7 +968,5 @@ int cs42l43_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u + cs42l43_jack_override_modes[override].report); + } + +- mutex_unlock(&priv->jack_lock); +- + return 1; + } +diff --git a/sound/soc/codecs/cs42l43.c b/sound/soc/codecs/cs42l43.c +--- a/sound/soc/codecs/cs42l43.c ++++ b/sound/soc/codecs/cs42l43.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1207,14 +1208,12 @@ static void cs42l43_spk_vu_sync(struct cs42l43_codec *priv) + { + struct cs42l43 *cs42l43 = priv->core; + +- mutex_lock(&priv->spk_vu_lock); ++ guard(mutex)(&priv->spk_vu_lock); + + regmap_update_bits(cs42l43->regmap, CS42L43_INTP_VOLUME_CTRL1, + CS42L43_AMP1_2_VU_MASK, CS42L43_AMP1_2_VU_MASK); + regmap_update_bits(cs42l43->regmap, CS42L43_INTP_VOLUME_CTRL1, + CS42L43_AMP1_2_VU_MASK, 0); +- +- mutex_unlock(&priv->spk_vu_lock); + } + + static int cs42l43_shutter_get(struct cs42l43_codec *priv, unsigned int shift) +@@ -1601,7 +1600,7 @@ static int cs42l43_pll_ev(struct snd_soc_dapm_widget *w, + struct cs42l43 *cs42l43 = priv->core; + int ret; + +- mutex_lock(&cs42l43->pll_lock); ++ guard(mutex)(&cs42l43->pll_lock); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +@@ -1626,8 +1625,6 @@ static int cs42l43_pll_ev(struct snd_soc_dapm_widget *w, + break; + } + +- mutex_unlock(&cs42l43->pll_lock); +- + return ret; + } + +@@ -2565,13 +2562,10 @@ static int cs42l43_set_sysclk(struct snd_soc_component *component, int clk_id, + { + struct cs42l43_codec *priv = snd_soc_component_get_drvdata(component); + struct cs42l43 *cs42l43 = priv->core; +- int ret; + +- mutex_lock(&cs42l43->pll_lock); +- ret = cs42l43_set_pll(priv, src, freq); +- mutex_unlock(&cs42l43->pll_lock); ++ guard(mutex)(&cs42l43->pll_lock); + +- return ret; ++ return cs42l43_set_pll(priv, src, freq); + } + + static int cs42l43_component_probe(struct snd_soc_component *component) +@@ -2941,8 +2935,8 @@ static const struct dev_pm_ops cs42l43_codec_pm_ops = { + }; + + static const struct platform_device_id cs42l43_codec_id_table[] = { +- { "cs42l43-codec", }, +- {} ++ { .name = "cs42l43-codec" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, cs42l43_codec_id_table); + +diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c +--- a/sound/soc/codecs/cs42l84.c ++++ b/sound/soc/codecs/cs42l84.c +@@ -10,6 +10,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -281,10 +282,9 @@ static int cs42l84_set_jack(struct snd_soc_component *component, struct snd_soc_ + struct cs42l84_private *cs42l84 = snd_soc_component_get_drvdata(component); + + /* Prevent race with interrupt handler */ +- mutex_lock(&cs42l84->irq_lock); ++ guard(mutex)(&cs42l84->irq_lock); + cs42l84->jack = jk; + snd_soc_jack_report(jk, cs42l84->hs_type, SND_JACK_HEADSET); +- mutex_unlock(&cs42l84->irq_lock); + + return 0; + } +@@ -831,7 +831,7 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data) + u8 current_ring_state; + int i; + +- mutex_lock(&cs42l84->irq_lock); ++ guard(mutex)(&cs42l84->irq_lock); + /* Read sticky registers to clear interrupt */ + for (i = 0; i < ARRAY_SIZE(stickies); i++) { + regmap_read(cs42l84->regmap, irq_params_table[i].status_addr, +@@ -902,8 +902,6 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data) + break; + } + +- mutex_unlock(&cs42l84->irq_lock); +- + return IRQ_HANDLED; + } + +@@ -919,8 +917,6 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data) + } + } + +- mutex_unlock(&cs42l84->irq_lock); +- + return IRQ_HANDLED; + } + +diff --git a/sound/soc/codecs/cs43130.c b/sound/soc/codecs/cs43130.c +--- a/sound/soc/codecs/cs43130.c ++++ b/sound/soc/codecs/cs43130.c +@@ -6,6 +6,7 @@ + * + * Authors: Li Xu + */ ++#include + #include + #include + #include +@@ -818,26 +819,26 @@ static int cs43130_dsd_hw_params(struct snd_pcm_substream *substream, + unsigned int required_clk; + u8 dsd_speed; + +- mutex_lock(&cs43130->clk_mutex); +- if (!cs43130->clk_req) { +- /* no DAI is currently using clk */ +- if (!(CS43130_MCLK_22M % params_rate(params))) +- required_clk = CS43130_MCLK_22M; +- else +- required_clk = CS43130_MCLK_24M; ++ scoped_guard(mutex, &cs43130->clk_mutex) { ++ if (!cs43130->clk_req) { ++ /* no DAI is currently using clk */ ++ if (!(CS43130_MCLK_22M % params_rate(params))) ++ required_clk = CS43130_MCLK_22M; ++ else ++ required_clk = CS43130_MCLK_24M; + +- cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); +- if (cs43130->pll_bypass) +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); +- else +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); ++ if (cs43130->pll_bypass) ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); ++ else ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ } ++ ++ cs43130->clk_req++; ++ if (cs43130->clk_req == 2) ++ cs43130_pcm_dsd_mix(true, cs43130->regmap); + } + +- cs43130->clk_req++; +- if (cs43130->clk_req == 2) +- cs43130_pcm_dsd_mix(true, cs43130->regmap); +- mutex_unlock(&cs43130->clk_mutex); +- + switch (params_rate(params)) { + case 176400: + dsd_speed = 0; +@@ -881,26 +882,26 @@ static int cs43130_hw_params(struct snd_pcm_substream *substream, + unsigned int required_clk; + u8 dsd_speed; + +- mutex_lock(&cs43130->clk_mutex); +- if (!cs43130->clk_req) { +- /* no DAI is currently using clk */ +- if (!(CS43130_MCLK_22M % params_rate(params))) +- required_clk = CS43130_MCLK_22M; +- else +- required_clk = CS43130_MCLK_24M; ++ scoped_guard(mutex, &cs43130->clk_mutex) { ++ if (!cs43130->clk_req) { ++ /* no DAI is currently using clk */ ++ if (!(CS43130_MCLK_22M % params_rate(params))) ++ required_clk = CS43130_MCLK_22M; ++ else ++ required_clk = CS43130_MCLK_24M; + +- cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); +- if (cs43130->pll_bypass) +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); +- else +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); ++ if (cs43130->pll_bypass) ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); ++ else ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ } ++ ++ cs43130->clk_req++; ++ if (cs43130->clk_req == 2) ++ cs43130_pcm_dsd_mix(true, cs43130->regmap); + } + +- cs43130->clk_req++; +- if (cs43130->clk_req == 2) +- cs43130_pcm_dsd_mix(true, cs43130->regmap); +- mutex_unlock(&cs43130->clk_mutex); +- + switch (dai->id) { + case CS43130_ASP_DOP_DAI: + case CS43130_XSP_DOP_DAI: +@@ -988,14 +989,13 @@ static int cs43130_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&cs43130->clk_mutex); ++ guard(mutex)(&cs43130->clk_mutex); + cs43130->clk_req--; + if (!cs43130->clk_req) { + /* no DAI is currently using clk */ + cs43130_change_clksrc(component, CS43130_MCLK_SRC_RCO); + cs43130_pcm_dsd_mix(false, cs43130->regmap); + } +- mutex_unlock(&cs43130->clk_mutex); + + return 0; + } +diff --git a/sound/soc/codecs/cs4349.c b/sound/soc/codecs/cs4349.c +--- a/sound/soc/codecs/cs4349.c ++++ b/sound/soc/codecs/cs4349.c +@@ -340,7 +340,13 @@ static int cs4349_runtime_resume(struct device *dev) + gpiod_set_value_cansleep(cs4349->reset_gpio, 1); + + regcache_cache_only(cs4349->regmap, false); +- regcache_sync(cs4349->regmap); ++ ret = regcache_sync(cs4349->regmap); ++ if (ret) { ++ regcache_cache_only(cs4349->regmap, true); ++ regcache_mark_dirty(cs4349->regmap); ++ gpiod_set_value_cansleep(cs4349->reset_gpio, 0); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/cs47l15.c b/sound/soc/codecs/cs47l15.c +--- a/sound/soc/codecs/cs47l15.c ++++ b/sound/soc/codecs/cs47l15.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -1285,9 +1286,8 @@ static int cs47l15_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -1317,9 +1317,8 @@ static void cs47l15_component_remove(struct snd_soc_component *component) + struct cs47l15 *cs47l15 = snd_soc_component_get_drvdata(component); + struct madera *madera = cs47l15->core.madera; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + wm_adsp2_component_remove(&cs47l15->core.adsp[0], component); + } +diff --git a/sound/soc/codecs/cs47l35.c b/sound/soc/codecs/cs47l35.c +--- a/sound/soc/codecs/cs47l35.c ++++ b/sound/soc/codecs/cs47l35.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -1566,9 +1567,8 @@ static int cs47l35_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -1600,9 +1600,8 @@ static void cs47l35_component_remove(struct snd_soc_component *component) + struct madera *madera = cs47l35->core.madera; + int i; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + for (i = 0; i < CS47L35_NUM_ADSP; i++) + wm_adsp2_component_remove(&cs47l35->core.adsp[i], component); +diff --git a/sound/soc/codecs/cs47l85.c b/sound/soc/codecs/cs47l85.c +--- a/sound/soc/codecs/cs47l85.c ++++ b/sound/soc/codecs/cs47l85.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -2504,9 +2505,8 @@ static int cs47l85_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -2537,9 +2537,8 @@ static void cs47l85_component_remove(struct snd_soc_component *component) + struct madera *madera = cs47l85->core.madera; + int i; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + for (i = 0; i < CS47L85_NUM_ADSP; i++) + wm_adsp2_component_remove(&cs47l85->core.adsp[i], component); +diff --git a/sound/soc/codecs/cs47l90.c b/sound/soc/codecs/cs47l90.c +--- a/sound/soc/codecs/cs47l90.c ++++ b/sound/soc/codecs/cs47l90.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -2423,9 +2424,8 @@ static int cs47l90_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -2456,9 +2456,8 @@ static void cs47l90_component_remove(struct snd_soc_component *component) + struct madera *madera = cs47l90->core.madera; + int i; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + for (i = 0; i < CS47L90_NUM_ADSP; i++) + wm_adsp2_component_remove(&cs47l90->core.adsp[i], component); +diff --git a/sound/soc/codecs/cs47l92.c b/sound/soc/codecs/cs47l92.c +--- a/sound/soc/codecs/cs47l92.c ++++ b/sound/soc/codecs/cs47l92.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -1892,9 +1893,8 @@ static int cs47l92_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -1922,9 +1922,8 @@ static void cs47l92_component_remove(struct snd_soc_component *component) + struct cs47l92 *cs47l92 = snd_soc_component_get_drvdata(component); + struct madera *madera = cs47l92->core.madera; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + wm_adsp2_component_remove(&cs47l92->core.adsp[0], component); + } +diff --git a/sound/soc/codecs/cs48l32.c b/sound/soc/codecs/cs48l32.c +--- a/sound/soc/codecs/cs48l32.c ++++ b/sound/soc/codecs/cs48l32.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -236,15 +237,13 @@ static int cs48l32_rate_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v + int ret; + + /* Prevent any mixer mux changes while we do this */ +- mutex_lock(&cs48l32_codec->rate_lock); ++ guard(mutex)(&cs48l32_codec->rate_lock); + + /* The write must be guarded by a number of SYSCLK cycles */ + cs48l32_spin_sysclk(cs48l32_codec); + ret = snd_soc_put_enum_double(kcontrol, ucontrol); + cs48l32_spin_sysclk(cs48l32_codec); + +- mutex_unlock(&cs48l32_codec->rate_lock); +- + return ret; + } + +@@ -2242,7 +2241,6 @@ static int cs48l32_dai_set_sysclk(struct snd_soc_dai *dai, + struct cs48l32_dai_priv *dai_priv = &cs48l32_codec->dai[dai->id - 1]; + unsigned int base = dai->driver->base; + unsigned int current_asp_rate, target_asp_rate; +- bool change_rate_domain = false; + int ret; + + if (clk_id == dai_priv->clk) +@@ -2284,19 +2282,15 @@ static int cs48l32_dai_set_sysclk(struct snd_soc_dai *dai, + + if ((current_asp_rate & CS48L32_ASP_RATE_MASK) != + (target_asp_rate & CS48L32_ASP_RATE_MASK)) { +- change_rate_domain = true; +- +- mutex_lock(&cs48l32_codec->rate_lock); + /* Guard the rate change with SYSCLK cycles */ +- cs48l32_spin_sysclk(cs48l32_codec); +- } +- +- snd_soc_component_update_bits(component, base + CS48L32_ASP_CONTROL1, +- CS48L32_ASP_RATE_MASK, target_asp_rate); +- +- if (change_rate_domain) { +- cs48l32_spin_sysclk(cs48l32_codec); +- mutex_unlock(&cs48l32_codec->rate_lock); ++ scoped_guard(mutex, &cs48l32_codec->rate_lock) { ++ cs48l32_spin_sysclk(cs48l32_codec); ++ snd_soc_component_update_bits(component, ++ base + CS48L32_ASP_CONTROL1, ++ CS48L32_ASP_RATE_MASK, ++ target_asp_rate); ++ cs48l32_spin_sysclk(cs48l32_codec); ++ } + } + } + +diff --git a/sound/soc/codecs/cx2072x.c b/sound/soc/codecs/cx2072x.c +--- a/sound/soc/codecs/cx2072x.c ++++ b/sound/soc/codecs/cx2072x.c +@@ -9,6 +9,7 @@ + // + + #include ++#include + #include + #include + #include +@@ -1408,7 +1409,7 @@ static int cx2072x_jack_status_check(void *data) + unsigned int type = 0; + int state = 0; + +- mutex_lock(&cx2072x->lock); ++ guard(mutex)(&cx2072x->lock); + + regmap_read(cx2072x->regmap, CX2072X_PORTA_PIN_SENSE, &jack); + jack = jack >> 24; +@@ -1434,8 +1435,6 @@ static int cx2072x_jack_status_check(void *data) + /* clear interrupt */ + regmap_write(cx2072x->regmap, CX2072X_UM_INTERRUPT_CRTL_E, 0x12 << 24); + +- mutex_unlock(&cx2072x->lock); +- + dev_dbg(codec->dev, "CX2072X_HSDETECT type=0x%X,Jack state = %x\n", + type, state); + return state; +diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c +--- a/sound/soc/codecs/da7213.c ++++ b/sound/soc/codecs/da7213.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -216,13 +217,10 @@ static int da7213_volsw_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_get_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_get_volsw(kcontrol, ucontrol); + } + + static int da7213_volsw_locked_put(struct snd_kcontrol *kcontrol, +@@ -230,13 +228,10 @@ static int da7213_volsw_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_put_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_put_volsw(kcontrol, ucontrol); + } + + static int da7213_enum_locked_get(struct snd_kcontrol *kcontrol, +@@ -244,13 +239,10 @@ static int da7213_enum_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_get_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_get_enum_double(kcontrol, ucontrol); + } + + static int da7213_enum_locked_put(struct snd_kcontrol *kcontrol, +@@ -258,13 +250,10 @@ static int da7213_enum_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_put_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_put_enum_double(kcontrol, ucontrol); + } + + /* ALC */ +@@ -465,9 +454,8 @@ static int da7213_tonegen_freq_get(struct snd_kcontrol *kcontrol, + __le16 val; + int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = regmap_raw_read(da7213->regmap, reg, &val, sizeof(val)); +- mutex_unlock(&da7213->ctrl_lock); ++ scoped_guard(mutex, &da7213->ctrl_lock) ++ ret = regmap_raw_read(da7213->regmap, reg, &val, sizeof(val)); + + if (ret) + return ret; +@@ -499,12 +487,11 @@ static int da7213_tonegen_freq_put(struct snd_kcontrol *kcontrol, + */ + val_new = cpu_to_le16(ucontrol->value.integer.value[0]); + +- mutex_lock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + ret = regmap_raw_read(da7213->regmap, reg, &val_old, sizeof(val_old)); + if (ret == 0 && (val_old != val_new)) + ret = regmap_raw_write(da7213->regmap, reg, + &val_new, sizeof(val_new)); +- mutex_unlock(&da7213->ctrl_lock); + + if (ret < 0) + return ret; +diff --git a/sound/soc/codecs/da7219.c b/sound/soc/codecs/da7219.c +--- a/sound/soc/codecs/da7219.c ++++ b/sound/soc/codecs/da7219.c +@@ -8,6 +8,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -256,13 +257,10 @@ static int da7219_volsw_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_get_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_get_volsw(kcontrol, ucontrol); + } + + static int da7219_volsw_locked_put(struct snd_kcontrol *kcontrol, +@@ -270,13 +268,10 @@ static int da7219_volsw_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_put_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_put_volsw(kcontrol, ucontrol); + } + + static int da7219_enum_locked_get(struct snd_kcontrol *kcontrol, +@@ -284,13 +279,10 @@ static int da7219_enum_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_get_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_get_enum_double(kcontrol, ucontrol); + } + + static int da7219_enum_locked_put(struct snd_kcontrol *kcontrol, +@@ -298,13 +290,10 @@ static int da7219_enum_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_put_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_put_enum_double(kcontrol, ucontrol); + } + + /* ALC */ +@@ -422,9 +411,8 @@ static int da7219_tonegen_freq_get(struct snd_kcontrol *kcontrol, + __le16 val; + int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = regmap_raw_read(da7219->regmap, reg, &val, sizeof(val)); +- mutex_unlock(&da7219->ctrl_lock); ++ scoped_guard(mutex, &da7219->ctrl_lock) ++ ret = regmap_raw_read(da7219->regmap, reg, &val, sizeof(val)); + + if (ret) + return ret; +@@ -456,12 +444,11 @@ static int da7219_tonegen_freq_put(struct snd_kcontrol *kcontrol, + */ + val_new = cpu_to_le16(ucontrol->value.integer.value[0]); + +- mutex_lock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + ret = regmap_raw_read(da7219->regmap, reg, &val_old, sizeof(val_old)); + if (ret == 0 && (val_old != val_new)) + ret = regmap_raw_write(da7219->regmap, reg, +- &val_new, sizeof(val_new)); +- mutex_unlock(&da7219->ctrl_lock); ++ &val_new, sizeof(val_new)); + + if (ret < 0) + return ret; +@@ -1167,15 +1154,12 @@ static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&da7219->pll_lock); ++ guard(mutex)(&da7219->pll_lock); + +- if ((da7219->clk_src == clk_id) && (da7219->mclk_rate == freq)) { +- mutex_unlock(&da7219->pll_lock); ++ if (da7219->clk_src == clk_id && da7219->mclk_rate == freq) + return 0; +- } + +- if ((freq < 2000000) || (freq > 54000000)) { +- mutex_unlock(&da7219->pll_lock); ++ if (freq < 2000000 || freq > 54000000) { + dev_err(codec_dai->dev, "Unsupported MCLK value %d\n", + freq); + return -EINVAL; +@@ -1193,7 +1177,6 @@ static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + break; + default: + dev_err(codec_dai->dev, "Unknown clock source %d\n", clk_id); +- mutex_unlock(&da7219->pll_lock); + return -EINVAL; + } + +@@ -1203,17 +1186,13 @@ static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + freq = clk_round_rate(da7219->mclk, freq); + ret = clk_set_rate(da7219->mclk, freq); + if (ret) { +- dev_err(codec_dai->dev, "Failed to set clock rate %d\n", +- freq); +- mutex_unlock(&da7219->pll_lock); ++ dev_err(codec_dai->dev, "Failed to set clock rate %d\n", freq); + return ret; + } + } + + da7219->mclk_rate = freq; + +- mutex_unlock(&da7219->pll_lock); +- + return 0; + } + +@@ -1296,13 +1275,10 @@ static int da7219_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + { + struct snd_soc_component *component = codec_dai->component; + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->pll_lock); +- ret = da7219_set_pll(component, source, fout); +- mutex_unlock(&da7219->pll_lock); ++ guard(mutex)(&da7219->pll_lock); + +- return ret; ++ return da7219_set_pll(component, source, fout); + } + + static int da7219_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c +--- a/sound/soc/codecs/es8316.c ++++ b/sound/soc/codecs/es8316.c +@@ -9,11 +9,13 @@ + + #include + #include ++#include + #include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -30,6 +32,13 @@ static const unsigned int supported_mclk_lrck_ratios[] = { + 256, 384, 400, 500, 512, 768, 1024 + }; + ++static const char * const es8316_supply_names[] = { ++ "avdd", ++ "cpvdd", ++ "dvdd", ++ "pvdd", ++}; ++ + struct es8316_priv { + struct mutex lock; + struct clk *mclk; +@@ -620,15 +629,15 @@ static irqreturn_t es8316_irq(int irq, void *data) + struct snd_soc_component *comp = es8316->component; + unsigned int flags; + +- mutex_lock(&es8316->lock); ++ guard(mutex)(&es8316->lock); + + regmap_read(es8316->regmap, ES8316_GPIO_FLAG, &flags); + if (flags == 0x00) +- goto out; /* Powered-down / reset */ ++ return IRQ_HANDLED; /* Powered-down / reset */ + + /* Catch spurious IRQ before set_jack is called */ + if (!es8316->jack) +- goto out; ++ return IRQ_HANDLED; + + if (es8316->jd_inverted) + flags ^= ES8316_GPIO_FLAG_HP_NOT_INSERTED; +@@ -681,8 +690,6 @@ static irqreturn_t es8316_irq(int irq, void *data) + } + } + +-out: +- mutex_unlock(&es8316->lock); + return IRQ_HANDLED; + } + +@@ -699,18 +706,16 @@ static void es8316_enable_jack_detect(struct snd_soc_component *component, + es8316->jd_inverted = device_property_read_bool(component->dev, + "everest,jack-detect-inverted"); + +- mutex_lock(&es8316->lock); ++ scoped_guard(mutex, &es8316->lock) { ++ es8316->jack = jack; + +- es8316->jack = jack; ++ if (es8316->jack->status & SND_JACK_MICROPHONE) ++ es8316_enable_micbias_for_mic_gnd_short_detect(component); + +- if (es8316->jack->status & SND_JACK_MICROPHONE) +- es8316_enable_micbias_for_mic_gnd_short_detect(component); +- +- snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE, +- ES8316_GPIO_ENABLE_INTERRUPT, +- ES8316_GPIO_ENABLE_INTERRUPT); +- +- mutex_unlock(&es8316->lock); ++ snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE, ++ ES8316_GPIO_ENABLE_INTERRUPT, ++ ES8316_GPIO_ENABLE_INTERRUPT); ++ } + + /* Enable irq and sync initial jack state */ + enable_irq(es8316->irq); +@@ -726,7 +731,7 @@ static void es8316_disable_jack_detect(struct snd_soc_component *component) + + disable_irq(es8316->irq); + +- mutex_lock(&es8316->lock); ++ guard(mutex)(&es8316->lock); + + snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE, + ES8316_GPIO_ENABLE_INTERRUPT, 0); +@@ -737,8 +742,6 @@ static void es8316_disable_jack_detect(struct snd_soc_component *component) + } + + es8316->jack = NULL; +- +- mutex_unlock(&es8316->lock); + } + + static int es8316_set_jack(struct snd_soc_component *component, +@@ -871,6 +874,11 @@ static int es8316_i2c_probe(struct i2c_client *i2c_client) + + i2c_set_clientdata(i2c_client, es8316); + ++ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(es8316_supply_names), ++ es8316_supply_names); ++ if (ret) ++ return dev_err_probe(dev, ret, "unable to enable supplies\n"); ++ + es8316->regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap); + if (IS_ERR(es8316->regmap)) + return PTR_ERR(es8316->regmap); +diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c +--- a/sound/soc/codecs/es8326.c ++++ b/sound/soc/codecs/es8326.c +@@ -6,6 +6,7 @@ + // Authors: David Yang + // + ++#include + #include + #include + #include +@@ -790,7 +791,7 @@ static void es8326_jack_button_handler(struct work_struct *work) + if (!(es8326->jack->status & SND_JACK_HEADSET)) /* Jack unplugged */ + return; + +- mutex_lock(&es8326->lock); ++ guard(mutex)(&es8326->lock); + iface = snd_soc_component_read(comp, ES8326_HPDET_STA); + switch (iface) { + case 0x93: +@@ -845,7 +846,6 @@ static void es8326_jack_button_handler(struct work_struct *work) + } + es8326_disable_micbias(es8326->component); + } +- mutex_unlock(&es8326->lock); + } + + static void es8326_jack_detect_handler(struct work_struct *work) +@@ -855,7 +855,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + struct snd_soc_component *comp = es8326->component; + unsigned int iface; + +- mutex_lock(&es8326->lock); ++ guard(mutex)(&es8326->lock); + iface = snd_soc_component_read(comp, ES8326_HPDET_STA); + dev_dbg(comp->dev, "gpio flag %#04x", iface); + +@@ -873,7 +873,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + regmap_update_bits(es8326->regmap, ES8326_HPDET_TYPE, + ES8326_HP_DET_JACK_POL, (es8326->jd_inverted ? + ~es8326->jack_pol : es8326->jack_pol)); +- goto exit; ++ return; + } + + if ((iface & ES8326_HPINSERT_FLAG) == 0) { +@@ -930,7 +930,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + queue_delayed_work(system_dfl_wq, &es8326->jack_detect_work, + msecs_to_jiffies(400)); + es8326->hp = 1; +- goto exit; ++ return; + } + if (es8326->jack->status & SND_JACK_HEADSET) { + /* detect button */ +@@ -939,7 +939,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + (ES8326_INT_SRC_PIN9 | ES8326_INT_SRC_BUTTON)); + es8326_enable_micbias(es8326->component); + queue_delayed_work(system_dfl_wq, &es8326->button_press_work, 10); +- goto exit; ++ return; + } + if ((iface & ES8326_HPBUTTON_FLAG) == 0x01) { + dev_dbg(comp->dev, "Headphone detected\n"); +@@ -958,8 +958,6 @@ static void es8326_jack_detect_handler(struct work_struct *work) + usleep_range(10000, 15000); + } + } +-exit: +- mutex_unlock(&es8326->lock); + } + + static irqreturn_t es8326_irq(int irq, void *dev_id) +@@ -1200,13 +1198,12 @@ static void es8326_enable_jack_detect(struct snd_soc_component *component, + { + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&es8326->lock); +- if (es8326->jd_inverted) +- snd_soc_component_update_bits(component, ES8326_HPDET_TYPE, +- ES8326_HP_DET_JACK_POL, ~es8326->jack_pol); +- es8326->jack = jack; +- +- mutex_unlock(&es8326->lock); ++ scoped_guard(mutex, &es8326->lock) { ++ if (es8326->jd_inverted) ++ snd_soc_component_update_bits(component, ES8326_HPDET_TYPE, ++ ES8326_HP_DET_JACK_POL, ~es8326->jack_pol); ++ es8326->jack = jack; ++ } + es8326_irq(es8326->irq, es8326); + } + +@@ -1219,13 +1216,12 @@ static void es8326_disable_jack_detect(struct snd_soc_component *component) + return; /* Already disabled (or never enabled) */ + cancel_delayed_work_sync(&es8326->jack_detect_work); + +- mutex_lock(&es8326->lock); ++ guard(mutex)(&es8326->lock); + if (es8326->jack->status & SND_JACK_MICROPHONE) { + es8326_disable_micbias(component); + snd_soc_jack_report(es8326->jack, 0, SND_JACK_HEADSET); + } + es8326->jack = NULL; +- mutex_unlock(&es8326->lock); + } + + static int es8326_set_jack(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c +--- a/sound/soc/codecs/es8328.c ++++ b/sound/soc/codecs/es8328.c +@@ -405,6 +405,11 @@ static const struct snd_soc_dapm_route es8328_dapm_routes[] = { + + { "Mic Bias", NULL, "Mic Bias Gen" }, + ++ { "LINPUT1", NULL, "Mic Bias" }, ++ { "RINPUT1", NULL, "Mic Bias" }, ++ { "LINPUT2", NULL, "Mic Bias" }, ++ { "RINPUT2", NULL, "Mic Bias" }, ++ + { "Left Mixer", NULL, "Left DAC" }, + { "Left Mixer", "Left Bypass Switch", "Left Line Mux" }, + { "Left Mixer", "Right Playback Switch", "Right DAC" }, +diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c +--- a/sound/soc/codecs/es8389.c ++++ b/sound/soc/codecs/es8389.c +@@ -36,6 +36,10 @@ struct es8389_private { + unsigned int sysclk; + int mastermode; + ++ u8 hpfl; ++ u8 hpfr; ++ u32 hpf_freq; ++ u32 capture_rate; + u8 mclk_src; + u8 vddd; + int version; +@@ -50,10 +54,29 @@ static const char * const es8389_core_supplies[] = { + static bool es8389_volatile_register(struct device *dev, + unsigned int reg) + { +- if ((reg <= 0xff)) +- return true; +- else ++ switch (reg) { ++ case ES8389_ADCL_VOL: ++ case ES8389_ADCR_VOL: ++ case ES8389_MIC1_GAIN: ++ case ES8389_MIC2_GAIN: ++ case ES8389_DACL_VOL: ++ case ES8389_DACR_VOL: ++ case ES8389_ALC_ON: ++ case ES8389_ALC_CTL: ++ case ES8389_ALC_TARGET: ++ case ES8389_ALC_GAIN: ++ case ES8389_ADC_MUTE: ++ case ES8389_OSR_VOL: ++ case ES8389_DAC_INV: ++ case ES8389_MIX_VOL: ++ case ES8389_DAC_MIX: ++ case ES8389_ADC_RESET: ++ case ES8389_ADC_MODE: ++ case ES8389_DMIC_EN: + return false; ++ default: ++ return true; ++ } + } + + static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -9550, 50, 0); +@@ -63,6 +86,92 @@ static const DECLARE_TLV_DB_SCALE(mix_vol_tlv, -9500, 100, 0); + static const DECLARE_TLV_DB_SCALE(alc_target_tlv, -3200, 200, 0); + static const DECLARE_TLV_DB_SCALE(alc_max_level, -3200, 200, 0); + ++static const u32 hpf_table[10][10] = { ++ {1020, 754, 624, 559, 527, 511, 502, 498, 497, 496}, ++ {754, 495, 368, 306, 274, 259, 251, 247, 246, 244}, ++ {624, 368, 243, 182, 151, 136, 128, 124, 123, 121}, ++ {559, 306, 182, 120, 90, 75, 68, 63, 62, 60}, ++ {527, 274, 151, 90, 60, 45, 38, 33, 32, 31}, ++ {511, 259, 136, 75, 45, 30, 23, 19, 18, 17}, ++ {502, 251, 128, 68, 38, 23, 16, 13, 11, 11}, ++ {498, 247, 124, 63, 33, 19, 13, 10, 8, 8}, ++ {497, 246, 123, 62, 32, 18, 11, 8, 8, 0}, ++ {496, 244, 121, 60, 31, 17, 11, 8, 0, 0} ++}; ++ ++static bool find_best_hpf_freq(u32 target_hz, u8 *hpf1, u8 *hpf2, u32 *out) ++{ ++ int best_row = -1, best_col = -1; ++ u32 min_diff = U32_MAX; ++ u32 f, diff; ++ int i, j; ++ ++ if (target_hz > 1020) ++ return false; ++ ++ for (i = 0; i < 10; i++) { ++ for (j = i; j < 10; j++) { ++ f = hpf_table[i][j]; ++ ++ diff = (target_hz > f) ? (target_hz - f) : (f - target_hz); ++ if (diff < min_diff) { ++ min_diff = diff; ++ best_row = i; ++ best_col = j; ++ *out = f; ++ } ++ } ++ } ++ ++ *hpf1 = best_col + ES8389_HPF_OFFSET; ++ *hpf2 = best_row + ES8389_HPF_OFFSET; ++ ++ return true; ++} ++ ++static int es8389_hpf_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = es8389->hpf_freq; ++ return 0; ++} ++ ++static int es8389_hpf_set(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ u32 freq; ++ bool hpf; ++ ++ if (es8389->hpf_freq == ucontrol->value.integer.value[0]) ++ return 0; ++ ++ if (es8389->capture_rate) { ++ freq = (ucontrol->value.integer.value[0] * 48000) / es8389->capture_rate; ++ ++ hpf = find_best_hpf_freq(freq, &es8389->hpfl, &es8389->hpfr, &es8389->hpf_freq); ++ if (!hpf) ++ return -EBUSY; ++ ++ if (es8389->hpf_freq != ucontrol->value.integer.value[0]) ++ dev_dbg(component->dev, "At the %u Hz sampling rate, %ld Hz could not be obtained." ++ "the frequency has been set to the closest value, %u Hz\n", ++ es8389->capture_rate, ucontrol->value.integer.value[0], es8389->hpf_freq); ++ ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, es8389->hpfl); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, es8389->hpfr); ++ } else { ++ es8389->hpf_freq = ucontrol->value.integer.value[0]; ++ dev_dbg(component->dev, "PCM_STREAM_CAPTURE is not active.retain the input frequency\n"); ++ } ++ ++ return 1; ++} ++ + static int es8389_dmic_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) + { +@@ -143,6 +252,16 @@ static const struct soc_enum alc_ramprate = + static const struct soc_enum alc_winsize = + SOC_ENUM_SINGLE(ES8389_ALC_CTL, 0, 16, winsize); + ++static const char *const es8389_adcl_mux_txt[] = { ++ "Normal", ++ "ADC2 channel to ADC1 channel", ++}; ++ ++static const char *const es8389_adcr_mux_txt[] = { ++ "Normal", ++ "ADC1 channel to ADC2 channel", ++}; ++ + static const char *const es8389_outl_mux_txt[] = { + "Normal", + "DAC2 channel to DAC1 channel", +@@ -170,6 +289,20 @@ static const unsigned int es8389_pga_values[] = { + 1, 5, 6 + }; + ++static const struct soc_enum es8389_adcl_mux_enum = ++ SOC_ENUM_SINGLE(ES8389_ADC_MODE, 5, ++ ARRAY_SIZE(es8389_adcl_mux_txt), es8389_adcl_mux_txt); ++ ++static const struct snd_kcontrol_new es8389_adcl_mux_controls = ++ SOC_DAPM_ENUM("INPUTL MUX", es8389_adcl_mux_enum); ++ ++static const struct soc_enum es8389_adcr_mux_enum = ++ SOC_ENUM_SINGLE(ES8389_ADC_MODE, 4, ++ ARRAY_SIZE(es8389_adcr_mux_txt), es8389_adcr_mux_txt); ++ ++static const struct snd_kcontrol_new es8389_adcr_mux_controls = ++ SOC_DAPM_ENUM("INPUTR MUX", es8389_adcr_mux_enum); ++ + static const struct soc_enum es8389_outl_mux_enum = + SOC_ENUM_SINGLE(ES8389_DAC_MIX, 5, + ARRAY_SIZE(es8389_outl_mux_txt), es8389_outl_mux_txt); +@@ -240,6 +373,8 @@ static const struct snd_kcontrol_new es8389_snd_controls[] = { + SOC_DOUBLE("ADC OSR Volume ON Switch", ES8389_ADC_MUTE, 6, 7, 1, 0), + SOC_SINGLE_TLV("ADC OSR Volume", ES8389_OSR_VOL, 0, 0xFF, 0, adc_vol_tlv), + SOC_DOUBLE("ADC OUTPUT Invert Switch", ES8389_ADC_HPF2, 5, 6, 1, 0), ++ SOC_SINGLE_EXT("ADC HPF Freq Select", SND_SOC_NOPM, 0, 1020, 0, ++ es8389_hpf_get, es8389_hpf_set), + + SOC_SINGLE_TLV("DACL Playback Volume", ES8389_DACL_VOL, 0, 0xFF, 0, dac_vol_tlv), + SOC_SINGLE_TLV("DACR Playback Volume", ES8389_DACR_VOL, 0, 0xFF, 0, dac_vol_tlv), +@@ -298,6 +433,8 @@ static const struct snd_soc_dapm_widget es8389_dapm_widgets[] = { + &es8389_adc_mixer_controls[0], + ARRAY_SIZE(es8389_adc_mixer_controls)), + SND_SOC_DAPM_MUX("ADC MUX", SND_SOC_NOPM, 0, 0, &es8389_dmic_mux_controls), ++ SND_SOC_DAPM_MUX("INPUTL MUX", SND_SOC_NOPM, 0, 0, &es8389_adcl_mux_controls), ++ SND_SOC_DAPM_MUX("INPUTR MUX", SND_SOC_NOPM, 0, 0, &es8389_adcr_mux_controls), + + SND_SOC_DAPM_MUX("OUTL MUX", SND_SOC_NOPM, 0, 0, &es8389_outl_mux_controls), + SND_SOC_DAPM_MUX("OUTR MUX", SND_SOC_NOPM, 0, 0, &es8389_outr_mux_controls), +@@ -311,10 +448,15 @@ static const struct snd_soc_dapm_route es8389_dapm_routes[] = { + {"ADCL", NULL, "PGAL"}, + {"ADCR", NULL, "PGAR"}, + ++ {"INPUTL MUX", "Normal", "ADCL"}, ++ {"INPUTL MUX", "ADC2 channel to ADC1 channel", "ADCR"}, ++ {"INPUTR MUX", "Normal", "ADCR"}, ++ {"INPUTR MUX", "ADC1 channel to ADC2 channel", "ADCL"}, ++ + {"ADC Mixer", "DACL ADCL Mixer", "DACL"}, + {"ADC Mixer", "DACR ADCR Mixer", "DACR"}, +- {"ADC Mixer", NULL, "ADCL"}, +- {"ADC Mixer", NULL, "ADCR"}, ++ {"ADC Mixer", NULL, "INPUTL MUX"}, ++ {"ADC Mixer", NULL, "INPUTR MUX"}, + + {"ADC MUX", "AMIC", "ADC Mixer"}, + {"ADC MUX", "DMIC", "DMIC"}, +@@ -415,52 +557,54 @@ static const struct _coeff_div coeff_div[] = { + {36, 576000, 16000, 0x00, 0x55, 0x84, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {48, 768000, 16000, 0x02, 0x57, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {50, 800000, 16000, 0x00, 0x7E, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {64, 1024000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {72, 1152000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {64, 1024000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {72, 1152000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {96, 1536000, 16000, 0x02, 0x55, 0x84, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {128, 2048000, 16000, 0x00, 0x51, 0x04, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {144, 2304000, 16000, 0x00, 0x51, 0x00, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {150, 2400000, 16000, 0x02, 0x7E, 0x01, 0xC9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {192, 3072000, 16000, 0x02, 0x65, 0x25, 0xE0, 0x00, 0xE1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {256, 4096000, 16000, 0x00, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {256, 4096000, 16000, 0x00, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {300, 4800000, 16000, 0x02, 0x66, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {384, 6144000, 16000, 0x02, 0x51, 0x04, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {512, 8192000, 16000, 0x01, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {512, 8192000, 16000, 0x01, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {750, 12000000, 16000, 0x0E, 0x7E, 0x01, 0xC9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {768, 12288000, 16000, 0x02, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {1024, 16384000, 16000, 0x03, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {768, 12288000, 16000, 0x02, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {1024, 16384000, 16000, 0x03, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1152, 18432000, 16000, 0x08, 0x51, 0x04, 0xD0, 0x01, 0xC1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1200, 19200000, 16000, 0x0B, 0x66, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1500, 24000000, 16000, 0x0E, 0x26, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {1536, 24576000, 16000, 0x05, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {1536, 24576000, 16000, 0x05, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1625, 26000000, 16000, 0x40, 0x6E, 0x05, 0xC8, 0x01, 0xC2, 0x90, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {800, 19200000, 24000, 0x07, 0x66, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x1A, 0x49, 0x14, 2, 2}, + {375, 12000000, 32000, 0x0E, 0x2E, 0x05, 0xC8, 0x00, 0xC2, 0x80, 0x40, 0x01, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x23, 0x61, 0x1B, 2, 0}, +- {600, 19200000, 32000, 0x05, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x23, 0x61, 0x1B, 2, 2}, +- {32, 1411200, 44100, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {600, 19200000, 32000, 0x05, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x23, 0x61, 0x1B, 2, 2}, ++ {32, 1411200, 44100, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {64, 2822400, 44100, 0x00, 0x51, 0x00, 0xC0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {128, 5644800, 44100, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {256, 11289600, 44100, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {512, 22579200, 44100, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {32, 1536000, 48000, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {128, 5644800, 44100, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {256, 11289600, 44100, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {512, 22579200, 44100, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {32, 1536000, 48000, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {48, 2304000, 48000, 0x02, 0x55, 0x04, 0xC0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {50, 2400000, 48000, 0x00, 0x76, 0x01, 0xC8, 0x10, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {64, 3072000, 48000, 0x00, 0x51, 0x04, 0xC0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {100, 4800000, 48000, 0x00, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {100, 4800000, 48000, 0x00, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {125, 6000000, 48000, 0x04, 0x6E, 0x05, 0xC8, 0x10, 0xC2, 0x80, 0x00, 0x01, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {128, 6144000, 48000, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {200, 9600000, 48000, 0x01, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {128, 6144000, 48000, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {200, 9600000, 48000, 0x01, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {250, 12000000, 48000, 0x04, 0x76, 0x01, 0xC8, 0x10, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {256, 12288000, 48000, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {384, 18432000, 48000, 0x02, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {400, 19200000, 48000, 0x03, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {500, 24000000, 48000, 0x04, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {512, 24576000, 48000, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {256, 12288000, 48000, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {384, 18432000, 48000, 0x02, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {400, 19200000, 48000, 0x03, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {500, 24000000, 48000, 0x04, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {512, 24576000, 48000, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {800, 38400000, 48000, 0x18, 0x45, 0x04, 0xC0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {128, 11289600, 88200, 0x00, 0x50, 0x00, 0xC0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x32, 0x89, 0x25, 2, 2}, +- {64, 6144000, 96000, 0x00, 0x41, 0x00, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, ++ {64, 6144000, 96000, 0x00, 0x41, 0x00, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, + {96, 9216000, 96000, 0x02, 0x43, 0x00, 0xC0, 0x10, 0xC0, 0x80, 0x00, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, + {256, 24576000, 96000, 0x00, 0x40, 0x00, 0xC0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, + {128, 24576000, 192000, 0x00, 0x50, 0x00, 0xC0, 0x18, 0xC1, 0x81, 0xC0, 0x00, 0x8F, 0x7F, 0xBF, 0xC0, 0x3F, 0x7F, 0x80, 0x12, 0xC0, 0x3F, 0xF9, 0x3F, 2, 2}, ++ {64, 12288000, 192000, 0x00, 0x41, 0x00, 0xC0, 0x18, 0xC1, 0x80, 0x00, 0x00, 0x8F, 0x7F, 0xEF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x3F, 0xF9, 0x3F, 1, 0}, + }; + + static inline int get_coeff(u8 vddd, u8 dmic, int mclk, int rate) +@@ -564,6 +708,8 @@ static int es8389_pcm_hw_params(struct snd_pcm_substream *substream, + int coeff, ret; + u8 dmic_enable, state = 0; + unsigned int regv; ++ u32 freq; ++ bool hpf; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: +@@ -590,7 +736,7 @@ static int es8389_pcm_hw_params(struct snd_pcm_substream *substream, + + if (es8389->mclk_src == ES8389_SCLK_PIN) { + regmap_update_bits(es8389->regmap, ES8389_MASTER_CLK, +- ES8389_MCLK_SOURCE, es8389->mclk_src); ++ ES8389_MCLK_MASK, ES8389_MCLK_FROM_SCLK); + es8389->sysclk = params_channels(params) * params_width(params) * params_rate(params); + } + +@@ -641,6 +787,28 @@ static int es8389_pcm_hw_params(struct snd_pcm_substream *substream, + dev_warn(component->dev, "Clock coefficients do not match"); + } + ++ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { ++ es8389->capture_rate = params_rate(params); ++ freq = (es8389->hpf_freq * 48000) / params_rate(params); ++ hpf = find_best_hpf_freq(freq, &es8389->hpfl, &es8389->hpfr, &es8389->hpf_freq); ++ if (!hpf) { ++ dev_err(component->dev, "The HPF frequency is invalid\n"); ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++} ++ ++static int es8389_pcm_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_component *component = dai->component; ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ ++ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ++ es8389->capture_rate = 0; ++ + return 0; + } + +@@ -721,8 +889,8 @@ static int es8389_mute(struct snd_soc_dai *dai, int mute, int direction) + regmap_update_bits(es8389->regmap, ES8389_DAC_FORMAT_MUTE, + 0x03, 0x00); + } else { +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x0a); +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x0a); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, es8389->hpfl); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, es8389->hpfr); + regmap_update_bits(es8389->regmap, ES8389_ADC_FORMAT_MUTE, + 0x03, 0x00); + } +@@ -738,6 +906,7 @@ static int es8389_mute(struct snd_soc_dai *dai, int mute, int direction) + + static const struct snd_soc_dai_ops es8389_ops = { + .hw_params = es8389_pcm_hw_params, ++ .hw_free = es8389_pcm_hw_free, + .set_fmt = es8389_set_dai_fmt, + .set_sysclk = es8389_set_dai_sysclk, + .set_tdm_slot = es8389_set_tdm_slot, +@@ -771,7 +940,7 @@ static void es8389_init(struct snd_soc_component *component) + + regmap_read(es8389->regmap, ES8389_MAX_REGISTER, ®); + es8389->version = reg; +- regmap_write(es8389->regmap, ES8389_ISO_CTL, 0x00); ++ regmap_write(es8389->regmap, ES8389_ISO_CTL, 0x56); + regmap_write(es8389->regmap, ES8389_RESET, 0x7E); + regmap_write(es8389->regmap, ES8389_ISO_CTL, 0x38); + regmap_write(es8389->regmap, ES8389_ADC_HPF1, 0x64); +@@ -823,7 +992,7 @@ static void es8389_init(struct snd_soc_component *component) + regmap_write(es8389->regmap, ES8389_SCLK_DIV, 0x04); + regmap_write(es8389->regmap, ES8389_LRCK_DIV1, 0x01); + regmap_write(es8389->regmap, ES8389_LRCK_DIV2, 0x00); +- regmap_write(es8389->regmap, ES8389_OSC_CLK, 0x00); ++ regmap_write(es8389->regmap, ES8389_OSC_CLK, 0x10); + regmap_write(es8389->regmap, ES8389_ADC_OSR, 0x1F); + regmap_write(es8389->regmap, ES8389_ADC_DSP, 0x7F); + regmap_write(es8389->regmap, ES8389_ADC_MUTE, 0xC0); +@@ -861,13 +1030,13 @@ static int es8389_resume(struct snd_soc_component *component) + regcache_cache_only(es8389->regmap, false); + regcache_cache_bypass(es8389->regmap, true); + regmap_read(es8389->regmap, ES8389_RESET, ®v); +- regcache_cache_bypass(es8389->regmap, false); + + if (regv == 0xff) + es8389_init(component); + else + es8389_set_bias_level(component, SND_SOC_BIAS_ON); + ++ regcache_cache_bypass(es8389->regmap, false); + regcache_sync(es8389->regmap); + + return 0; +@@ -913,6 +1082,7 @@ static int es8389_probe(struct snd_soc_component *component) + return ret; + } + ++ es8389->hpf_freq = ES8389_HPF_DEFAULT; + es8389_init(component); + es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); + +diff --git a/sound/soc/codecs/es8389.h b/sound/soc/codecs/es8389.h +--- a/sound/soc/codecs/es8389.h ++++ b/sound/soc/codecs/es8389.h +@@ -106,6 +106,9 @@ + #define ES8389_MIC_SEL_MASK (7 << 4) + #define ES8389_MIC_DEFAULT (1 << 4) + ++#define ES8389_HPF_DEFAULT 16 ++#define ES8389_HPF_OFFSET 4 ++ + #define ES8389_MASTER_MODE_EN (1 << 0) + + #define ES8389_TDM_OFF (0 << 0) +@@ -116,9 +119,11 @@ + #define ES8389_TDM_SLOT (0x70 << 0) + #define ES8389_TDM_SHIFT 4 + +-#define ES8389_MCLK_SOURCE (1 << 6) +-#define ES8389_MCLK_PIN (1 << 6) +-#define ES8389_SCLK_PIN (0 << 6) ++#define ES8389_MCLK_MASK (3 << 6) ++#define ES8389_MCLK_FROM_SCLK (1 << 6) ++#define ES8389_MCLK_SOURCE ES8389_MCLK_PIN ++#define ES8389_MCLK_PIN 0 ++#define ES8389_SCLK_PIN 1 + + /* ES8389_FMT */ + #define ES8389_S24_LE (0 << 5) +diff --git a/sound/soc/codecs/es9356.c b/sound/soc/codecs/es9356.c +--- a/sound/soc/codecs/es9356.c ++++ b/sound/soc/codecs/es9356.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -19,8 +20,6 @@ + #include + #include + #include +-#include +-#include + #include + #include + #include +@@ -500,16 +499,19 @@ static int es9356_power_state(struct snd_soc_dai *dai, unsigned char ps, unsigne + } + + /* power state changes are not independent across functions */ +- mutex_lock(&es9356->pde_lock); +- ret = es9356_pde_transition_delay(es9356, func, pde_entity, ps?ps0:ps3); +- if (ret) { +- regmap_write(es9356->regmap, +- SDW_SDCA_CTL(func, pde_entity, ES9356_SDCA_CTL_REQ_POWER_STATE, 0), ps?ps3:ps0); +- es9356_pde_transition_delay(es9356, func, pde_entity, ps?ps3:ps0); +- } else +- dev_dbg(component->dev, "%s PDE is already %d\n", __func__, ps?ps0:ps3); +- +- mutex_unlock(&es9356->pde_lock); ++ scoped_guard(mutex, &es9356->pde_lock) { ++ ret = es9356_pde_transition_delay(es9356, func, pde_entity, ps ? ps0 : ps3); ++ if (ret) { ++ regmap_write(es9356->regmap, ++ SDW_SDCA_CTL(func, pde_entity, ++ ES9356_SDCA_CTL_REQ_POWER_STATE, 0), ++ ps ? ps3 : ps0); ++ es9356_pde_transition_delay(es9356, func, pde_entity, ps ? ps3 : ps0); ++ } else { ++ dev_dbg(component->dev, "%s PDE is already %d\n", __func__, ++ ps ? ps0 : ps3); ++ } ++ } + + if (rate) + regmap_write(es9356->regmap, +@@ -671,7 +673,7 @@ static int es9356_sdca_mbq_size(struct device *dev, unsigned int reg) + } + } + +-static struct regmap_sdw_mbq_cfg es9356_mbq_config = { ++static const struct regmap_sdw_mbq_cfg es9356_mbq_config = { + .mbq_size = es9356_sdca_mbq_size, + }; + +@@ -1091,9 +1093,8 @@ static int es9356_sdca_dev_system_suspend(struct device *dev) + { + struct es9356_sdw_priv *es9356 = dev_get_drvdata(dev); + +- mutex_lock(&es9356->disable_irq_lock); +- es9356->disable_irq = true; +- mutex_unlock(&es9356->disable_irq_lock); ++ scoped_guard(mutex, &es9356->disable_irq_lock) ++ es9356->disable_irq = true; + + return es9356_sdca_dev_suspend(dev); + } +diff --git a/sound/soc/codecs/fs-amp-lib.c b/sound/soc/codecs/fs-amp-lib.c +--- a/sound/soc/codecs/fs-amp-lib.c ++++ b/sound/soc/codecs/fs-amp-lib.c +@@ -221,7 +221,7 @@ static void fs_print_firmware_info(struct fs_amp_lib *amp_lib) + + int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) + { +- const struct firmware *cont; ++ const struct firmware *cont __free(firmware) = NULL; + struct fs_fwm_header *hdr; + int ret; + +@@ -237,7 +237,6 @@ int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) + dev_info(amp_lib->dev, "Loading %s - size: %zu\n", name, cont->size); + + hdr = devm_kmemdup(amp_lib->dev, cont->data, cont->size, GFP_KERNEL); +- release_firmware(cont); + if (!hdr) + return -ENOMEM; + +diff --git a/sound/soc/codecs/fs210x.c b/sound/soc/codecs/fs210x.c +--- a/sound/soc/codecs/fs210x.c ++++ b/sound/soc/codecs/fs210x.c +@@ -4,6 +4,7 @@ + // + // Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + ++#include + #include + #include + #include +@@ -771,9 +772,8 @@ static int fs210x_dai_hw_params(struct snd_pcm_substream *substream, + if (fs210x->devid == FS2105S_DEVICE_ID && fs210x->srate == 16000) + return -EOPNOTSUPP; + +- mutex_lock(&fs210x->lock); +- ret = fs210x_set_hw_params(fs210x); +- mutex_unlock(&fs210x->lock); ++ scoped_guard(mutex, &fs210x->lock) ++ ret = fs210x_set_hw_params(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to set hw params: %d\n", ret); + +@@ -790,15 +790,11 @@ static int fs210x_dai_mute(struct snd_soc_dai *dai, int mute, int stream) + + fs210x = snd_soc_component_get_drvdata(dai->component); + +- mutex_lock(&fs210x->lock); +- +- if (!fs210x->is_inited || fs210x->is_suspended) { +- mutex_unlock(&fs210x->lock); +- return 0; ++ scoped_guard(mutex, &fs210x->lock) { ++ if (!fs210x->is_inited || fs210x->is_suspended) ++ return 0; + } + +- mutex_unlock(&fs210x->lock); +- + if (mute) { + cancel_delayed_work_sync(&fs210x->fault_check_work); + cancel_delayed_work_sync(&fs210x->start_work); +@@ -817,15 +813,11 @@ static int fs210x_dai_trigger(struct snd_pcm_substream *substream, + + fs210x = snd_soc_component_get_drvdata(dai->component); + +- mutex_lock(&fs210x->lock); +- +- if (!fs210x->is_inited || fs210x->is_suspended || fs210x->is_playing) { +- mutex_unlock(&fs210x->lock); +- return 0; ++ scoped_guard(mutex, &fs210x->lock) { ++ if (!fs210x->is_inited || fs210x->is_suspended || fs210x->is_playing) ++ return 0; + } + +- mutex_unlock(&fs210x->lock); +- + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: +@@ -853,13 +845,11 @@ static void fs210x_start_work(struct work_struct *work) + + fs210x = container_of(work, struct fs210x_priv, start_work.work); + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + + ret = fs210x_dev_play(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to start playing: %d\n", ret); +- +- mutex_unlock(&fs210x->lock); + } + + static void fs210x_fault_check_work(struct work_struct *work) +@@ -870,15 +860,13 @@ static void fs210x_fault_check_work(struct work_struct *work) + + fs210x = container_of(work, struct fs210x_priv, fault_check_work.work); + +- mutex_lock(&fs210x->lock); ++ scoped_guard(mutex, &fs210x->lock) { ++ if (!fs210x->is_inited || fs210x->is_suspended || !fs210x->is_playing) ++ return; + +- if (!fs210x->is_inited || fs210x->is_suspended || !fs210x->is_playing) { +- mutex_unlock(&fs210x->lock); +- return; ++ ret = fs210x_reg_read(fs210x, FS210X_05H_ANASTAT, &status); + } + +- ret = fs210x_reg_read(fs210x, FS210X_05H_ANASTAT, &status); +- mutex_unlock(&fs210x->lock); + if (ret) + return; + +@@ -991,7 +979,7 @@ static int fs210x_effect_scene_get(struct snd_kcontrol *kcontrol, + if (fs210x->scene_id < 1) + return -EINVAL; + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + /* + * FS210x has scene(s) as below: + * init scene: id = 0 +@@ -1000,7 +988,6 @@ static int fs210x_effect_scene_get(struct snd_kcontrol *kcontrol, + */ + index = fs210x->scene_id - 1; + ucontrol->value.integer.value[0] = index; +- mutex_unlock(&fs210x->lock); + + return 0; + } +@@ -1019,7 +1006,7 @@ static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + return -EINVAL; + } + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + + /* + * FS210x has scene(s) as below: +@@ -1029,17 +1016,14 @@ static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + */ + scene_id = ucontrol->value.integer.value[0] + 1; + scene_count = fs210x->amp_lib.scene_count - 1; /* Skip init scene */ +- if (scene_id < 1 || scene_id > scene_count) { +- mutex_unlock(&fs210x->lock); ++ if (scene_id < 1 || scene_id > scene_count) + return -ERANGE; +- } + + if (scene_id != fs210x->scene_id) + is_changed = true; + + if (fs210x->is_suspended) { + fs210x->scene_id = scene_id; +- mutex_unlock(&fs210x->lock); + return is_changed; + } + +@@ -1047,8 +1031,6 @@ static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + if (ret) + dev_err(fs210x->dev, "Failed to set scene: %d\n", ret); + +- mutex_unlock(&fs210x->lock); +- + if (!ret && is_changed) + return 1; + +@@ -1062,12 +1044,10 @@ static int fs210x_playback_event(struct snd_soc_dapm_widget *w, + struct fs210x_priv *fs210x = snd_soc_component_get_drvdata(cmpnt); + int ret = 0; + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + +- if (fs210x->is_suspended) { +- mutex_unlock(&fs210x->lock); ++ if (fs210x->is_suspended) + return 0; +- } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +@@ -1088,8 +1068,6 @@ static int fs210x_playback_event(struct snd_soc_dapm_widget *w, + break; + } + +- mutex_unlock(&fs210x->lock); +- + return ret; + } + +@@ -1220,11 +1198,9 @@ static int fs210x_probe(struct snd_soc_component *cmpnt) + if (ret) + return ret; + +- mutex_lock(&fs210x->lock); +- ret = fs210x_init_chip(fs210x); +- mutex_unlock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + +- return ret; ++ return fs210x_init_chip(fs210x); + } + + static void fs210x_remove(struct snd_soc_component *cmpnt) +@@ -1251,15 +1227,15 @@ static int fs210x_suspend(struct snd_soc_component *cmpnt) + + regcache_cache_only(fs210x->regmap, true); + +- mutex_lock(&fs210x->lock); +- fs210x->cur_scene = NULL; +- fs210x->is_inited = false; +- fs210x->is_playing = false; +- fs210x->is_suspended = true; ++ scoped_guard(mutex, &fs210x->lock) { ++ fs210x->cur_scene = NULL; ++ fs210x->is_inited = false; ++ fs210x->is_playing = false; ++ fs210x->is_suspended = true; + +- gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */ +- fsleep(30000); /* >= 30ms */ +- mutex_unlock(&fs210x->lock); ++ gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */ ++ fsleep(30000); /* >= 30ms */ ++ } + + cancel_delayed_work_sync(&fs210x->start_work); + cancel_delayed_work_sync(&fs210x->fault_check_work); +@@ -1288,14 +1264,11 @@ static int fs210x_resume(struct snd_soc_component *cmpnt) + return ret; + } + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + + fs210x->is_suspended = false; +- ret = fs210x_init_chip(fs210x); + +- mutex_unlock(&fs210x->lock); +- +- return ret; ++ return fs210x_init_chip(fs210x); + } + #else + #define fs210x_suspend NULL +diff --git a/sound/soc/codecs/hdac_hda.c b/sound/soc/codecs/hdac_hda.c +--- a/sound/soc/codecs/hdac_hda.c ++++ b/sound/soc/codecs/hdac_hda.c +@@ -437,7 +437,7 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) + + #ifdef CONFIG_SND_HDA_PATCH_LOADER + if (loadable_patch[hda_pvt->dev_index] && *loadable_patch[hda_pvt->dev_index]) { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + + dev_info(&hdev->dev, "Applying patch firmware '%s'\n", + loadable_patch[hda_pvt->dev_index]); +@@ -451,7 +451,6 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) + dev_err(&hdev->dev, "%s: failed to load hda patch %d\n", __func__, ret); + goto error_no_pm; + } +- release_firmware(fw); + } + } + #endif +diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c +--- a/sound/soc/codecs/hdac_hdmi.c ++++ b/sound/soc/codecs/hdac_hdmi.c +@@ -10,6 +10,7 @@ + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + ++#include + #include + #include + #include +@@ -537,10 +538,11 @@ static struct hdac_hdmi_port *hdac_hdmi_get_port_from_cvt( + continue; + + list_for_each_entry(port, &pcm->port_list, head) { +- mutex_lock(&pcm->lock); +- ret = hdac_hdmi_query_port_connlist(hdev, +- port->pin, port); +- mutex_unlock(&pcm->lock); ++ scoped_guard(mutex, &pcm->lock) { ++ ret = hdac_hdmi_query_port_connlist(hdev, ++ port->pin, ++ port); ++ } + if (ret < 0) + continue; + +@@ -640,11 +642,11 @@ static void hdac_hdmi_pcm_close(struct snd_pcm_substream *substream, + pcm = hdac_hdmi_get_pcm_from_cvt(hdmi, dai_map->cvt); + + if (pcm) { +- mutex_lock(&pcm->lock); +- pcm->chmap_set = false; +- memset(pcm->chmap, 0, sizeof(pcm->chmap)); +- pcm->channels = 0; +- mutex_unlock(&pcm->lock); ++ scoped_guard(mutex, &pcm->lock) { ++ pcm->chmap_set = false; ++ memset(pcm->chmap, 0, sizeof(pcm->chmap)); ++ pcm->channels = 0; ++ } + } + + if (dai_map->port) +@@ -922,7 +924,7 @@ static int hdac_hdmi_set_pin_port_mux(struct snd_kcontrol *kcontrol, + if (port == NULL) + return -EINVAL; + +- mutex_lock(&hdmi->pin_mutex); ++ guard(mutex)(&hdmi->pin_mutex); + list_for_each_entry(pcm, &hdmi->pcm_list, head) { + if (list_empty(&pcm->port_list)) + continue; +@@ -945,12 +947,10 @@ static int hdac_hdmi_set_pin_port_mux(struct snd_kcontrol *kcontrol, + list_add_tail(&port->head, &pcm->port_list); + if (port->eld.monitor_present && port->eld.eld_valid) { + hdac_hdmi_jack_report_sync(pcm, port, true); +- mutex_unlock(&hdmi->pin_mutex); + return ret; + } + } + } +- mutex_unlock(&hdmi->pin_mutex); + + return ret; + } +@@ -1274,67 +1274,65 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, + * In case of non MST pin, get_eld info API expectes port + * to be -1. + */ +- mutex_lock(&hdmi->pin_mutex); +- port->eld.monitor_present = false; ++ scoped_guard(mutex, &hdmi->pin_mutex) { ++ port->eld.monitor_present = false; + +- if (pin->mst_capable) +- port_id = port->id; ++ if (pin->mst_capable) ++ port_id = port->id; + +- size = snd_hdac_acomp_get_eld(hdev, pin->nid, port_id, +- &port->eld.monitor_present, +- port->eld.eld_buffer, +- ELD_MAX_SIZE); ++ size = snd_hdac_acomp_get_eld(hdev, pin->nid, port_id, ++ &port->eld.monitor_present, ++ port->eld.eld_buffer, ++ ELD_MAX_SIZE); + +- if (size > 0) { +- size = min(size, ELD_MAX_SIZE); +- if (hdac_hdmi_parse_eld(hdev, port) < 0) +- size = -EINVAL; +- } +- +- eld_valid = port->eld.eld_valid; +- +- if (size > 0) { +- port->eld.eld_valid = true; +- port->eld.eld_size = size; +- } else { +- port->eld.eld_valid = false; +- port->eld.eld_size = 0; +- } +- +- eld_changed = (eld_valid != port->eld.eld_valid); +- +- pcm = hdac_hdmi_get_pcm(hdev, port); +- +- if (!port->eld.monitor_present || !port->eld.eld_valid) { +- +- dev_dbg(&hdev->dev, "%s: disconnect for pin:port %d:%d\n", +- __func__, pin->nid, port->id); +- +- /* +- * PCMs are not registered during device probe, so don't +- * report jack here. It will be done in usermode mux +- * control select. +- */ +- if (pcm) { +- hdac_hdmi_jack_report(pcm, port, false); +- schedule_work(&port->dapm_work); ++ if (size > 0) { ++ size = min(size, ELD_MAX_SIZE); ++ if (hdac_hdmi_parse_eld(hdev, port) < 0) ++ size = -EINVAL; + } + +- mutex_unlock(&hdmi->pin_mutex); +- return; +- } ++ eld_valid = port->eld.eld_valid; + +- if (port->eld.monitor_present && port->eld.eld_valid) { +- if (pcm) { +- hdac_hdmi_jack_report(pcm, port, true); +- schedule_work(&port->dapm_work); ++ if (size > 0) { ++ port->eld.eld_valid = true; ++ port->eld.eld_size = size; ++ } else { ++ port->eld.eld_valid = false; ++ port->eld.eld_size = 0; + } + +- print_hex_dump_debug("ELD: ", DUMP_PREFIX_OFFSET, 16, 1, +- port->eld.eld_buffer, port->eld.eld_size, false); ++ eld_changed = (eld_valid != port->eld.eld_valid); + ++ pcm = hdac_hdmi_get_pcm(hdev, port); ++ ++ if (!port->eld.monitor_present || !port->eld.eld_valid) { ++ ++ dev_dbg(&hdev->dev, "%s: disconnect for pin:port %d:%d\n", ++ __func__, pin->nid, port->id); ++ ++ /* ++ * PCMs are not registered during device probe, so don't ++ * report jack here. It will be done in usermode mux ++ * control select. ++ */ ++ if (pcm) { ++ hdac_hdmi_jack_report(pcm, port, false); ++ schedule_work(&port->dapm_work); ++ } ++ ++ return; ++ } ++ ++ if (port->eld.monitor_present && port->eld.eld_valid) { ++ if (pcm) { ++ hdac_hdmi_jack_report(pcm, port, true); ++ schedule_work(&port->dapm_work); ++ } ++ ++ print_hex_dump_debug("ELD: ", DUMP_PREFIX_OFFSET, 16, 1, ++ port->eld.eld_buffer, port->eld.eld_size, false); ++ } + } +- mutex_unlock(&hdmi->pin_mutex); + + if (eld_changed && pcm) + snd_ctl_notify(hdmi->card, +@@ -1795,13 +1793,12 @@ static void hdac_hdmi_set_chmap(struct hdac_device *hdev, int pcm_idx, + if (list_empty(&pcm->port_list)) + return; + +- mutex_lock(&pcm->lock); ++ guard(mutex)(&pcm->lock); + pcm->chmap_set = true; + memcpy(pcm->chmap, chmap, ARRAY_SIZE(pcm->chmap)); + list_for_each_entry(port, &pcm->port_list, head) + if (prepared) + hdac_hdmi_setup_audio_infoframe(hdev, pcm, port); +- mutex_unlock(&pcm->lock); + } + + static bool is_hdac_hdmi_pcm_attached(struct hdac_device *hdev, int pcm_idx) +@@ -1866,8 +1863,10 @@ static int hdac_hdmi_dev_probe(struct hdac_device *hdev) + snd_hdac_ext_bus_link_get(hdev->bus, hlink); + + hdmi_priv = devm_kzalloc(&hdev->dev, sizeof(*hdmi_priv), GFP_KERNEL); +- if (hdmi_priv == NULL) ++ if (hdmi_priv == NULL) { ++ snd_hdac_ext_bus_link_put(hdev->bus, hlink); + return -ENOMEM; ++ } + + snd_hdac_register_chmap_ops(hdev, &hdmi_priv->chmap); + hdmi_priv->chmap.ops.get_chmap = hdac_hdmi_get_chmap; +@@ -1876,8 +1875,10 @@ static int hdac_hdmi_dev_probe(struct hdac_device *hdev) + hdmi_priv->chmap.ops.get_spk_alloc = hdac_hdmi_get_spk_alloc; + hdmi_priv->hdev = hdev; + +- if (!hdac_id) ++ if (!hdac_id) { ++ snd_hdac_ext_bus_link_put(hdev->bus, hlink); + return -ENODEV; ++ } + + if (hdac_id->driver_data) + hdmi_priv->drv_data = +@@ -1902,6 +1903,8 @@ static int hdac_hdmi_dev_probe(struct hdac_device *hdev) + if (ret < 0) { + dev_err(&hdev->dev, + "Failed in parse and map nid with err: %d\n", ret); ++ snd_hdac_ext_bus_link_put(hdev->bus, hlink); ++ snd_hdac_display_power(hdev->bus, hdev->addr, false); + return ret; + } + snd_hdac_refresh_widgets(hdev); +diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c +--- a/sound/soc/codecs/hdmi-codec.c ++++ b/sound/soc/codecs/hdmi-codec.c +@@ -4,6 +4,7 @@ + * Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com/ + * Author: Jyri Sarha + */ ++#include + #include + #include + #include +@@ -452,31 +453,30 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, + if (!((has_playback && tx) || (has_capture && !tx))) + return 0; + +- mutex_lock(&hcp->lock); ++ guard(mutex)(&hcp->lock); + if (hcp->busy) { + dev_err(dai->dev, "Only one simultaneous stream supported!\n"); +- mutex_unlock(&hcp->lock); + return -EINVAL; + } + + if (hcp->hcd.ops->audio_startup) { + ret = hcp->hcd.ops->audio_startup(dai->dev->parent, hcp->hcd.data); + if (ret) +- goto err; ++ return ret; + } + + if (tx && hcp->hcd.ops->get_eld) { + ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data, + hcp->eld, sizeof(hcp->eld)); + if (ret) +- goto err; ++ return ret; + + snd_parse_eld(dai->dev, &hcp->eld_parsed, + hcp->eld, sizeof(hcp->eld)); + + ret = snd_pcm_hw_constraint_eld(substream->runtime, hcp->eld); + if (ret) +- goto err; ++ return ret; + + /* Select chmap supported */ + hdmi_codec_eld_chmap(hcp); +@@ -484,8 +484,6 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, + + hcp->busy = true; + +-err: +- mutex_unlock(&hcp->lock); + return ret; + } + +@@ -503,9 +501,8 @@ static void hdmi_codec_shutdown(struct snd_pcm_substream *substream, + hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; + hcp->hcd.ops->audio_shutdown(dai->dev->parent, hcp->hcd.data); + +- mutex_lock(&hcp->lock); ++ guard(mutex)(&hcp->lock); + hcp->busy = false; +- mutex_unlock(&hcp->lock); + } + + static int hdmi_codec_fill_codec_params(struct snd_soc_dai *dai, +diff --git a/sound/soc/codecs/idt821034.c b/sound/soc/codecs/idt821034.c +--- a/sound/soc/codecs/idt821034.c ++++ b/sound/soc/codecs/idt821034.c +@@ -7,6 +7,7 @@ + // Author: Herve Codina + + #include ++#include + #include + #include + #include +@@ -413,12 +414,12 @@ static int idt821034_kctrl_gain_get(struct snd_kcontrol *kcontrol, + + ch = IDT821034_ID_GET_CHAN(mc->reg); + +- mutex_lock(&idt821034->mutex); +- if (IDT821034_ID_IS_OUT(mc->reg)) +- val = idt821034->amps.ch[ch].amp_out.gain; +- else +- val = idt821034->amps.ch[ch].amp_in.gain; +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) { ++ if (IDT821034_ID_IS_OUT(mc->reg)) ++ val = idt821034->amps.ch[ch].amp_out.gain; ++ else ++ val = idt821034->amps.ch[ch].amp_in.gain; ++ } + + ucontrol->value.integer.value[0] = val & mask; + if (invert) +@@ -456,7 +457,7 @@ static int idt821034_kctrl_gain_put(struct snd_kcontrol *kcontrol, + + ch = IDT821034_ID_GET_CHAN(mc->reg); + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + if (IDT821034_ID_IS_OUT(mc->reg)) { + amp = &idt821034->amps.ch[ch].amp_out; +@@ -466,22 +467,18 @@ static int idt821034_kctrl_gain_put(struct snd_kcontrol *kcontrol, + gain_type = IDT821034_GAIN_TX; + } + +- if (amp->gain == val) { +- ret = 0; +- goto end; +- } ++ if (amp->gain == val) ++ return 0; + + if (!amp->is_muted) { + ret = idt821034_set_gain_channel(idt821034, ch, gain_type, val); + if (ret) +- goto end; ++ return ret; + } + + amp->gain = val; +- ret = 1; /* The value changed */ +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return 1; + } + + static int idt821034_kctrl_mute_get(struct snd_kcontrol *kcontrol, +@@ -495,11 +492,11 @@ static int idt821034_kctrl_mute_get(struct snd_kcontrol *kcontrol, + + ch = IDT821034_ID_GET_CHAN(id); + +- mutex_lock(&idt821034->mutex); +- is_muted = IDT821034_ID_IS_OUT(id) ? +- idt821034->amps.ch[ch].amp_out.is_muted : +- idt821034->amps.ch[ch].amp_in.is_muted; +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) { ++ is_muted = IDT821034_ID_IS_OUT(id) ? ++ idt821034->amps.ch[ch].amp_out.is_muted : ++ idt821034->amps.ch[ch].amp_in.is_muted; ++ } + + ucontrol->value.integer.value[0] = !is_muted; + +@@ -521,7 +518,7 @@ static int idt821034_kctrl_mute_put(struct snd_kcontrol *kcontrol, + ch = IDT821034_ID_GET_CHAN(id); + is_mute = !ucontrol->value.integer.value[0]; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + if (IDT821034_ID_IS_OUT(id)) { + amp = &idt821034->amps.ch[ch].amp_out; +@@ -531,21 +528,17 @@ static int idt821034_kctrl_mute_put(struct snd_kcontrol *kcontrol, + gain_type = IDT821034_GAIN_TX; + } + +- if (amp->is_muted == is_mute) { +- ret = 0; +- goto end; +- } ++ if (amp->is_muted == is_mute) ++ return 0; + + ret = idt821034_set_gain_channel(idt821034, ch, gain_type, + is_mute ? 0 : amp->gain); + if (ret) +- goto end; ++ return ret; + + amp->is_muted = is_mute; +- ret = 1; /* The value changed */ +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return 1; + } + + static const DECLARE_TLV_DB_LINEAR(idt821034_gain_in, -300, 1300); +@@ -623,24 +616,20 @@ static int idt821034_power_event(struct snd_soc_dapm_widget *w, + struct idt821034 *idt821034 = snd_soc_component_get_drvdata(component); + unsigned int id = w->shift; + u8 power, mask; +- int ret; + u8 ch; + + ch = IDT821034_ID_GET_CHAN(id); + mask = IDT821034_ID_IS_OUT(id) ? IDT821034_CONF_PWRUP_RX : IDT821034_CONF_PWRUP_TX; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + power = idt821034_get_channel_power(idt821034, ch); + if (SND_SOC_DAPM_EVENT_ON(event)) + power |= mask; + else + power &= ~mask; +- ret = idt821034_set_channel_power(idt821034, ch, power); + +- mutex_unlock(&idt821034->mutex); +- +- return ret; ++ return idt821034_set_channel_power(idt821034, ch, power); + } + + static const struct snd_soc_dapm_widget idt821034_dapm_widgets[] = { +@@ -717,9 +706,9 @@ static int idt821034_dai_set_tdm_slot(struct snd_soc_dai *dai, + ch = 0; + while (mask && ch < IDT821034_NB_CHANNEL) { + if (mask & 0x1) { +- mutex_lock(&idt821034->mutex); +- ret = idt821034_set_channel_ts(idt821034, ch, IDT821034_CH_RX, slot); +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) ++ ret = idt821034_set_channel_ts(idt821034, ch, ++ IDT821034_CH_RX, slot); + if (ret) { + dev_err(dai->dev, "ch%u set tx tdm slot failed (%d)\n", + ch, ret); +@@ -742,9 +731,9 @@ static int idt821034_dai_set_tdm_slot(struct snd_soc_dai *dai, + ch = 0; + while (mask && ch < IDT821034_NB_CHANNEL) { + if (mask & 0x1) { +- mutex_lock(&idt821034->mutex); +- ret = idt821034_set_channel_ts(idt821034, ch, IDT821034_CH_TX, slot); +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) ++ ret = idt821034_set_channel_ts(idt821034, ch, ++ IDT821034_CH_TX, slot); + if (ret) { + dev_err(dai->dev, "ch%u set rx tdm slot failed (%d)\n", + ch, ret); +@@ -769,9 +758,8 @@ static int idt821034_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + { + struct idt821034 *idt821034 = snd_soc_component_get_drvdata(dai->component); + u8 conf; +- int ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + conf = idt821034_get_codec_conf(idt821034); + +@@ -785,13 +773,10 @@ static int idt821034_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + default: + dev_err(dai->dev, "Unsupported DAI format 0x%x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); +- ret = -EINVAL; +- goto end; ++ return -EINVAL; + } +- ret = idt821034_set_codec_conf(idt821034, conf); +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return idt821034_set_codec_conf(idt821034, conf); + } + + static int idt821034_dai_hw_params(struct snd_pcm_substream *substream, +@@ -800,9 +785,8 @@ static int idt821034_dai_hw_params(struct snd_pcm_substream *substream, + { + struct idt821034 *idt821034 = snd_soc_component_get_drvdata(dai->component); + u8 conf; +- int ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + conf = idt821034_get_codec_conf(idt821034); + +@@ -816,13 +800,10 @@ static int idt821034_dai_hw_params(struct snd_pcm_substream *substream, + default: + dev_err(dai->dev, "Unsupported PCM format 0x%x\n", + params_format(params)); +- ret = -EINVAL; +- goto end; ++ return -EINVAL; + } +- ret = idt821034_set_codec_conf(idt821034, conf); +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return idt821034_set_codec_conf(idt821034, conf); + } + + static const unsigned int idt821034_sample_bits[] = {8}; +@@ -897,11 +878,11 @@ static int idt821034_reset_audio(struct idt821034 *idt821034) + int ret; + u8 i; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + ret = idt821034_set_codec_conf(idt821034, 0); + if (ret) +- goto end; ++ return ret; + + for (i = 0; i < IDT821034_NB_CHANNEL; i++) { + idt821034->amps.ch[i].amp_out.gain = IDT821034_GAIN_OUT_INIT_RAW; +@@ -909,24 +890,21 @@ static int idt821034_reset_audio(struct idt821034 *idt821034) + ret = idt821034_set_gain_channel(idt821034, i, IDT821034_GAIN_RX, + idt821034->amps.ch[i].amp_out.gain); + if (ret) +- goto end; ++ return ret; + + idt821034->amps.ch[i].amp_in.gain = IDT821034_GAIN_IN_INIT_RAW; + idt821034->amps.ch[i].amp_in.is_muted = false; + ret = idt821034_set_gain_channel(idt821034, i, IDT821034_GAIN_TX, + idt821034->amps.ch[i].amp_in.gain); + if (ret) +- goto end; ++ return ret; + + ret = idt821034_set_channel_power(idt821034, i, 0); + if (ret) +- goto end; ++ return ret; + } + +- ret = 0; +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ return 0; + } + + static int idt821034_component_probe(struct snd_soc_component *component) +@@ -965,7 +943,7 @@ static int idt821034_chip_gpio_set(struct gpio_chip *c, unsigned int offset, + u8 slic_raw; + int ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + slic_raw = idt821034_get_written_slic_raw(idt821034, ch); + if (val) +@@ -974,8 +952,6 @@ static int idt821034_chip_gpio_set(struct gpio_chip *c, unsigned int offset, + slic_raw &= ~mask; + ret = idt821034_write_slic_raw(idt821034, ch, slic_raw); + +- mutex_unlock(&idt821034->mutex); +- + if (ret) + dev_err(&idt821034->spi->dev, "set gpio %d (%u, 0x%x) failed (%d)\n", + offset, ch, mask, ret); +@@ -991,9 +967,8 @@ static int idt821034_chip_gpio_get(struct gpio_chip *c, unsigned int offset) + u8 slic_raw; + int ret; + +- mutex_lock(&idt821034->mutex); +- ret = idt821034_read_slic_raw(idt821034, ch, &slic_raw); +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) ++ ret = idt821034_read_slic_raw(idt821034, ch, &slic_raw); + if (ret) { + dev_err(&idt821034->spi->dev, "get gpio %d (%u, 0x%x) failed (%d)\n", + offset, ch, mask, ret); +@@ -1015,9 +990,8 @@ static int idt821034_chip_get_direction(struct gpio_chip *c, unsigned int offset + struct idt821034 *idt821034 = gpiochip_get_data(c); + u8 slic_dir; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + slic_dir = idt821034_get_slic_conf(idt821034, ch); +- mutex_unlock(&idt821034->mutex); + + return slic_dir & mask ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT; + } +@@ -1034,7 +1008,7 @@ static int idt821034_chip_direction_input(struct gpio_chip *c, unsigned int offs + if (mask & ~(IDT821034_SLIC_IO1_IN | IDT821034_SLIC_IO0_IN)) + return -EPERM; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + slic_conf = idt821034_get_slic_conf(idt821034, ch) | mask; + +@@ -1044,7 +1018,6 @@ static int idt821034_chip_direction_input(struct gpio_chip *c, unsigned int offs + offset, ch, mask, ret); + } + +- mutex_unlock(&idt821034->mutex); + return ret; + } + +@@ -1060,7 +1033,7 @@ static int idt821034_chip_direction_output(struct gpio_chip *c, unsigned int off + if (ret) + return ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + slic_conf = idt821034_get_slic_conf(idt821034, ch) & ~mask; + +@@ -1070,7 +1043,6 @@ static int idt821034_chip_direction_output(struct gpio_chip *c, unsigned int off + offset, ch, mask, ret); + } + +- mutex_unlock(&idt821034->mutex); + return ret; + } + +@@ -1079,24 +1051,22 @@ static int idt821034_reset_gpio(struct idt821034 *idt821034) + int ret; + u8 i; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + /* IO0 and IO1 as input for all channels and output IO set to 0 */ + for (i = 0; i < IDT821034_NB_CHANNEL; i++) { + ret = idt821034_set_slic_conf(idt821034, i, + IDT821034_SLIC_IO1_IN | IDT821034_SLIC_IO0_IN); + if (ret) +- goto end; ++ return ret; + + ret = idt821034_write_slic_raw(idt821034, i, 0); + if (ret) +- goto end; ++ return ret; + + } +- ret = 0; +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return 0; + } + + static int idt821034_gpio_init(struct idt821034 *idt821034) +diff --git a/sound/soc/codecs/lochnagar-sc.c b/sound/soc/codecs/lochnagar-sc.c +--- a/sound/soc/codecs/lochnagar-sc.c ++++ b/sound/soc/codecs/lochnagar-sc.c +@@ -137,15 +137,23 @@ static int lochnagar_sc_set_usb_fmt(struct snd_soc_dai *dai, unsigned int fmt) + return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBP_CFP); + } + ++static const u64 lochnagar_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops lochnagar_sc_line_ops = { + .startup = lochnagar_sc_line_startup, + .shutdown = lochnagar_sc_line_shutdown, + .set_fmt = lochnagar_sc_set_line_fmt, ++ .auto_selectable_formats = &lochnagar_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_ops lochnagar_sc_usb_ops = { + .startup = lochnagar_sc_startup, + .set_fmt = lochnagar_sc_set_usb_fmt, ++ .auto_selectable_formats = &lochnagar_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver lochnagar_sc_dai[] = { +diff --git a/sound/soc/codecs/lpass-macro-common.c b/sound/soc/codecs/lpass-macro-common.c +--- a/sound/soc/codecs/lpass-macro-common.c ++++ b/sound/soc/codecs/lpass-macro-common.c +@@ -1,6 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-only + // Copyright (c) 2022, The Linux Foundation. All rights reserved. + ++#include + #include + #include + #include +@@ -71,21 +72,16 @@ EXPORT_SYMBOL_GPL(lpass_macro_pds_exit); + + void lpass_macro_set_codec_version(enum lpass_codec_version version) + { +- mutex_lock(&lpass_codec_mutex); ++ guard(mutex)(&lpass_codec_mutex); + lpass_codec_version = version; +- mutex_unlock(&lpass_codec_mutex); + } + EXPORT_SYMBOL_GPL(lpass_macro_set_codec_version); + + enum lpass_codec_version lpass_macro_get_codec_version(void) + { +- enum lpass_codec_version ver; ++ guard(mutex)(&lpass_codec_mutex); + +- mutex_lock(&lpass_codec_mutex); +- ver = lpass_codec_version; +- mutex_unlock(&lpass_codec_mutex); +- +- return ver; ++ return lpass_codec_version; + } + EXPORT_SYMBOL_GPL(lpass_macro_get_codec_version); + +diff --git a/sound/soc/codecs/lpass-rx-macro.c b/sound/soc/codecs/lpass-rx-macro.c +--- a/sound/soc/codecs/lpass-rx-macro.c ++++ b/sound/soc/codecs/lpass-rx-macro.c +@@ -6,6 +6,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2030,9 +2031,10 @@ static struct snd_soc_dai_driver rx_macro_dai[] = { + }, + }; + +-static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) ++static int rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) + { + struct regmap *regmap = rx->regmap; ++ int ret; + + if (mclk_enable) { + if (rx->rx_mclk_users == 0) { +@@ -2047,14 +2049,16 @@ static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) + CDC_RX_FS_MCLK_CNT_EN_MASK, + CDC_RX_FS_MCLK_CNT_ENABLE); + regcache_mark_dirty(regmap); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) ++ return ret; + } + rx->rx_mclk_users++; + } else { + if (rx->rx_mclk_users <= 0) { + dev_err(rx->dev, "%s: clock already disabled\n", __func__); + rx->rx_mclk_users = 0; +- return; ++ return 0; + } + rx->rx_mclk_users--; + if (rx->rx_mclk_users == 0) { +@@ -2068,6 +2072,8 @@ static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) + CDC_RX_CLK_MCLK2_EN_MASK, 0x0); + } + } ++ ++ return 0; + } + + static int rx_macro_mclk_event(struct snd_soc_dapm_widget *w, +@@ -2079,11 +2085,9 @@ static int rx_macro_mclk_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +- rx_macro_mclk_enable(rx, true); +- break; ++ return rx_macro_mclk_enable(rx, true); + case SND_SOC_DAPM_POST_PMD: +- rx_macro_mclk_enable(rx, false); +- break; ++ return rx_macro_mclk_enable(rx, false); + default: + dev_err(component->dev, "%s: invalid DAPM event %d\n", __func__, event); + ret = -EINVAL; +@@ -3671,14 +3675,16 @@ static int swclk_gate_enable(struct clk_hw *hw) + struct rx_macro *rx = to_rx_macro(hw); + int ret; + +- ret = clk_prepare_enable(rx->mclk); ++ ret = pm_runtime_resume_and_get(rx->dev); ++ if (ret < 0) ++ return ret; ++ ++ ret = rx_macro_mclk_enable(rx, true); + if (ret) { +- dev_err(rx->dev, "unable to prepare mclk\n"); ++ clk_disable_unprepare(rx->mclk); + return ret; + } + +- rx_macro_mclk_enable(rx, true); +- + regmap_update_bits(rx->regmap, CDC_RX_CLK_RST_CTRL_SWR_CONTROL, + CDC_RX_SWR_CLK_EN_MASK, 1); + +@@ -3693,7 +3699,7 @@ static void swclk_gate_disable(struct clk_hw *hw) + CDC_RX_SWR_CLK_EN_MASK, 0); + + rx_macro_mclk_enable(rx, false); +- clk_disable_unprepare(rx->mclk); ++ pm_runtime_put_autosuspend(rx->dev); + } + + static int swclk_gate_is_enabled(struct clk_hw *hw) +@@ -3864,28 +3870,31 @@ static int rx_macro_probe(struct platform_device *pdev) + rx->dev = dev; + + /* set MCLK and NPL rates */ +- clk_set_rate(rx->mclk, MCLK_FREQ); +- clk_set_rate(rx->npl, MCLK_FREQ); +- +- ret = clk_prepare_enable(rx->macro); ++ ret = clk_set_rate(rx->mclk, MCLK_FREQ); + if (ret) + return ret; + +- ret = clk_prepare_enable(rx->dcodec); ++ ret = clk_set_rate(rx->npl, MCLK_FREQ); + if (ret) +- goto err_dcodec; ++ return ret; + +- ret = clk_prepare_enable(rx->mclk); ++ ret = devm_pm_clk_create(dev); + if (ret) +- goto err_mclk; ++ return ret; + +- ret = clk_prepare_enable(rx->npl); +- if (ret) +- goto err_npl; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ return ret; + +- ret = clk_prepare_enable(rx->fsgen); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_fsgen; ++ return ret; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret) ++ return ret; + + /* reset swr block */ + regmap_update_bits(rx->regmap, CDC_RX_CLK_RST_CTRL_SWR_CONTROL, +@@ -3902,46 +3911,25 @@ static int rx_macro_probe(struct platform_device *pdev) + rx_macro_dai, + ARRAY_SIZE(rx_macro_dai)); + if (ret) +- goto err_clkout; +- +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = rx_macro_register_mclk_output(rx); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; ++ ++ ret = pm_runtime_put_autosuspend(dev); ++ if (ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", ret); + + return 0; + +-err_clkout: +- clk_disable_unprepare(rx->fsgen); +-err_fsgen: +- clk_disable_unprepare(rx->npl); +-err_npl: +- clk_disable_unprepare(rx->mclk); +-err_mclk: +- clk_disable_unprepare(rx->dcodec); +-err_dcodec: +- clk_disable_unprepare(rx->macro); ++err_rpm_put: ++ if (pm_runtime_put_sync_suspend(dev) < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind\n"); + + return ret; + } + +-static void rx_macro_remove(struct platform_device *pdev) +-{ +- struct rx_macro *rx = dev_get_drvdata(&pdev->dev); +- +- clk_disable_unprepare(rx->mclk); +- clk_disable_unprepare(rx->npl); +- clk_disable_unprepare(rx->fsgen); +- clk_disable_unprepare(rx->macro); +- clk_disable_unprepare(rx->dcodec); +-} +- + static const struct of_device_id rx_macro_dt_match[] = { + { + .compatible = "qcom,sc7280-lpass-rx-macro", +@@ -3969,13 +3957,17 @@ MODULE_DEVICE_TABLE(of, rx_macro_dt_match); + static int rx_macro_runtime_suspend(struct device *dev) + { + struct rx_macro *rx = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(rx->regmap, true); +- regcache_mark_dirty(rx->regmap); + +- clk_disable_unprepare(rx->fsgen); +- clk_disable_unprepare(rx->npl); +- clk_disable_unprepare(rx->mclk); ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(rx->regmap, false); ++ return ret; ++ } ++ ++ regcache_mark_dirty(rx->regmap); + + return 0; + } +@@ -3985,33 +3977,23 @@ static int rx_macro_runtime_resume(struct device *dev) + struct rx_macro *rx = dev_get_drvdata(dev); + int ret; + +- ret = clk_prepare_enable(rx->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(dev, "unable to prepare mclk\n"); ++ regcache_cache_only(rx->regmap, true); ++ regcache_mark_dirty(rx->regmap); + return ret; + } + +- ret = clk_prepare_enable(rx->npl); +- if (ret) { +- dev_err(dev, "unable to prepare mclkx2\n"); +- goto err_npl; +- } +- +- ret = clk_prepare_enable(rx->fsgen); +- if (ret) { +- dev_err(dev, "unable to prepare fsgen\n"); +- goto err_fsgen; +- } + regcache_cache_only(rx->regmap, false); +- regcache_sync(rx->regmap); ++ ret = regcache_sync(rx->regmap); ++ if (ret) { ++ regcache_cache_only(rx->regmap, true); ++ regcache_mark_dirty(rx->regmap); ++ pm_clk_suspend(dev); ++ return ret; ++ } + + return 0; +-err_fsgen: +- clk_disable_unprepare(rx->npl); +-err_npl: +- clk_disable_unprepare(rx->mclk); +- +- return ret; + } + + static const struct dev_pm_ops rx_macro_pm_ops = { +@@ -4026,7 +4008,6 @@ static struct platform_driver rx_macro_driver = { + .pm = pm_ptr(&rx_macro_pm_ops), + }, + .probe = rx_macro_probe, +- .remove = rx_macro_remove, + }; + + module_platform_driver(rx_macro_driver); +diff --git a/sound/soc/codecs/lpass-tx-macro.c b/sound/soc/codecs/lpass-tx-macro.c +--- a/sound/soc/codecs/lpass-tx-macro.c ++++ b/sound/soc/codecs/lpass-tx-macro.c +@@ -6,6 +6,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -614,6 +615,7 @@ static int tx_macro_mclk_enable(struct tx_macro *tx, + bool mclk_enable) + { + struct regmap *regmap = tx->regmap; ++ int ret; + + if (mclk_enable) { + if (tx->tx_mclk_users == 0) { +@@ -626,7 +628,9 @@ static int tx_macro_mclk_enable(struct tx_macro *tx, + CDC_TX_FS_CNT_EN_MASK, + CDC_TX_FS_CNT_ENABLE); + regcache_mark_dirty(regmap); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) ++ return ret; + } + tx->tx_mclk_users++; + } else { +@@ -739,11 +743,9 @@ static int tx_macro_mclk_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +- tx_macro_mclk_enable(tx, true); +- break; ++ return tx_macro_mclk_enable(tx, true); + case SND_SOC_DAPM_POST_PMD: +- tx_macro_mclk_enable(tx, false); +- break; ++ return tx_macro_mclk_enable(tx, false); + default: + break; + } +@@ -2149,17 +2151,20 @@ static int swclk_gate_enable(struct clk_hw *hw) + struct regmap *regmap = tx->regmap; + int ret; + +- ret = clk_prepare_enable(tx->mclk); ++ ret = pm_runtime_resume_and_get(tx->dev); ++ if (ret < 0) ++ return ret; ++ ++ ret = tx_macro_mclk_enable(tx, true); + if (ret) { +- dev_err(tx->dev, "failed to enable mclk\n"); ++ pm_runtime_put_autosuspend(tx->dev); + return ret; + } + +- tx_macro_mclk_enable(tx, true); +- + regmap_update_bits(regmap, CDC_TX_CLK_RST_CTRL_SWR_CONTROL, + CDC_TX_SWR_CLK_EN_MASK, + CDC_TX_SWR_CLK_ENABLE); ++ + return 0; + } + +@@ -2172,7 +2177,7 @@ static void swclk_gate_disable(struct clk_hw *hw) + CDC_TX_SWR_CLK_EN_MASK, 0x0); + + tx_macro_mclk_enable(tx, false); +- clk_disable_unprepare(tx->mclk); ++ pm_runtime_put_autosuspend(tx->dev); + } + + static int swclk_gate_is_enabled(struct clk_hw *hw) +@@ -2315,28 +2320,31 @@ static int tx_macro_probe(struct platform_device *pdev) + tx->active_decimator[TX_MACRO_AIF3_CAP] = -1; + + /* set MCLK and NPL rates */ +- clk_set_rate(tx->mclk, MCLK_FREQ); +- clk_set_rate(tx->npl, MCLK_FREQ); +- +- ret = clk_prepare_enable(tx->macro); ++ ret = clk_set_rate(tx->mclk, MCLK_FREQ); + if (ret) + goto err; + +- ret = clk_prepare_enable(tx->dcodec); ++ ret = clk_set_rate(tx->npl, MCLK_FREQ); + if (ret) +- goto err_dcodec; ++ goto err; + +- ret = clk_prepare_enable(tx->mclk); ++ ret = devm_pm_clk_create(dev); + if (ret) +- goto err_mclk; ++ goto err; + +- ret = clk_prepare_enable(tx->npl); +- if (ret) +- goto err_npl; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ goto err; + +- ret = clk_prepare_enable(tx->fsgen); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_fsgen; ++ goto err; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) ++ goto err; + + + /* reset soundwire block */ +@@ -2356,30 +2364,21 @@ static int tx_macro_probe(struct platform_device *pdev) + tx_macro_dai, + ARRAY_SIZE(tx_macro_dai)); + if (ret) +- goto err_clkout; +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = tx_macro_register_mclk_output(tx); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; ++ ++ ret = pm_runtime_put_autosuspend(dev); ++ if (ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", ret); + + return 0; + +-err_clkout: +- clk_disable_unprepare(tx->fsgen); +-err_fsgen: +- clk_disable_unprepare(tx->npl); +-err_npl: +- clk_disable_unprepare(tx->mclk); +-err_mclk: +- clk_disable_unprepare(tx->dcodec); +-err_dcodec: +- clk_disable_unprepare(tx->macro); ++err_rpm_put: ++ if (pm_runtime_put_sync_suspend(dev) < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind\n"); + err: + lpass_macro_pds_exit(tx->pds); + +@@ -2390,25 +2389,23 @@ static void tx_macro_remove(struct platform_device *pdev) + { + struct tx_macro *tx = dev_get_drvdata(&pdev->dev); + +- clk_disable_unprepare(tx->macro); +- clk_disable_unprepare(tx->dcodec); +- clk_disable_unprepare(tx->mclk); +- clk_disable_unprepare(tx->npl); +- clk_disable_unprepare(tx->fsgen); +- + lpass_macro_pds_exit(tx->pds); + } + + static int tx_macro_runtime_suspend(struct device *dev) + { + struct tx_macro *tx = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(tx->regmap, true); +- regcache_mark_dirty(tx->regmap); + +- clk_disable_unprepare(tx->fsgen); +- clk_disable_unprepare(tx->npl); +- clk_disable_unprepare(tx->mclk); ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(tx->regmap, false); ++ return ret; ++ } ++ ++ regcache_mark_dirty(tx->regmap); + + return 0; + } +@@ -2418,34 +2415,23 @@ static int tx_macro_runtime_resume(struct device *dev) + struct tx_macro *tx = dev_get_drvdata(dev); + int ret; + +- ret = clk_prepare_enable(tx->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(dev, "unable to prepare mclk\n"); ++ regcache_cache_only(tx->regmap, true); ++ regcache_mark_dirty(tx->regmap); + return ret; + } + +- ret = clk_prepare_enable(tx->npl); +- if (ret) { +- dev_err(dev, "unable to prepare npl\n"); +- goto err_npl; +- } +- +- ret = clk_prepare_enable(tx->fsgen); +- if (ret) { +- dev_err(dev, "unable to prepare fsgen\n"); +- goto err_fsgen; +- } +- + regcache_cache_only(tx->regmap, false); +- regcache_sync(tx->regmap); ++ ret = regcache_sync(tx->regmap); ++ if (ret) { ++ regcache_cache_only(tx->regmap, true); ++ regcache_mark_dirty(tx->regmap); ++ pm_clk_suspend(dev); ++ return ret; ++ } + + return 0; +-err_fsgen: +- clk_disable_unprepare(tx->npl); +-err_npl: +- clk_disable_unprepare(tx->mclk); +- +- return ret; + } + + static const struct dev_pm_ops tx_macro_pm_ops = { +diff --git a/sound/soc/codecs/lpass-va-macro.c b/sound/soc/codecs/lpass-va-macro.c +--- a/sound/soc/codecs/lpass-va-macro.c ++++ b/sound/soc/codecs/lpass-va-macro.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1351,34 +1352,43 @@ static int fsgen_gate_enable(struct clk_hw *hw) + { + struct va_macro *va = to_va_macro(hw); + struct regmap *regmap = va->regmap; +- int ret; ++ int ret, rpm_ret; + +- if (va->has_swr_master) { +- ret = clk_prepare_enable(va->mclk); +- if (ret) +- return ret; +- } ++ ret = pm_runtime_resume_and_get(va->dev); ++ if (ret < 0) ++ return ret; + + ret = va_macro_mclk_enable(va, true); ++ if (ret) { ++ rpm_ret = pm_runtime_put_autosuspend(va->dev); ++ if (rpm_ret < 0) ++ dev_warn(va->dev, ++ "runtime PM put failed in fsgen enable unwind: %d\n", ++ rpm_ret); ++ return ret; ++ } + if (va->has_swr_master) + regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_SWR_CONTROL, + CDC_VA_SWR_CLK_EN_MASK, CDC_VA_SWR_CLK_ENABLE); + +- return ret; ++ return 0; + } + + static void fsgen_gate_disable(struct clk_hw *hw) + { + struct va_macro *va = to_va_macro(hw); + struct regmap *regmap = va->regmap; ++ int ret; + + if (va->has_swr_master) + regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_SWR_CONTROL, + CDC_VA_SWR_CLK_EN_MASK, 0x0); + + va_macro_mclk_enable(va, false); +- if (va->has_swr_master) +- clk_disable_unprepare(va->mclk); ++ ++ ret = pm_runtime_put_autosuspend(va->dev); ++ if (ret < 0) ++ dev_warn(va->dev, "runtime PM put failed in fsgen disable: %d\n", ret); + } + + static int fsgen_gate_is_enabled(struct clk_hw *hw) +@@ -1539,6 +1549,7 @@ static int va_macro_probe(struct platform_device *pdev) + void __iomem *base; + u32 sample_rate = 0; + int ret; ++ int rpm_ret; + + va = devm_kzalloc(dev, sizeof(*va), GFP_KERNEL); + if (!va) +@@ -1594,7 +1605,9 @@ static int va_macro_probe(struct platform_device *pdev) + va->has_npl_clk = data->has_npl_clk; + + /* mclk rate */ +- clk_set_rate(va->mclk, 2 * VA_MACRO_MCLK_FREQ); ++ ret = clk_set_rate(va->mclk, 2 * VA_MACRO_MCLK_FREQ); ++ if (ret) ++ goto err; + + if (va->has_npl_clk) { + va->npl = devm_clk_get(dev, "npl"); +@@ -1603,25 +1616,29 @@ static int va_macro_probe(struct platform_device *pdev) + goto err; + } + +- clk_set_rate(va->npl, 2 * VA_MACRO_MCLK_FREQ); ++ ret = clk_set_rate(va->npl, 2 * VA_MACRO_MCLK_FREQ); ++ if (ret) ++ goto err; + } + +- ret = clk_prepare_enable(va->macro); ++ ret = devm_pm_clk_create(dev); + if (ret) + goto err; + +- ret = clk_prepare_enable(va->dcodec); +- if (ret) +- goto err_dcodec; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ goto err; + +- ret = clk_prepare_enable(va->mclk); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_mclk; ++ goto err; + +- if (va->has_npl_clk) { +- ret = clk_prepare_enable(va->npl); +- if (ret) +- goto err_npl; ++ rpm_ret = pm_runtime_resume_and_get(dev); ++ if (rpm_ret < 0) { ++ ret = rpm_ret; ++ goto err; + } + + /** +@@ -1634,7 +1651,7 @@ static int va_macro_probe(struct platform_device *pdev) + /* read version from register */ + ret = va_macro_set_lpass_codec_version(va); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; + } + + if (va->has_swr_master) { +@@ -1664,35 +1681,28 @@ static int va_macro_probe(struct platform_device *pdev) + va_macro_dais, + ARRAY_SIZE(va_macro_dais)); + if (ret) +- goto err_clkout; +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = va_macro_register_fsgen_output(va); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; + + va->fsgen = devm_clk_hw_get_clk(dev, &va->hw, "fsgen"); + if (IS_ERR(va->fsgen)) { + ret = PTR_ERR(va->fsgen); +- goto err_clkout; ++ goto err_rpm_put; + } + ++ rpm_ret = pm_runtime_put_autosuspend(dev); ++ if (rpm_ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", rpm_ret); ++ + return 0; + +-err_clkout: +- if (va->has_npl_clk) +- clk_disable_unprepare(va->npl); +-err_npl: +- clk_disable_unprepare(va->mclk); +-err_mclk: +- clk_disable_unprepare(va->dcodec); +-err_dcodec: +- clk_disable_unprepare(va->macro); ++err_rpm_put: ++ rpm_ret = pm_runtime_put_sync_suspend(dev); ++ if (rpm_ret < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind: %d\n", rpm_ret); + err: + lpass_macro_pds_exit(va->pds); + +@@ -1703,53 +1713,52 @@ static void va_macro_remove(struct platform_device *pdev) + { + struct va_macro *va = dev_get_drvdata(&pdev->dev); + +- if (va->has_npl_clk) +- clk_disable_unprepare(va->npl); +- +- clk_disable_unprepare(va->mclk); +- clk_disable_unprepare(va->dcodec); +- clk_disable_unprepare(va->macro); +- + lpass_macro_pds_exit(va->pds); + } + + static int va_macro_runtime_suspend(struct device *dev) + { + struct va_macro *va = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(va->regmap, true); ++ ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(va->regmap, false); ++ return ret; ++ } ++ + regcache_mark_dirty(va->regmap); + +- if (va->has_npl_clk) +- clk_disable_unprepare(va->npl); +- +- clk_disable_unprepare(va->mclk); +- + return 0; + } + + static int va_macro_runtime_resume(struct device *dev) + { + struct va_macro *va = dev_get_drvdata(dev); +- int ret; ++ int ret, sret; + +- ret = clk_prepare_enable(va->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(va->dev, "unable to prepare mclk\n"); ++ regcache_cache_only(va->regmap, true); ++ regcache_mark_dirty(va->regmap); + return ret; + } + +- if (va->has_npl_clk) { +- ret = clk_prepare_enable(va->npl); +- if (ret) { +- clk_disable_unprepare(va->mclk); +- dev_err(va->dev, "unable to prepare npl\n"); +- return ret; +- } +- } +- + regcache_cache_only(va->regmap, false); +- regcache_sync(va->regmap); ++ ++ ret = regcache_sync(va->regmap); ++ if (ret) { ++ regcache_cache_only(va->regmap, true); ++ regcache_mark_dirty(va->regmap); ++ sret = pm_clk_suspend(dev); ++ if (sret) ++ dev_err(va->dev, ++ "failed to suspend clocks after regcache sync failure: %d\n", ++ sret); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c +--- a/sound/soc/codecs/lpass-wsa-macro.c ++++ b/sound/soc/codecs/lpass-wsa-macro.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2529,15 +2530,13 @@ static const struct snd_soc_dapm_route wsa_audio_map[] = { + static int wsa_swrm_clock(struct wsa_macro *wsa, bool enable) + { + struct regmap *regmap = wsa->regmap; ++ int ret; + + if (enable) { +- int ret; +- +- ret = clk_prepare_enable(wsa->mclk); +- if (ret) { +- dev_err(wsa->dev, "failed to enable mclk\n"); ++ ret = pm_runtime_resume_and_get(wsa->dev); ++ if (ret < 0) + return ret; +- } ++ + wsa_macro_mclk_enable(wsa, true); + + regmap_update_bits(regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, +@@ -2548,7 +2547,10 @@ static int wsa_swrm_clock(struct wsa_macro *wsa, bool enable) + regmap_update_bits(regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, + CDC_WSA_SWR_CLK_EN_MASK, 0); + wsa_macro_mclk_enable(wsa, false); +- clk_disable_unprepare(wsa->mclk); ++ ++ ret = pm_runtime_put_autosuspend(wsa->dev); ++ if (ret < 0) ++ dev_warn(wsa->dev, "runtime PM put failed: %d\n", ret); + } + + return 0; +@@ -2656,7 +2658,7 @@ static int wsa_macro_register_mclk_output(struct wsa_macro *wsa) + init.num_parents = 1; + wsa->hw.init = &init; + hw = &wsa->hw; +- ret = clk_hw_register(wsa->dev, hw); ++ ret = devm_clk_hw_register(wsa->dev, hw); + if (ret) + return ret; + +@@ -2771,28 +2773,31 @@ static int wsa_macro_probe(struct platform_device *pdev) + wsa->dev = dev; + + /* set MCLK and NPL rates */ +- clk_set_rate(wsa->mclk, WSA_MACRO_MCLK_FREQ); +- clk_set_rate(wsa->npl, WSA_MACRO_MCLK_FREQ); +- +- ret = clk_prepare_enable(wsa->macro); ++ ret = clk_set_rate(wsa->mclk, WSA_MACRO_MCLK_FREQ); + if (ret) +- goto err; ++ return ret; + +- ret = clk_prepare_enable(wsa->dcodec); ++ ret = clk_set_rate(wsa->npl, WSA_MACRO_MCLK_FREQ); + if (ret) +- goto err_dcodec; ++ return ret; + +- ret = clk_prepare_enable(wsa->mclk); ++ ret = devm_pm_clk_create(dev); + if (ret) +- goto err_mclk; ++ return ret; + +- ret = clk_prepare_enable(wsa->npl); +- if (ret) +- goto err_npl; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ return ret; + +- ret = clk_prepare_enable(wsa->fsgen); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_fsgen; ++ return ret; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) ++ return ret; + + /* reset swr ip */ + regmap_update_bits(wsa->regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, +@@ -2809,56 +2814,37 @@ static int wsa_macro_probe(struct platform_device *pdev) + wsa_macro_dai, + ARRAY_SIZE(wsa_macro_dai)); + if (ret) +- goto err_clkout; +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = wsa_macro_register_mclk_output(wsa); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; ++ ++ ret = pm_runtime_put_autosuspend(dev); ++ if (ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", ret); + + return 0; +- +-err_clkout: +- clk_disable_unprepare(wsa->fsgen); +-err_fsgen: +- clk_disable_unprepare(wsa->npl); +-err_npl: +- clk_disable_unprepare(wsa->mclk); +-err_mclk: +- clk_disable_unprepare(wsa->dcodec); +-err_dcodec: +- clk_disable_unprepare(wsa->macro); +-err: ++err_rpm_put: ++ if (pm_runtime_put_sync_suspend(dev) < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind\n"); + return ret; +- +-} +- +-static void wsa_macro_remove(struct platform_device *pdev) +-{ +- struct wsa_macro *wsa = dev_get_drvdata(&pdev->dev); +- +- clk_disable_unprepare(wsa->macro); +- clk_disable_unprepare(wsa->dcodec); +- clk_disable_unprepare(wsa->mclk); +- clk_disable_unprepare(wsa->npl); +- clk_disable_unprepare(wsa->fsgen); + } + + static int wsa_macro_runtime_suspend(struct device *dev) + { + struct wsa_macro *wsa = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(wsa->regmap, true); +- regcache_mark_dirty(wsa->regmap); + +- clk_disable_unprepare(wsa->fsgen); +- clk_disable_unprepare(wsa->npl); +- clk_disable_unprepare(wsa->mclk); ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(wsa->regmap, false); ++ return ret; ++ } ++ ++ regcache_mark_dirty(wsa->regmap); + + return 0; + } +@@ -2866,36 +2852,29 @@ static int wsa_macro_runtime_suspend(struct device *dev) + static int wsa_macro_runtime_resume(struct device *dev) + { + struct wsa_macro *wsa = dev_get_drvdata(dev); +- int ret; ++ int ret, sret; + +- ret = clk_prepare_enable(wsa->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(dev, "unable to prepare mclk\n"); ++ regcache_cache_only(wsa->regmap, true); ++ regcache_mark_dirty(wsa->regmap); ++ return ret; ++ } ++ regcache_cache_only(wsa->regmap, false); ++ ++ ret = regcache_sync(wsa->regmap); ++ if (ret) { ++ regcache_cache_only(wsa->regmap, true); ++ regcache_mark_dirty(wsa->regmap); ++ sret = pm_clk_suspend(dev); ++ if (sret) ++ dev_err(dev, ++ "failed to suspend clocks after regcache sync failure: %d\n", ++ sret); + return ret; + } + +- ret = clk_prepare_enable(wsa->npl); +- if (ret) { +- dev_err(dev, "unable to prepare mclkx2\n"); +- goto err_npl; +- } +- +- ret = clk_prepare_enable(wsa->fsgen); +- if (ret) { +- dev_err(dev, "unable to prepare fsgen\n"); +- goto err_fsgen; +- } +- +- regcache_cache_only(wsa->regmap, false); +- regcache_sync(wsa->regmap); +- + return 0; +-err_fsgen: +- clk_disable_unprepare(wsa->npl); +-err_npl: +- clk_disable_unprepare(wsa->mclk); +- +- return ret; + } + + static const struct dev_pm_ops wsa_macro_pm_ops = { +@@ -2929,7 +2908,6 @@ static struct platform_driver wsa_macro_driver = { + .pm = pm_ptr(&wsa_macro_pm_ops), + }, + .probe = wsa_macro_probe, +- .remove = wsa_macro_remove, + }; + + module_platform_driver(wsa_macro_driver); +diff --git a/sound/soc/codecs/madera.c b/sound/soc/codecs/madera.c +--- a/sound/soc/codecs/madera.c ++++ b/sound/soc/codecs/madera.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -513,7 +514,7 @@ int madera_domain_clk_ev(struct snd_soc_dapm_widget *w, + * We can't rely on the DAPM mutex for locking because we need a lock + * that can safely be called in hw_params + */ +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +@@ -532,8 +533,6 @@ int madera_domain_clk_ev(struct snd_soc_dapm_widget *w, + + madera_debug_dump_domain_groups(priv); + +- mutex_unlock(&priv->rate_lock); +- + return 0; + } + EXPORT_SYMBOL_GPL(madera_domain_clk_ev); +@@ -875,9 +874,8 @@ static int madera_adsp_rate_get(struct snd_kcontrol *kcontrol, + const int adsp_num = e->shift_l; + int item; + +- mutex_lock(&priv->rate_lock); +- cached_rate = priv->adsp_rate_cache[adsp_num]; +- mutex_unlock(&priv->rate_lock); ++ scoped_guard(mutex, &priv->rate_lock) ++ cached_rate = priv->adsp_rate_cache[adsp_num]; + + item = snd_soc_enum_val_to_item(e, cached_rate); + ucontrol->value.enumerated.item[0] = item; +@@ -893,7 +891,6 @@ static int madera_adsp_rate_put(struct snd_kcontrol *kcontrol, + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + const int adsp_num = e->shift_l; + const unsigned int item = ucontrol->value.enumerated.item[0]; +- int ret = 0; + + if (item >= e->items) + return -EINVAL; +@@ -903,22 +900,20 @@ static int madera_adsp_rate_put(struct snd_kcontrol *kcontrol, + * maintain consistent behaviour that rate domains cannot be changed + * while in use since this is a hardware requirement + */ +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + if (!madera_can_change_grp_rate(priv, priv->adsp[adsp_num].cs_dsp.base)) { + dev_warn(priv->madera->dev, + "Cannot change '%s' while in use by active audio paths\n", + kcontrol->id.name); +- ret = -EBUSY; ++ return -EBUSY; + } else if (priv->adsp_rate_cache[adsp_num] != e->values[item]) { + /* Volatile register so defer until the codec is powered up */ + priv->adsp_rate_cache[adsp_num] = e->values[item]; +- ret = 1; ++ return 1; + } + +- mutex_unlock(&priv->rate_lock); +- +- return ret; ++ return 0; + } + + static const struct soc_enum madera_adsp_rate_enum[] = { +@@ -1061,15 +1056,13 @@ int madera_rate_put(struct snd_kcontrol *kcontrol, + * Prevent the domain powering up while we're checking whether it's + * safe to change rate domain + */ +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + val = snd_soc_component_read(component, e->reg); + val >>= e->shift_l; + val &= e->mask; +- if (snd_soc_enum_item_to_val(e, item) == val) { +- ret = 0; +- goto out; +- } ++ if (snd_soc_enum_item_to_val(e, item) == val) ++ return 0; + + if (!madera_can_change_grp_rate(priv, e->reg)) { + dev_warn(priv->madera->dev, +@@ -1082,8 +1075,6 @@ int madera_rate_put(struct snd_kcontrol *kcontrol, + ret = snd_soc_put_enum_double(kcontrol, ucontrol); + madera_spin_sysclk(priv); + } +-out: +- mutex_unlock(&priv->rate_lock); + + return ret; + } +@@ -3041,12 +3032,11 @@ static int madera_hw_params_rate(struct snd_pcm_substream *substream, + if ((cur & MADERA_AIF1_RATE_MASK) == (tar & MADERA_AIF1_RATE_MASK)) + return 0; + +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + if (!madera_can_change_grp_rate(priv, base + MADERA_AIF_RATE_CTRL)) { + madera_aif_warn(dai, "Cannot change rate while active\n"); +- ret = -EBUSY; +- goto out; ++ return -EBUSY; + } + + /* Guard the rate change with SYSCLK cycles */ +@@ -3055,9 +3045,6 @@ static int madera_hw_params_rate(struct snd_pcm_substream *substream, + MADERA_AIF1_RATE_MASK, tar); + madera_spin_sysclk(priv); + +-out: +- mutex_unlock(&priv->rate_lock); +- + return ret; + } + +@@ -3345,6 +3332,16 @@ static int madera_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + return 0; + } + ++static const u64 madera_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + const struct snd_soc_dai_ops madera_dai_ops = { + .startup = &madera_startup, + .set_fmt = &madera_set_fmt, +@@ -3352,6 +3349,8 @@ const struct snd_soc_dai_ops madera_dai_ops = { + .hw_params = &madera_hw_params, + .set_sysclk = &madera_dai_set_sysclk, + .set_tristate = &madera_set_tristate, ++ .auto_selectable_formats = &madera_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + EXPORT_SYMBOL_GPL(madera_dai_ops); + +diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c +--- a/sound/soc/codecs/max98090.c ++++ b/sound/soc/codecs/max98090.c +@@ -2341,6 +2341,7 @@ static irqreturn_t max98090_interrupt(int irq, void *data) + * + * @component: MAX98090 component + * @jack: jack to report detection events on ++ * @data: can be used if codec driver need extra data for configuring jack + * + * Enable microphone detection via IRQ on the MAX98090. If GPIOs are + * being used to bring out signals to the processor then only platform +@@ -2646,12 +2647,18 @@ static void max98090_i2c_remove(struct i2c_client *client) + static int max98090_runtime_resume(struct device *dev) + { + struct max98090_priv *max98090 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(max98090->regmap, false); + + max98090_reset(max98090); + +- regcache_sync(max98090->regmap); ++ ret = regcache_sync(max98090->regmap); ++ if (ret < 0) { ++ regcache_cache_only(max98090->regmap, true); ++ regcache_mark_dirty(max98090->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c +--- a/sound/soc/codecs/max98095.c ++++ b/sound/soc/codecs/max98095.c +@@ -5,6 +5,7 @@ + * Copyright 2011 Maxim Integrated Products + */ + ++#include + #include + #include + #include +@@ -1532,15 +1533,17 @@ static int max98095_put_eq_enum(struct snd_kcontrol *kcontrol, + regsave = snd_soc_component_read(component, M98095_088_CFG_LEVEL); + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, 0); + +- mutex_lock(&max98095->lock); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG); +- m98095_eq_band(component, channel, 0, coef_set->band1); +- m98095_eq_band(component, channel, 1, coef_set->band2); +- m98095_eq_band(component, channel, 2, coef_set->band3); +- m98095_eq_band(component, channel, 3, coef_set->band4); +- m98095_eq_band(component, channel, 4, coef_set->band5); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, 0); +- mutex_unlock(&max98095->lock); ++ scoped_guard(mutex, &max98095->lock) { ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, M98095_SEG); ++ m98095_eq_band(component, channel, 0, coef_set->band1); ++ m98095_eq_band(component, channel, 1, coef_set->band2); ++ m98095_eq_band(component, channel, 2, coef_set->band3); ++ m98095_eq_band(component, channel, 3, coef_set->band4); ++ m98095_eq_band(component, channel, 4, coef_set->band5); ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, 0); ++ } + + /* Restore the original on/off state */ + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, regsave); +@@ -1683,12 +1686,14 @@ static int max98095_put_bq_enum(struct snd_kcontrol *kcontrol, + regsave = snd_soc_component_read(component, M98095_088_CFG_LEVEL); + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, 0); + +- mutex_lock(&max98095->lock); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG); +- m98095_biquad_band(component, channel, 0, coef_set->band1); +- m98095_biquad_band(component, channel, 1, coef_set->band2); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, 0); +- mutex_unlock(&max98095->lock); ++ scoped_guard(mutex, &max98095->lock) { ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, M98095_SEG); ++ m98095_biquad_band(component, channel, 0, coef_set->band1); ++ m98095_biquad_band(component, channel, 1, coef_set->band2); ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, 0); ++ } + + /* Restore the original on/off state */ + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, regsave); +diff --git a/sound/soc/codecs/max98363.c b/sound/soc/codecs/max98363.c +--- a/sound/soc/codecs/max98363.c ++++ b/sound/soc/codecs/max98363.c +@@ -100,7 +100,12 @@ static int max98363_resume(struct device *dev) + return ret; + + regcache_cache_only(max98363->regmap, false); +- regcache_sync(max98363->regmap); ++ ret = regcache_sync(max98363->regmap); ++ if (ret) { ++ regcache_cache_only(max98363->regmap, true); ++ regcache_mark_dirty(max98363->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98373-i2c.c b/sound/soc/codecs/max98373-i2c.c +--- a/sound/soc/codecs/max98373-i2c.c ++++ b/sound/soc/codecs/max98373-i2c.c +@@ -488,10 +488,17 @@ static int max98373_suspend(struct device *dev) + static int max98373_resume(struct device *dev) + { + struct max98373_priv *max98373 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(max98373->regmap, false); + max98373_reset(max98373, dev); +- regcache_sync(max98373->regmap); ++ ret = regcache_sync(max98373->regmap); ++ if (ret) { ++ regcache_cache_only(max98373->regmap, true); ++ regcache_mark_dirty(max98373->regmap); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/max98373-sdw.c b/sound/soc/codecs/max98373-sdw.c +--- a/sound/soc/codecs/max98373-sdw.c ++++ b/sound/soc/codecs/max98373-sdw.c +@@ -277,7 +277,12 @@ static int max98373_resume(struct device *dev) + } + + regcache_cache_only(max98373->regmap, false); +- regcache_sync(max98373->regmap); ++ ret = regcache_sync(max98373->regmap); ++ if (ret) { ++ regcache_cache_only(max98373->regmap, true); ++ regcache_mark_dirty(max98373->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98388.c b/sound/soc/codecs/max98388.c +--- a/sound/soc/codecs/max98388.c ++++ b/sound/soc/codecs/max98388.c +@@ -863,10 +863,16 @@ static int max98388_suspend(struct device *dev) + static int max98388_resume(struct device *dev) + { + struct max98388_priv *max98388 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(max98388->regmap, false); + max98388_reset(max98388, dev); +- regcache_sync(max98388->regmap); ++ ret = regcache_sync(max98388->regmap); ++ if (ret) { ++ regcache_cache_only(max98388->regmap, true); ++ regcache_mark_dirty(max98388->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98390.c b/sound/soc/codecs/max98390.c +--- a/sound/soc/codecs/max98390.c ++++ b/sound/soc/codecs/max98390.c +@@ -788,7 +788,6 @@ static int max98390_dsm_init(struct snd_soc_component *component) + const char *vendor, *product; + struct max98390_priv *max98390 = + snd_soc_component_get_drvdata(component); +- const struct firmware *fw; + char *dsm_param; + + vendor = dmi_get_system_info(DMI_SYS_VENDOR); +@@ -805,6 +804,8 @@ static int max98390_dsm_init(struct snd_soc_component *component) + snprintf(filename, sizeof(filename), "%s", + max98390->dsm_param_name); + } ++ ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, filename, component->dev); + if (ret) { + ret = request_firmware(&fw, "dsm_param.bin", component->dev); +@@ -812,7 +813,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + ret = request_firmware(&fw, "dsmparam.bin", + component->dev); + if (ret) +- goto err; ++ return ret; + } + } + +@@ -822,8 +823,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + if (fw->size < MAX98390_DSM_PARAM_MIN_SIZE) { + dev_err(component->dev, + "param fw is invalid.\n"); +- ret = -EINVAL; +- goto err_alloc; ++ return -EINVAL; + } + dsm_param = (char *)fw->data; + param_start_addr = (dsm_param[0] & 0xff) | (dsm_param[1] & 0xff) << 8; +@@ -833,8 +833,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + fw->size < param_size + MAX98390_DSM_PAYLOAD_OFFSET) { + dev_err(component->dev, + "param fw is invalid.\n"); +- ret = -EINVAL; +- goto err_alloc; ++ return -EINVAL; + } + regmap_write(max98390->regmap, MAX98390_R203A_AMP_EN, 0x80); + dsm_param += MAX98390_DSM_PAYLOAD_OFFSET; +@@ -842,10 +841,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + dsm_param, param_size); + regmap_write(max98390->regmap, MAX98390_R23E1_DSP_GLOBAL_EN, 0x01); + +-err_alloc: +- release_firmware(fw); +-err: +- return ret; ++ return 0; + } + + static void max98390_init_regs(struct snd_soc_component *component) +@@ -952,11 +948,17 @@ static int max98390_suspend(struct device *dev) + static int max98390_resume(struct device *dev) + { + struct max98390_priv *max98390 = dev_get_drvdata(dev); ++ int ret; + + dev_dbg(dev, "%s:Enter\n", __func__); + + regcache_cache_only(max98390->regmap, false); +- regcache_sync(max98390->regmap); ++ ret = regcache_sync(max98390->regmap); ++ if (ret) { ++ regcache_cache_only(max98390->regmap, true); ++ regcache_mark_dirty(max98390->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98396.c b/sound/soc/codecs/max98396.c +--- a/sound/soc/codecs/max98396.c ++++ b/sound/soc/codecs/max98396.c +@@ -1600,19 +1600,37 @@ static int max98396_resume(struct device *dev) + if (max98396->pvdd) { + ret = regulator_enable(max98396->pvdd); + if (ret < 0) +- return ret; ++ goto err_core_supplies; + } + + if (max98396->vbat) { + ret = regulator_enable(max98396->vbat); + if (ret < 0) +- return ret; ++ goto err_pvdd; + } + + regcache_cache_only(max98396->regmap, false); + max98396_reset(max98396, dev); +- regcache_sync(max98396->regmap); ++ ret = regcache_sync(max98396->regmap); ++ if (ret < 0) { ++ regcache_cache_only(max98396->regmap, true); ++ regcache_mark_dirty(max98396->regmap); ++ goto err_vbat; ++ } ++ + return 0; ++ ++err_vbat: ++ if (max98396->vbat) ++ regulator_disable(max98396->vbat); ++err_pvdd: ++ if (max98396->pvdd) ++ regulator_disable(max98396->pvdd); ++err_core_supplies: ++ regulator_bulk_disable(MAX98396_NUM_CORE_SUPPLIES, ++ max98396->core_supplies); ++ ++ return ret; + } + + static const struct dev_pm_ops max98396_pm = { +diff --git a/sound/soc/codecs/max98927.c b/sound/soc/codecs/max98927.c +--- a/sound/soc/codecs/max98927.c ++++ b/sound/soc/codecs/max98927.c +@@ -742,11 +742,17 @@ static int max98927_suspend(struct device *dev) + static int max98927_resume(struct device *dev) + { + struct max98927_priv *max98927 = dev_get_drvdata(dev); ++ int ret; + + regmap_write(max98927->regmap, MAX98927_R0100_SOFT_RESET, + MAX98927_SOFT_RESET); + regcache_cache_only(max98927->regmap, false); +- regcache_sync(max98927->regmap); ++ ret = regcache_sync(max98927->regmap); ++ if (ret) { ++ regcache_cache_only(max98927->regmap, true); ++ regcache_mark_dirty(max98927->regmap); ++ return ret; ++ } + return 0; + } + +diff --git a/sound/soc/codecs/msm8916-wcd-analog.c b/sound/soc/codecs/msm8916-wcd-analog.c +--- a/sound/soc/codecs/msm8916-wcd-analog.c ++++ b/sound/soc/codecs/msm8916-wcd-analog.c +@@ -217,9 +217,11 @@ + #define CDC_A_RX_HPH_BIAS_LDO_OCP (0xf195) + #define CDC_A_RX_HPH_BIAS_CNP (0xf196) + #define CDC_A_RX_HPH_CNP_EN (0xf197) ++#define CDC_A_RX_HPH_L_TEST (0xf19A) + #define CDC_A_RX_HPH_L_PA_DAC_CTL (0xf19B) + #define RX_HPA_L_PA_DAC_CTL_DATA_RESET_MASK BIT(1) + #define RX_HPA_L_PA_DAC_CTL_DATA_RESET_RESET BIT(1) ++#define CDC_A_RX_HPH_R_TEST (0xf19C) + #define CDC_A_RX_HPH_R_PA_DAC_CTL (0xf19D) + #define RX_HPH_R_PA_DAC_CTL_DATA_RESET BIT(1) + #define RX_HPH_R_PA_DAC_CTL_DATA_RESET_MASK BIT(1) +@@ -232,6 +234,8 @@ + #define RX_EAR_CTL_PA_SEL_MASK BIT(7) + #define RX_EAR_CTL_PA_SEL BIT(7) + ++#define CDC_A_RX_EAR_STATUS (0xf1A1) ++ + #define CDC_A_SPKR_DAC_CTL (0xf1B0) + #define SPKR_DAC_CTL_DAC_RESET_MASK BIT(4) + #define SPKR_DAC_CTL_DAC_RESET_NORMAL 0 +@@ -250,6 +254,7 @@ + SPKR_DRV_CAL_EN | SPKR_DRV_SETTLE_EN | \ + SPKR_DRV_FW_EN | SPKR_DRV_BOOST_SET | \ + SPKR_DRV_CMFB_SET | SPKR_DRV_GAIN_SET) ++#define CDC_A_SPKR_ANA_BIAS_SET (0xf1B3) + #define CDC_A_SPKR_OCP_CTL (0xf1B4) + #define CDC_A_SPKR_PWRSTG_CTL (0xf1B5) + #define SPKR_PWRSTG_CTL_DAC_EN_MASK BIT(0) +@@ -264,6 +269,7 @@ + + #define CDC_A_SPKR_DRV_DBG (0xf1B7) + #define CDC_A_CURRENT_LIMIT (0xf1C0) ++#define CDC_A_BYPASS_MODE (0xf1C2) + #define CDC_A_BOOST_EN_CTL (0xf1C3) + #define CDC_A_SLOPE_COMP_IP_ZERO (0xf1C4) + #define CDC_A_SEC_ACCESS (0xf1D0) +@@ -286,6 +292,11 @@ static const char * const supply_names[] = { + + #define MBHC_MAX_BUTTONS (5) + ++struct wcd_reg_seq { ++ const struct reg_default *seq; ++ int seq_size; ++}; ++ + struct pm8916_wcd_analog_priv { + u16 pmic_rev; + u16 codec_version; +@@ -696,6 +707,59 @@ static int pm8916_wcd_analog_enable_ear_pa(struct snd_soc_dapm_widget *w, + return 0; + } + ++static int pm8916_wcd_analog_enable_hphl_pa(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, ++ int event) ++{ ++ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); ++ ++ /* This quirk is not required for revisions prior to CAJON_2_0 */ ++ if (priv->codec_version < 4) ++ return 0; ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ usleep_range(7000, 7100); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_L_TEST, ++ 0x04, 0x04); ++ break; ++ case SND_SOC_DAPM_POST_PMD: ++ /* wait 20 ms after the digital codec has powered down */ ++ msleep(20); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_L_TEST, ++ 0x04, 0x00); ++ break; ++ } ++ return 0; ++} ++ ++static int pm8916_wcd_analog_enable_hphr_pa(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, ++ int event) ++{ ++ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); ++ ++ /* This quirk is not required for revisions prior to CAJON_2_0 */ ++ if (priv->codec_version < 4) ++ return 0; ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ usleep_range(7000, 7100); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_R_TEST, ++ 0x04, 0x04); ++ break; ++ case SND_SOC_DAPM_POST_PMD: ++ msleep(20); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_R_TEST, ++ 0x04, 0x00); ++ break; ++ } ++ return 0; ++} ++ + static const struct reg_default wcd_reg_defaults_2_0[] = { + {CDC_A_RX_COM_OCP_CTL, 0xD1}, + {CDC_A_RX_COM_OCP_COUNT, 0xFF}, +@@ -715,9 +779,69 @@ static const struct reg_default wcd_reg_defaults_2_0[] = { + {CDC_A_MASTER_BIAS_CTL, 0x30}, + }; + ++static const struct wcd_reg_seq pm8916_data = { ++ .seq = wcd_reg_defaults_2_0, ++ .seq_size = ARRAY_SIZE(wcd_reg_defaults_2_0), ++}; ++ ++static const struct reg_default wcd_reg_defaults_pm8950[] = { ++ {CDC_A_RX_COM_OCP_CTL, 0xd1}, ++ {CDC_A_RX_COM_OCP_COUNT, 0xff}, ++ {CDC_D_SEC_ACCESS, 0xa5}, ++ {CDC_D_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_TX_1_2_OPAMP_BIAS, 0x4c}, ++ {CDC_A_NCP_FBCTRL, 0xa8}, ++ {CDC_A_NCP_VCTRL, 0xa4}, ++ {CDC_A_SPKR_DRV_CTL, 0x69}, ++ {CDC_A_SPKR_DRV_DBG, 0x01}, ++ {CDC_A_SEC_ACCESS, 0xa5}, ++ {CDC_A_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_CURRENT_LIMIT, 0x82}, ++ {CDC_A_SPKR_ANA_BIAS_SET, 0x41}, ++ {CDC_A_SPKR_DAC_CTL, 0x03}, ++ {CDC_A_SPKR_OCP_CTL, 0xe1}, ++ {CDC_A_RX_HPH_BIAS_PA, 0xfa}, ++ {CDC_A_MASTER_BIAS_CTL, 0x30}, ++ {CDC_A_MICB_1_INT_RBIAS, 0x00}, ++}; ++ ++static const struct wcd_reg_seq pm8950_data = { ++ .seq = wcd_reg_defaults_pm8950, ++ .seq_size = ARRAY_SIZE(wcd_reg_defaults_pm8950), ++}; ++ ++static const struct reg_default wcd_reg_defaults_pm8953[] = { ++ {CDC_A_RX_COM_OCP_CTL, 0xd1}, ++ {CDC_A_RX_COM_OCP_COUNT, 0xff}, ++ {CDC_D_SEC_ACCESS, 0xa5}, ++ {CDC_D_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_TX_1_2_OPAMP_BIAS, 0x4c}, ++ {CDC_A_NCP_FBCTRL, 0xa8}, ++ {CDC_A_NCP_VCTRL, 0xa4}, ++ {CDC_A_SPKR_DRV_CTL, 0x69}, ++ {CDC_A_SPKR_DRV_DBG, 0x01}, ++ {CDC_A_SEC_ACCESS, 0xa5}, ++ {CDC_A_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_CURRENT_LIMIT, 0xa2}, ++ {CDC_A_BYPASS_MODE, 0x18}, ++ {CDC_A_SPKR_ANA_BIAS_SET, 0x41}, ++ {CDC_A_SPKR_DAC_CTL, 0x03}, ++ {CDC_A_SPKR_OCP_CTL, 0xe1}, ++ {CDC_A_RX_HPH_BIAS_PA, 0xfa}, ++ {CDC_A_RX_EAR_STATUS, 0x10}, ++ {CDC_A_MASTER_BIAS_CTL, 0x30}, ++ {CDC_A_MICB_1_INT_RBIAS, 0x00}, ++}; ++ ++static const struct wcd_reg_seq pm8953_data = { ++ .seq = wcd_reg_defaults_pm8953, ++ .seq_size = ARRAY_SIZE(wcd_reg_defaults_pm8953), ++}; ++ + static int pm8916_wcd_analog_probe(struct snd_soc_component *component) + { + struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); ++ const struct wcd_reg_seq *wcd_reg_init_data; + int err, reg; + + err = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); +@@ -738,9 +862,11 @@ static int pm8916_wcd_analog_probe(struct snd_soc_component *component) + snd_soc_component_write(component, CDC_D_PERPH_RESET_CTL4, 0x01); + snd_soc_component_write(component, CDC_A_PERPH_RESET_CTL4, 0x01); + +- for (reg = 0; reg < ARRAY_SIZE(wcd_reg_defaults_2_0); reg++) +- snd_soc_component_write(component, wcd_reg_defaults_2_0[reg].reg, +- wcd_reg_defaults_2_0[reg].def); ++ wcd_reg_init_data = of_device_get_match_data(component->dev); ++ ++ for (reg = 0; reg < wcd_reg_init_data->seq_size; reg++) ++ snd_soc_component_write(component, wcd_reg_init_data->seq[reg].reg, ++ wcd_reg_init_data->seq[reg].def); + + priv->component = component; + +@@ -883,11 +1009,15 @@ static const struct snd_soc_dapm_widget pm8916_wcd_analog_dapm_widgets[] = { + SND_SOC_DAPM_MUX("EAR_S", SND_SOC_NOPM, 0, 0, &ear_mux), + SND_SOC_DAPM_SUPPLY("EAR CP", CDC_A_NCP_EN, 4, 0, NULL, 0), + +- SND_SOC_DAPM_PGA("HPHL PA", CDC_A_RX_HPH_CNP_EN, 5, 0, NULL, 0), ++ SND_SOC_DAPM_PGA_E("HPHL PA", CDC_A_RX_HPH_CNP_EN, 5, 0, NULL, 0, ++ pm8916_wcd_analog_enable_hphl_pa, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("HPHL", SND_SOC_NOPM, 0, 0, &hphl_mux), + SND_SOC_DAPM_MIXER("HPHL DAC", CDC_A_RX_HPH_L_PA_DAC_CTL, 3, 0, NULL, + 0), +- SND_SOC_DAPM_PGA("HPHR PA", CDC_A_RX_HPH_CNP_EN, 4, 0, NULL, 0), ++ SND_SOC_DAPM_PGA_E("HPHR PA", CDC_A_RX_HPH_CNP_EN, 4, 0, NULL, 0, ++ pm8916_wcd_analog_enable_hphr_pa, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("HPHR", SND_SOC_NOPM, 0, 0, &hphr_mux), + SND_SOC_DAPM_MIXER("HPHR DAC", CDC_A_RX_HPH_R_PA_DAC_CTL, 3, 0, NULL, + 0), +@@ -1259,7 +1389,9 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev) + } + + static const struct of_device_id pm8916_wcd_analog_spmi_match_table[] = { +- { .compatible = "qcom,pm8916-wcd-analog-codec", }, ++ { .compatible = "qcom,pm8916-wcd-analog-codec", .data = &pm8916_data }, ++ { .compatible = "qcom,pm8950-wcd-analog-codec", .data = &pm8950_data }, ++ { .compatible = "qcom,pm8953-wcd-analog-codec", .data = &pm8953_data }, + { } + }; + +diff --git a/sound/soc/codecs/mt6357.c b/sound/soc/codecs/mt6357.c +--- a/sound/soc/codecs/mt6357.c ++++ b/sound/soc/codecs/mt6357.c +@@ -1834,8 +1834,8 @@ static int mt6357_platform_driver_probe(struct platform_device *pdev) + } + + static const struct platform_device_id mt6357_platform_ids[] = { +- {"mt6357-sound", 0}, +- { /* sentinel */ }, ++ { .name = "mt6357-sound" }, ++ { /* sentinel */ } + }; + MODULE_DEVICE_TABLE(platform, mt6357_platform_ids); + +diff --git a/sound/soc/codecs/mt6359-accdet.c b/sound/soc/codecs/mt6359-accdet.c +--- a/sound/soc/codecs/mt6359-accdet.c ++++ b/sound/soc/codecs/mt6359-accdet.c +@@ -6,6 +6,7 @@ + // Author: Argus Lin + // + ++#include + #include + #include + #include +@@ -398,14 +399,13 @@ static void mt6359_accdet_work(struct work_struct *work) + struct mt6359_accdet *priv = + container_of(work, struct mt6359_accdet, accdet_work); + +- mutex_lock(&priv->res_lock); ++ guard(mutex)(&priv->res_lock); + priv->pre_accdet_status = priv->accdet_status; + check_jack_btn_type(priv); + + if (priv->jack_plugged && + priv->pre_accdet_status != priv->accdet_status) + mt6359_accdet_jack_report(priv); +- mutex_unlock(&priv->res_lock); + } + + static void mt6359_accdet_jd_work(struct work_struct *work) +@@ -416,7 +416,7 @@ static void mt6359_accdet_jd_work(struct work_struct *work) + struct mt6359_accdet *priv = + container_of(work, struct mt6359_accdet, jd_work); + +- mutex_lock(&priv->res_lock); ++ guard(mutex)(&priv->res_lock); + if (priv->jd_sts == M_PLUG_IN) { + priv->jack_plugged = true; + +@@ -450,7 +450,6 @@ static void mt6359_accdet_jd_work(struct work_struct *work) + + if (priv->caps & ACCDET_PMIC_EINT_IRQ) + recover_eint_setting(priv); +- mutex_unlock(&priv->res_lock); + } + + static irqreturn_t mt6359_accdet_irq(int irq, void *data) +@@ -459,7 +458,7 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + unsigned int irq_val = 0, val = 0, value = 0; + int ret; + +- mutex_lock(&priv->res_lock); ++ guard(mutex)(&priv->res_lock); + regmap_read(priv->regmap, ACCDET_IRQ_ADDR, &irq_val); + + if (irq_val & ACCDET_IRQ_MASK_SFT) { +@@ -474,7 +473,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + 1000); + if (ret) { + dev_err(priv->dev, "%s(), ret %d\n", __func__, ret); +- mutex_unlock(&priv->res_lock); + return IRQ_NONE; + } + regmap_update_bits(priv->regmap, ACCDET_IRQ_ADDR, +@@ -498,7 +496,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + if (ret) { + dev_err(priv->dev, "%s(), ret %d\n", __func__, + ret); +- mutex_unlock(&priv->res_lock); + return IRQ_NONE; + } + regmap_update_bits(priv->regmap, ACCDET_IRQ_ADDR, +@@ -521,7 +518,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + if (ret) { + dev_err(priv->dev, "%s(), ret %d\n", __func__, + ret); +- mutex_unlock(&priv->res_lock); + return IRQ_NONE; + } + regmap_update_bits(priv->regmap, ACCDET_IRQ_ADDR, +@@ -540,7 +536,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + + queue_work(priv->jd_workqueue, &priv->jd_work); + } +- mutex_unlock(&priv->res_lock); + + return IRQ_HANDLED; + } +diff --git a/sound/soc/codecs/ntpfw.c b/sound/soc/codecs/ntpfw.c +--- a/sound/soc/codecs/ntpfw.c ++++ b/sound/soc/codecs/ntpfw.c +@@ -89,7 +89,7 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) + { + struct device *dev = &i2c->dev; + const struct ntpfw_chunk *chunk; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + const u8 *data; + size_t leftover; + int ret; +@@ -101,10 +101,8 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) + return ret; + } + +- if (!ntpfw_verify(dev, fw->data, fw->size, magic)) { +- ret = -EINVAL; +- goto done; +- } ++ if (!ntpfw_verify(dev, fw->data, fw->size, magic)) ++ return -EINVAL; + + data = fw->data + sizeof(struct ntpfw_header); + leftover = fw->size - sizeof(struct ntpfw_header); +@@ -112,23 +110,18 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) + while (leftover) { + chunk = (struct ntpfw_chunk *)data; + +- if (!ntpfw_verify_chunk(dev, chunk, leftover)) { +- ret = -EINVAL; +- goto done; +- } ++ if (!ntpfw_verify_chunk(dev, chunk, leftover)) ++ return -EINVAL; + + ret = ntpfw_send_chunk(i2c, chunk); + if (ret) +- goto done; ++ return ret; + + data += be16_to_cpu(chunk->length) + sizeof(*chunk); + leftover -= be16_to_cpu(chunk->length) + sizeof(*chunk); + } + +-done: +- release_firmware(fw); +- +- return ret; ++ return 0; + } + EXPORT_SYMBOL_GPL(ntpfw_load); + +diff --git a/sound/soc/codecs/ntpfw.h b/sound/soc/codecs/ntpfw.h +--- a/sound/soc/codecs/ntpfw.h ++++ b/sound/soc/codecs/ntpfw.h +@@ -1,5 +1,5 @@ + /* SPDX-License-Identifier: GPL-2.0-only */ +-/** ++/* + * ntpfw.h - Firmware helper functions for Neofidelity codecs + * + * Copyright (c) 2024, SaluteDevices. All Rights Reserved. +@@ -13,10 +13,11 @@ + /** + * ntpfw_load - load firmware to amplifier over i2c interface. + * +- * @i2c Pointer to amplifier's I2C client. +- * @name Firmware file name. +- * @magic Magic number to validate firmware. +- * @return 0 or error code upon error. ++ * @i2c: Pointer to amplifier's I2C client. ++ * @name: Firmware file name. ++ * @magic: Magic number to validate firmware. ++ * ++ * Returns: 0 or error code upon error. + */ + int ntpfw_load(struct i2c_client *i2c, const char *name, const u32 magic); + +diff --git a/sound/soc/codecs/pcm1681.c b/sound/soc/codecs/pcm1681.c +--- a/sound/soc/codecs/pcm1681.c ++++ b/sound/soc/codecs/pcm1681.c +@@ -199,10 +199,17 @@ static int pcm1681_hw_params(struct snd_pcm_substream *substream, + return pcm1681_set_deemph(component); + } + ++static const u64 pcm1681_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J; ++ + static const struct snd_soc_dai_ops pcm1681_dai_ops = { + .set_fmt = pcm1681_set_dai_fmt, + .hw_params = pcm1681_hw_params, + .mute_stream = pcm1681_mute, ++ .auto_selectable_formats = &pcm1681_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm1754.c b/sound/soc/codecs/pcm1754.c +--- a/sound/soc/codecs/pcm1754.c ++++ b/sound/soc/codecs/pcm1754.c +@@ -78,10 +78,16 @@ static int pcm1754_mute_stream(struct snd_soc_dai *dai, int mute, int stream) + return 0; + } + ++static const u64 pcm1754_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J; ++ + static const struct snd_soc_dai_ops pcm1754_dai_ops = { + .set_fmt = pcm1754_set_dai_fmt, + .hw_params = pcm1754_hw_params, + .mute_stream = pcm1754_mute_stream, ++ .auto_selectable_formats = &pcm1754_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_driver pcm1754_dai = { +diff --git a/sound/soc/codecs/pcm1789.c b/sound/soc/codecs/pcm1789.c +--- a/sound/soc/codecs/pcm1789.c ++++ b/sound/soc/codecs/pcm1789.c +@@ -164,11 +164,18 @@ static int pcm1789_trigger(struct snd_pcm_substream *substream, int cmd, + return ret; + } + ++static const u64 pcm1789_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J; ++ + static const struct snd_soc_dai_ops pcm1789_dai_ops = { + .set_fmt = pcm1789_set_dai_fmt, + .hw_params = pcm1789_hw_params, + .mute_stream = pcm1789_mute, + .trigger = pcm1789_trigger, ++ .auto_selectable_formats = &pcm1789_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm179x.c b/sound/soc/codecs/pcm179x.c +--- a/sound/soc/codecs/pcm179x.c ++++ b/sound/soc/codecs/pcm179x.c +@@ -142,10 +142,16 @@ static int pcm179x_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 pcm179x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J; ++ + static const struct snd_soc_dai_ops pcm179x_dai_ops = { + .set_fmt = pcm179x_set_dai_fmt, + .hw_params = pcm179x_hw_params, + .mute_stream = pcm179x_mute, ++ .auto_selectable_formats = &pcm179x_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm186x.c b/sound/soc/codecs/pcm186x.c +--- a/sound/soc/codecs/pcm186x.c ++++ b/sound/soc/codecs/pcm186x.c +@@ -473,11 +473,20 @@ static int pcm186x_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + return 0; + } + ++static const u64 pcm186x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops pcm186x_dai_ops = { + .set_sysclk = pcm186x_set_dai_sysclk, + .set_tdm_slot = pcm186x_set_tdm_slot, + .set_fmt = pcm186x_set_fmt, + .hw_params = pcm186x_hw_params, ++ .auto_selectable_formats = &pcm186x_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver pcm1863_dai = { +diff --git a/sound/soc/codecs/pcm3060.c b/sound/soc/codecs/pcm3060.c +--- a/sound/soc/codecs/pcm3060.c ++++ b/sound/soc/codecs/pcm3060.c +@@ -164,10 +164,18 @@ static int pcm3060_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 pcm3060_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops pcm3060_dai_ops = { + .set_sysclk = pcm3060_set_sysclk, + .set_fmt = pcm3060_set_fmt, + .hw_params = pcm3060_hw_params, ++ .auto_selectable_formats = &pcm3060_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define PCM3060_DAI_RATES_ADC (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | \ +diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c +--- a/sound/soc/codecs/pcm512x.c ++++ b/sound/soc/codecs/pcm512x.c +@@ -6,7 +6,7 @@ + * Copyright 2014 Linaro Ltd + */ + +- ++#include + #include + #include + #include +@@ -399,10 +399,9 @@ static int pcm512x_digital_playback_switch_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + +- mutex_lock(&pcm512x->mutex); ++ guard(mutex)(&pcm512x->mutex); + ucontrol->value.integer.value[0] = !(pcm512x->mute & 0x4); + ucontrol->value.integer.value[1] = !(pcm512x->mute & 0x2); +- mutex_unlock(&pcm512x->mutex); + + return 0; + } +@@ -414,7 +413,7 @@ static int pcm512x_digital_playback_switch_put(struct snd_kcontrol *kcontrol, + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + int ret, changed = 0; + +- mutex_lock(&pcm512x->mutex); ++ guard(mutex)(&pcm512x->mutex); + + if ((pcm512x->mute & 0x4) == (ucontrol->value.integer.value[0] << 2)) { + pcm512x->mute ^= 0x4; +@@ -430,13 +429,10 @@ static int pcm512x_digital_playback_switch_put(struct snd_kcontrol *kcontrol, + if (ret != 0) { + dev_err(component->dev, + "Failed to update digital mute: %d\n", ret); +- mutex_unlock(&pcm512x->mutex); + return ret; + } + } + +- mutex_unlock(&pcm512x->mutex); +- + return changed; + } + +@@ -1465,7 +1461,7 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + int ret; + unsigned int mute_det; + +- mutex_lock(&pcm512x->mutex); ++ guard(mutex)(&pcm512x->mutex); + + if (mute) { + pcm512x->mute |= 0x1; +@@ -1475,7 +1471,7 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + if (ret != 0) { + dev_err(component->dev, + "Failed to set digital mute: %d\n", ret); +- goto unlock; ++ return ret; + } + + regmap_read_poll_timeout(pcm512x->regmap, +@@ -1488,7 +1484,7 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + if (ret != 0) { + dev_err(component->dev, + "Failed to update digital mute: %d\n", ret); +- goto unlock; ++ return ret; + } + + regmap_read_poll_timeout(pcm512x->regmap, +@@ -1499,18 +1495,24 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + 200, 10000); + } + +-unlock: +- mutex_unlock(&pcm512x->mutex); +- + return ret; + } + ++static const u64 pcm512x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B; ++ + static const struct snd_soc_dai_ops pcm512x_dai_ops = { + .startup = pcm512x_dai_startup, + .hw_params = pcm512x_hw_params, + .set_fmt = pcm512x_set_fmt, + .mute_stream = pcm512x_mute, + .set_bclk_ratio = pcm512x_set_bclk_ratio, ++ .auto_selectable_formats = &pcm512x_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm6240.c b/sound/soc/codecs/pcm6240.c +--- a/sound/soc/codecs/pcm6240.c ++++ b/sound/soc/codecs/pcm6240.c +@@ -12,6 +12,7 @@ + // Author: Shenghao Ding + // + ++#include + #include + #include + #include +@@ -605,7 +606,7 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, + unsigned int reg = mc->reg; + unsigned int val; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + + if (pcm_dev->chip_id == PCM1690) { + ret = pcmdev_dev_read(pcm_dev, dev_no, PCM1690_REG_MODE_CTRL, +@@ -613,18 +614,18 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, + if (ret) { + dev_err(pcm_dev->dev, "%s: read mode err=%d\n", + __func__, ret); +- goto out; ++ return ret; + } + val &= PCM1690_REG_MODE_CTRL_DAMS_MSK; + /* Set to wide-range mode, before using vol ctrl. */ + if (!val && vol_ctrl_type == PCMDEV_PCM1690_VOL_CTRL) { + ucontrol->value.integer.value[0] = -25500; +- goto out; ++ return ret; + } + /* Set to fine mode, before using fine vol ctrl. */ + if (val && vol_ctrl_type == PCMDEV_PCM1690_FINE_VOL_CTRL) { + ucontrol->value.integer.value[0] = -12750; +- goto out; ++ return ret; + } + } + +@@ -632,15 +633,14 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, + if (ret) { + dev_err(pcm_dev->dev, "%s: read err=%d\n", + __func__, ret); +- goto out; ++ return ret; + } + + val = (val >> shift) & mask; + val = (val > max) ? max : val; + val = mc->invert ? max - val : val; + ucontrol->value.integer.value[0] = val; +-out: +- mutex_unlock(&pcm_dev->codec_lock); ++ + return ret; + } + +@@ -678,7 +678,7 @@ static int pcmdev_put_volsw(struct snd_kcontrol *kcontrol, + unsigned int val, val_mask; + unsigned int reg = mc->reg; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + val = ucontrol->value.integer.value[0] & mask; + val = (val > max) ? max : val; + val = mc->invert ? max - val : val; +@@ -702,7 +702,7 @@ static int pcmdev_put_volsw(struct snd_kcontrol *kcontrol, + __func__, rc); + else + rc = 1; +- mutex_unlock(&pcm_dev->codec_lock); ++ + return rc; + } + +@@ -1577,10 +1577,10 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) + { + struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(comp); + struct i2c_adapter *adap = pcm_dev->client->adapter; +- const struct firmware *fw_entry = NULL; ++ const struct firmware *fw_entry __free(firmware) = NULL; + int ret, i, j; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + + pcm_dev->component = comp; + +@@ -1588,7 +1588,7 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) + for (j = 0; j < 2; j++) { + ret = pcmdev_gain_ctrl_add(pcm_dev, i, j); + if (ret < 0) +- goto out; ++ return ret; + } + } + +@@ -1621,21 +1621,17 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) + if (ret) { + dev_err(pcm_dev->dev, "%s: request %s err = %d\n", __func__, + pcm_dev->bin_name, ret); +- goto out; ++ return ret; + } + + ret = pcmdev_regbin_ready(fw_entry, pcm_dev); + if (ret) { + dev_err(pcm_dev->dev, "%s: %s parse err = %d\n", __func__, + pcm_dev->bin_name, ret); +- goto out; ++ return ret; + } +- ret = pcmdev_profile_ctrl_add(pcm_dev); +-out: +- release_firmware(fw_entry); + +- mutex_unlock(&pcm_dev->codec_lock); +- return ret; ++ return pcmdev_profile_ctrl_add(pcm_dev); + } + + +@@ -1645,9 +1641,8 @@ static void pcmdevice_comp_remove(struct snd_soc_component *codec) + + if (!pcm_dev) + return; +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + pcmdevice_config_info_remove(pcm_dev); +- mutex_unlock(&pcm_dev->codec_lock); + } + + static const struct snd_soc_dapm_widget pcmdevice_dapm_widgets[] = { +@@ -1890,9 +1885,9 @@ static int pcmdevice_mute(struct snd_soc_dai *dai, int mute, int stream) + else + block_type = PCMDEVICE_BIN_BLK_PRE_POWER_UP; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + pcmdevice_select_cfg_blk(pcm_dev, pcm_dev->cur_conf, block_type); +- mutex_unlock(&pcm_dev->codec_lock); ++ + return 0; + } + +diff --git a/sound/soc/codecs/peb2466.c b/sound/soc/codecs/peb2466.c +--- a/sound/soc/codecs/peb2466.c ++++ b/sound/soc/codecs/peb2466.c +@@ -6,6 +6,7 @@ + // + // Author: Herve Codina + ++#include + #include + #include + #include +@@ -1537,17 +1538,14 @@ static int peb2466_fw_parse(struct snd_soc_component *component, + + static int peb2466_load_coeffs(struct snd_soc_component *component, const char *fw_name) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw, fw_name, component->dev); + if (ret) + return ret; + +- ret = peb2466_fw_parse(component, fw->data, fw->size); +- release_firmware(fw); +- +- return ret; ++ return peb2466_fw_parse(component, fw->data, fw->size); + } + + static int peb2466_component_probe(struct snd_soc_component *component) +@@ -1704,13 +1702,11 @@ static int peb2466_chip_gpio_update_bits(struct peb2466 *peb2466, unsigned int x + * So, a specific cache value is used. + */ + +- mutex_lock(&peb2466->gpio.lock); ++ guard(mutex)(&peb2466->gpio.lock); + + cache = peb2466_chip_gpio_get_cache(peb2466, xr_reg); +- if (!cache) { +- ret = -EINVAL; +- goto end; +- } ++ if (!cache) ++ return -EINVAL; + + tmp = *cache; + tmp &= ~mask; +@@ -1718,14 +1714,11 @@ static int peb2466_chip_gpio_update_bits(struct peb2466 *peb2466, unsigned int x + + ret = regmap_write(peb2466->regmap, xr_reg, tmp); + if (ret) +- goto end; ++ return ret; + + *cache = tmp; +- ret = 0; + +-end: +- mutex_unlock(&peb2466->gpio.lock); +- return ret; ++ return 0; + } + + static int peb2466_chip_gpio_set(struct gpio_chip *c, unsigned int offset, +diff --git a/sound/soc/codecs/pm4125-sdw.c b/sound/soc/codecs/pm4125-sdw.c +--- a/sound/soc/codecs/pm4125-sdw.c ++++ b/sound/soc/codecs/pm4125-sdw.c +@@ -464,10 +464,16 @@ static int __maybe_unused pm4125_sdw_runtime_suspend(struct device *dev) + static int __maybe_unused pm4125_sdw_runtime_resume(struct device *dev) + { + struct pm4125_sdw_priv *priv = dev_get_drvdata(dev); ++ int ret; + + if (priv->regmap) { + regcache_cache_only(priv->regmap, false); +- regcache_sync(priv->regmap); ++ ret = regcache_sync(priv->regmap); ++ if (ret) { ++ regcache_cache_only(priv->regmap, true); ++ regcache_mark_dirty(priv->regmap); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/pm4125.c b/sound/soc/codecs/pm4125.c +--- a/sound/soc/codecs/pm4125.c ++++ b/sound/soc/codecs/pm4125.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1295,7 +1296,7 @@ static const struct irq_domain_ops pm4125_domain_ops = { + + static int pm4125_irq_init(struct pm4125_priv *pm4125, struct device *dev) + { +- pm4125->virq = irq_domain_add_linear(NULL, 1, &pm4125_domain_ops, NULL); ++ pm4125->virq = irq_domain_create_linear(NULL, 1, &pm4125_domain_ops, NULL); + if (!(pm4125->virq)) { + dev_err(dev, "%s: Failed to add IRQ domain\n", __func__); + return -EINVAL; +diff --git a/sound/soc/codecs/rt1017-sdca-sdw.c b/sound/soc/codecs/rt1017-sdca-sdw.c +--- a/sound/soc/codecs/rt1017-sdca-sdw.c ++++ b/sound/soc/codecs/rt1017-sdca-sdw.c +@@ -784,7 +784,12 @@ static int rt1017_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt1017->regmap, false); +- regcache_sync(rt1017->regmap); ++ ret = regcache_sync(rt1017->regmap); ++ if (ret) { ++ regcache_cache_only(rt1017->regmap, true); ++ regcache_mark_dirty(rt1017->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt1316-sdw.c b/sound/soc/codecs/rt1316-sdw.c +--- a/sound/soc/codecs/rt1316-sdw.c ++++ b/sound/soc/codecs/rt1316-sdw.c +@@ -756,7 +756,12 @@ static int rt1316_dev_resume(struct device *dev) + } + + regcache_cache_only(rt1316->regmap, false); +- regcache_sync(rt1316->regmap); ++ ret = regcache_sync(rt1316->regmap); ++ if (ret) { ++ regcache_cache_only(rt1316->regmap, true); ++ regcache_mark_dirty(rt1316->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt1318-sdw.c b/sound/soc/codecs/rt1318-sdw.c +--- a/sound/soc/codecs/rt1318-sdw.c ++++ b/sound/soc/codecs/rt1318-sdw.c +@@ -830,7 +830,12 @@ static int rt1318_dev_resume(struct device *dev) + return ret; + + regcache_cache_only(rt1318->regmap, false); +- regcache_sync(rt1318->regmap); ++ ret = regcache_sync(rt1318->regmap); ++ if (ret) { ++ regcache_cache_only(rt1318->regmap, true); ++ regcache_mark_dirty(rt1318->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c +--- a/sound/soc/codecs/rt1320-sdw.c ++++ b/sound/soc/codecs/rt1320-sdw.c +@@ -410,6 +410,402 @@ static const struct reg_sequence rt1321_blind_write[] = { + { 0x41001988, 0x03 }, + }; + ++static const struct reg_sequence rt1321_va1_blind_write[] = { ++ { 0x0000c003, 0xf0 }, ++ { 0x0000c01b, 0xfc }, ++ { 0x0000c5c3, 0xf2 }, ++ { 0x0000c5c2, 0x00 }, ++ { 0x0000c5c1, 0x10 }, ++ { 0x0000c5c0, 0x04 }, ++ { 0x0000c5c7, 0x03 }, ++ { 0x0000c5c6, 0x10 }, ++ { 0x0000c526, 0x47 }, ++ { 0x0000c5c4, 0x12 }, ++ { 0x0000c5c5, 0x60 }, ++ { 0x0000c520, 0x10 }, ++ { 0x0000c521, 0x32 }, ++ { 0x0000c5c7, 0x00 }, ++ { 0x0000c5c8, 0x03 }, ++ { 0x0000c5d3, 0x08 }, ++ { 0x0000c5d2, 0x0a }, ++ { 0x0000c5d1, 0x49 }, ++ { 0x0000c5d0, 0x0f }, ++ { 0x0000c580, 0x10 }, ++ { 0x0000c581, 0x32 }, ++ { 0x0000c582, 0x01 }, ++ { 0x0000c682, 0x60 }, ++ { 0x0000c019, 0x10 }, ++ { 0x0000c5f0, 0x01 }, ++ { 0x0000c5f7, 0x22 }, ++ { 0x0000c5f6, 0x22 }, ++ { 0x0000c057, 0x51 }, ++ { 0x0000c054, 0x55 }, ++ { 0x0000c053, 0x55 }, ++ { 0x0000c052, 0x55 }, ++ { 0x0000c051, 0x01 }, ++ { 0x0000c050, 0x15 }, ++ { 0x0000c060, 0x99 }, ++ { 0x0000c030, 0x55 }, ++ { 0x0000c061, 0x55 }, ++ { 0x0000c063, 0x55 }, ++ { 0x0000c065, 0xa5 }, ++ { 0x0000c06b, 0x0a }, ++ { 0x0000ca05, 0xd6 }, ++ { 0x0000ca06, 0x11 }, ++ { 0x0000ca07, 0x1e }, ++ { 0x0000ca25, 0xd6 }, ++ { 0x0000ca26, 0x11 }, ++ { 0x0000ca27, 0x1e }, ++ { 0x0000cd00, 0x05 }, ++ { 0x0000cd81, 0x49 }, ++ { 0x0000cd82, 0x49 }, ++ { 0x0000c604, 0x40 }, ++ { 0x0000c609, 0x40 }, ++ { 0x0000c046, 0xf7 }, ++ { 0x0000c045, 0xff }, ++ { 0x0000c044, 0xff }, ++ { 0x0000c043, 0xff }, ++ { 0x0000c042, 0xff }, ++ { 0x0000c041, 0xff }, ++ { 0x0000c040, 0xff }, ++ { 0x0000c049, 0xff }, ++ { 0x0000c028, 0x3f }, ++ { 0x0000c020, 0x3f }, ++ { 0x0000c032, 0x13 }, ++ { 0x0000c033, 0x01 }, ++ { 0x0000cc10, 0x01 }, ++ { 0x0000dc20, 0x03 }, ++ { 0x0000de03, 0x05 }, ++ { 0x0000dc00, 0x00 }, ++ { 0x0000c700, 0xf0 }, ++ { 0x0000c701, 0x13 }, ++ { 0x0000c900, 0xc3 }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000c086, 0x02 }, ++ { 0x0000c085, 0x7f }, ++ { 0x0000c084, 0x00 }, ++ { 0x0000c081, 0xff }, ++ { 0x0000f084, 0x0f }, ++ { 0x0000f083, 0xff }, ++ { 0x0000f082, 0xff }, ++ { 0x0000f081, 0xff }, ++ { 0x0000f080, 0xff }, ++ { 0x20003003, 0x3f }, ++ { 0x20005818, 0x81 }, ++ { 0x20009018, 0x81 }, ++ { 0x2000301c, 0x81 }, ++ { 0x0000c003, 0xc0 }, ++ { 0x0000c047, 0x80 }, ++ { 0x0000d541, 0x80 }, ++ { 0x0000d487, 0x0b }, ++ { 0x0000d487, 0x3b }, ++ { 0x0000d486, 0xc3 }, ++ { 0x0000d470, 0x89 }, ++ { 0x0000d471, 0x3a }, ++ { 0x0000d472, 0x3d }, ++ { 0x0000d474, 0x11 }, ++ { 0x0000d475, 0x32 }, ++ { 0x0000d476, 0x64 }, ++ { 0x0000d477, 0x10 }, ++ { 0x0000d478, 0xff }, ++ { 0x0000d479, 0x20 }, ++ { 0x0000d47a, 0x10 }, ++ { 0x0000d73c, 0xb7 }, ++ { 0x0000d73d, 0xd7 }, ++ { 0x0000d73e, 0x00 }, ++ { 0x0000d73f, 0x10 }, ++ { 0x1000cd56, 0x00 }, ++ { 0x3fc2dfc0, 0x03 }, ++ { 0x3fc2dfc1, 0x00 }, ++ { 0x3fc2dfc2, 0x00 }, ++ { 0x3fc2dfc3, 0x00 }, ++ { 0x3fc2dfc4, 0x01 }, ++ { 0x3fc2dfc5, 0x00 }, ++ { 0x3fc2dfc6, 0x00 }, ++ { 0x3fc2dfc7, 0x00 }, ++ { 0x3fc2df80, 0x00 }, ++ { 0x3fc2df81, 0x00 }, ++ { 0x3fc2df82, 0x00 }, ++ { 0x3fc2df83, 0x00 }, ++ { 0x0000d541, 0x40 }, ++ { 0x0000d486, 0x43 }, ++ { 0x1000db00, 0x04 }, ++ { 0x1000db01, 0x00 }, ++ { 0x1000db02, 0x10 }, ++ { 0x1000db03, 0x00 }, ++ { 0x1000db04, 0x00 }, ++ { 0x1000db05, 0x45 }, ++ { 0x1000db06, 0x0d }, ++ { 0x1000db07, 0x01 }, ++ { 0x1000db08, 0x00 }, ++ { 0x1000db09, 0x00 }, ++ { 0x1000db0a, 0xbf }, ++ { 0x1000db0b, 0x0b }, ++ { 0x1000db0c, 0x11 }, ++ { 0x1000db0d, 0x00 }, ++ { 0x1000db0e, 0x00 }, ++ { 0x1000db0f, 0x00 }, ++ { 0x1000db10, 0x2c }, ++ { 0x1000db11, 0xfa }, ++ { 0x1000db12, 0x00 }, ++ { 0x1000db13, 0x00 }, ++ { 0x1000db14, 0x09 }, ++ { 0x0000d540, 0x21 }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000d714, 0x17 }, ++ { 0x0000c5c3, 0xf2 }, ++ { 0x0000c5c8, 0x03 }, ++ { 0x20009012, 0x00 }, ++ { 0x0000dd08, 0x17 }, ++ { 0x0000dd09, 0x0e }, ++ { 0x0000dd0a, 0x17 }, ++ { 0x0000dd0b, 0x0e }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000d471, 0x3a }, ++ { 0x0000db00, 0x00 }, ++ { 0x0000db01, 0x00 }, ++ { 0x0000db02, 0x73 }, ++ { 0x0000db03, 0x00 }, ++ { 0x0000db04, 0x00 }, ++ { 0x0000db05, 0x00 }, ++ { 0x0000db06, 0x00 }, ++ { 0x0000db07, 0x00 }, ++ { 0x0000db08, 0x7f }, ++ { 0x0000db09, 0x00 }, ++ { 0x0000db1a, 0x00 }, ++ { 0x0000db1b, 0x00 }, ++ { 0x0000db19, 0x00 }, ++}; ++ ++static const struct reg_sequence rt1321_va2_blind_write[] = { ++ { 0x0000c003, 0xf0 }, ++ { 0x0000c01b, 0xfc }, ++ { 0x0000c5c3, 0xf2 }, ++ { 0x0000c5c2, 0x00 }, ++ { 0x0000c5c1, 0x10 }, ++ { 0x0000c5c0, 0x04 }, ++ { 0x0000c5c7, 0x03 }, ++ { 0x0000c5c6, 0x10 }, ++ { 0x0000c526, 0x47 }, ++ { 0x0000c5c4, 0x12 }, ++ { 0x0000c5c5, 0x60 }, ++ { 0x0000c520, 0x10 }, ++ { 0x0000c521, 0x32 }, ++ { 0x0000c5c7, 0x00 }, ++ { 0x0000c5c8, 0x03 }, ++ { 0x0000c5d3, 0x08 }, ++ { 0x0000c5d2, 0x0a }, ++ { 0x0000c5d1, 0x49 }, ++ { 0x0000c5d0, 0x0f }, ++ { 0x0000c580, 0x10 }, ++ { 0x0000c581, 0x32 }, ++ { 0x0000c582, 0x01 }, ++ { 0x0000c682, 0x60 }, ++ { 0x0000c019, 0x10 }, ++ { 0x0000c5f0, 0x01 }, ++ { 0x0000c5f7, 0x22 }, ++ { 0x0000c5f6, 0x22 }, ++ { 0x0000c057, 0x51 }, ++ { 0x0000c054, 0x55 }, ++ { 0x0000c053, 0x55 }, ++ { 0x0000c052, 0x55 }, ++ { 0x0000c051, 0x01 }, ++ { 0x0000c050, 0x15 }, ++ { 0x0000c060, 0x99 }, ++ { 0x0000c030, 0x55 }, ++ { 0x0000c061, 0x55 }, ++ { 0x0000c063, 0x55 }, ++ { 0x0000c065, 0xa5 }, ++ { 0x0000c06b, 0x0a }, ++ { 0x0000ca05, 0xd6 }, ++ { 0x0000ca06, 0x11 }, ++ { 0x0000ca07, 0x1e }, ++ { 0x0000ca25, 0xd6 }, ++ { 0x0000ca26, 0x11 }, ++ { 0x0000ca27, 0x1e }, ++ { 0x0000cd00, 0x05 }, ++ { 0x0000cd81, 0x49 }, ++ { 0x0000cd82, 0x49 }, ++ { 0x0000c604, 0x40 }, ++ { 0x0000c609, 0x40 }, ++ { 0x0000c046, 0xf7 }, ++ { 0x0000c045, 0xff }, ++ { 0x0000c044, 0xff }, ++ { 0x0000c043, 0xff }, ++ { 0x0000c042, 0xff }, ++ { 0x0000c041, 0xff }, ++ { 0x0000c040, 0xff }, ++ { 0x0000c049, 0xff }, ++ { 0x0000c028, 0x3f }, ++ { 0x0000c020, 0x3f }, ++ { 0x0000c032, 0x13 }, ++ { 0x0000c033, 0x01 }, ++ { 0x0000cc10, 0x01 }, ++ { 0x0000dc20, 0x03 }, ++ { 0x0000de03, 0x05 }, ++ { 0x0000dc00, 0x00 }, ++ { 0x0000c700, 0xf0 }, ++ { 0x0000c701, 0x13 }, ++ { 0x0000c900, 0xc3 }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000c086, 0x02 }, ++ { 0x0000c085, 0x7f }, ++ { 0x0000c084, 0x00 }, ++ { 0x0000c081, 0xff }, ++ { 0x0000f084, 0x0f }, ++ { 0x0000f083, 0xff }, ++ { 0x0000f082, 0xff }, ++ { 0x0000f081, 0xff }, ++ { 0x0000f080, 0xff }, ++ { 0x20003003, 0x3f }, ++ { 0x20005818, 0x81 }, ++ { 0x20009018, 0x81 }, ++ { 0x2000301c, 0x81 }, ++ { 0x0000c003, 0xc0 }, ++ { 0x0000c047, 0x80 }, ++ { 0x0000d541, 0x80 }, ++ { 0x0000d487, 0x0b }, ++ { 0x0000d487, 0x3b }, ++ { 0x0000d486, 0xc3 }, ++ { 0x0000d470, 0x89 }, ++ { 0x0000d471, 0x3a }, ++ { 0x0000d472, 0x3d }, ++ { 0x0000d474, 0x11 }, ++ { 0x0000d475, 0x32 }, ++ { 0x0000d476, 0x64 }, ++ { 0x0000d477, 0x10 }, ++ { 0x0000d478, 0xff }, ++ { 0x0000d479, 0x20 }, ++ { 0x0000d47a, 0x10 }, ++ { 0x10008000, 0x67 }, ++ { 0x10008001, 0x80 }, ++ { 0x10008002, 0x00 }, ++ { 0x10008003, 0x00 }, ++ { 0x1000cd56, 0x00 }, ++ { 0x0000d486, 0x43 }, ++ { 0x3fc2dfc3, 0x00 }, ++ { 0x3fc2dfc2, 0x00 }, ++ { 0x3fc2dfc1, 0x00 }, ++ { 0x3fc2dfc0, 0x03 }, ++ { 0x3fc2dfc7, 0x00 }, ++ { 0x3fc2dfc6, 0x00 }, ++ { 0x3fc2dfc5, 0x00 }, ++ { 0x3fc2dfc4, 0x01 }, ++ { 0x3fc2dfa3, 0x00 }, ++ { 0x3fc2dfa2, 0x00 }, ++ { 0x3fc2dfa1, 0x00 }, ++ { 0x3fc2dfa0, 0x00 }, ++ { 0x3fc2df80, 0x10 }, ++ { 0x3fc2df81, 0x20 }, ++ { 0x3fc2df82, 0x00 }, ++ { 0x3fc2df83, 0x00 }, ++ { 0x3fc2df84, 0x50 }, ++ { 0x3fc2df85, 0x19 }, ++ { 0x3fc2df86, 0x00 }, ++ { 0x3fc2df87, 0x00 }, ++ { 0x3fc2df88, 0x52 }, ++ { 0x3fc2df89, 0x23 }, ++ { 0x3fc2df8a, 0x00 }, ++ { 0x3fc2df8b, 0x00 }, ++ { 0x3fc2df8c, 0xe0 }, ++ { 0x3fc2df8d, 0x2e }, ++ { 0x3fc2df8e, 0x00 }, ++ { 0x3fc2df8f, 0x00 }, ++ { 0x3fc2df90, 0xe0 }, ++ { 0x3fc2df91, 0x2e }, ++ { 0x3fc2df92, 0x00 }, ++ { 0x3fc2df93, 0x00 }, ++ { 0x3fc2df94, 0x01 }, ++ { 0x3fc2df95, 0x08 }, ++ { 0x3fc2df96, 0x00 }, ++ { 0x3fc2df97, 0x00 }, ++ { 0x3fc2df40, 0x80 }, ++ { 0x3fc2df41, 0xbb }, ++ { 0x3fc2df42, 0x00 }, ++ { 0x3fc2df43, 0x00 }, ++ { 0x3fc2df44, 0xc0 }, ++ { 0x3fc2df45, 0x99 }, ++ { 0x3fc2df46, 0x01 }, ++ { 0x3fc2df47, 0x00 }, ++ { 0x3fc2df48, 0x00 }, ++ { 0x3fc2df49, 0x00 }, ++ { 0x3fc2df4a, 0x00 }, ++ { 0x3fc2df4b, 0x00 }, ++ { 0x3fc2df4c, 0x00 }, ++ { 0x3fc2df4d, 0x00 }, ++ { 0x3fc2df4e, 0x00 }, ++ { 0x3fc2df4f, 0x00 }, ++ { 0x3fc2df50, 0x01 }, ++ { 0x3fc2df51, 0x00 }, ++ { 0x3fc2df52, 0x00 }, ++ { 0x3fc2df53, 0x00 }, ++ { 0x3fc2df54, 0x01 }, ++ { 0x3fc2df55, 0x00 }, ++ { 0x3fc2df56, 0x00 }, ++ { 0x3fc2df57, 0x00 }, ++ { 0x3fc2df58, 0x00 }, ++ { 0x3fc2df59, 0x00 }, ++ { 0x3fc2df5a, 0x00 }, ++ { 0x3fc2df5b, 0x00 }, ++ { 0x3fc2df5c, 0x01 }, ++ { 0x3fc2df5d, 0x00 }, ++ { 0x3fc2df5e, 0x00 }, ++ { 0x3fc2df5f, 0x00 }, ++ { 0x3fc2df60, 0x00 }, ++ { 0x3fc2df61, 0x00 }, ++ { 0x3fc2df62, 0x00 }, ++ { 0x3fc2df63, 0x00 }, ++ { 0x3fc2df64, 0x00 }, ++ { 0x3fc2df65, 0x00 }, ++ { 0x3fc2df66, 0x00 }, ++ { 0x3fc2df67, 0x10 }, ++ { 0x3fc2df68, 0x01 }, ++ { 0x3fc2df69, 0x00 }, ++ { 0x3fc2df6a, 0x00 }, ++ { 0x3fc2df6b, 0x00 }, ++ { 0x3fc2df6c, 0x01 }, ++ { 0x3fc2df6d, 0x00 }, ++ { 0x3fc2df6e, 0x00 }, ++ { 0x3fc2df6f, 0x00 }, ++ { 0x3fc2df70, 0x04 }, ++ { 0x3fc2df71, 0x00 }, ++ { 0x3fc2df72, 0x00 }, ++ { 0x3fc2df73, 0x00 }, ++ { 0x3fc2df74, 0x01 }, ++ { 0x3fc2df75, 0x00 }, ++ { 0x3fc2df76, 0x00 }, ++ { 0x3fc2df77, 0x00 }, ++ { 0x1000db00, 0x04 }, ++ { 0x1000db01, 0x00 }, ++ { 0x1000db02, 0x10 }, ++ { 0x1000db03, 0x00 }, ++ { 0x1000db04, 0x00 }, ++ { 0x1000db05, 0x45 }, ++ { 0x1000db06, 0x0d }, ++ { 0x1000db07, 0x01 }, ++ { 0x1000db08, 0x00 }, ++ { 0x1000db09, 0x00 }, ++ { 0x1000db0a, 0xbf }, ++ { 0x1000db0b, 0x0b }, ++ { 0x1000db0c, 0x11 }, ++ { 0x1000db0d, 0x00 }, ++ { 0x1000db0e, 0x00 }, ++ { 0x1000db0f, 0x00 }, ++ { 0x1000db10, 0x2c }, ++ { 0x1000db11, 0xfa }, ++ { 0x1000db12, 0x00 }, ++ { 0x1000db13, 0x00 }, ++ { 0x1000db14, 0x09 }, ++ { 0x0000d540, 0x21 }, ++ { 0x0000d714, 0x17 }, ++ { 0x0000dd0b, 0x0d }, ++ { 0x0000dd0a, 0xff }, ++ { 0x0000dd09, 0x0d }, ++ { 0x0000dd08, 0xff }, ++ { 0x0000c5fb, 0x12 }, ++ { 0x0000c570, 0x08 }, ++}; ++ + static const struct reg_default rt1320_reg_defaults[] = { + { SDW_SDCA_CTL(FUNC_NUM_MIC, RT1320_SDCA_ENT_PDE11, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x03 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC, RT1320_SDCA_ENT_FU113, RT1320_SDCA_CTL_FU_MUTE, CH_01), 0x01 }, +@@ -440,6 +836,7 @@ static const struct reg_default rt1320_mbq_defaults[] = { + static bool rt1320_readable_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0xc000 ... 0xc086: + case 0xc400 ... 0xc409: + case 0xc480 ... 0xc48f: +@@ -497,10 +894,15 @@ static bool rt1320_readable_register(struct device *dev, unsigned int reg) + case RT1321_PATCH_MAIN_VER ... RT1321_PATCH_BETA_VER: + case 0x1000f008: + case 0x1000f021: +- case 0x2000300f: ++ case 0x20003000 ... 0x2000300f: + case 0x2000301c: +- case 0x2000900f: ++ case 0x20003040 ... 0x20003059: ++ case 0x20005800 ... 0x2000580f: ++ case 0x20005818: ++ case 0x20005840 ... 0x20005859: ++ case 0x20009000 ... 0x2000900f: + case 0x20009018: ++ case 0x20009040 ... 0x20009059: + case 0x3fc000c0 ... 0x3fc2dfc8: + case 0x3fe00000 ... 0x3fe36fff: + /* 0x40801508/0x40801809/0x4080180a/0x40801909/0x4080190a */ +@@ -545,6 +947,7 @@ static bool rt1320_readable_register(struct device *dev, unsigned int reg) + static bool rt1320_volatile_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0xc000: + case 0xc003: + case 0xc081: +@@ -596,15 +999,20 @@ static bool rt1320_volatile_register(struct device *dev, unsigned int reg) + case 0x1000c000 ... 0x1000dfff: + case 0x1000f008: + case 0x1000f021: +- case 0x2000300f: ++ case 0x2000300e ... 0x2000300f: + case 0x2000301c: +- case 0x2000900f: ++ case 0x20003040 ... 0x20003059: ++ case 0x2000580e ... 0x2000580f: ++ case 0x20005818: ++ case 0x20005840 ... 0x20005859: ++ case 0x2000900e ... 0x2000900f: + case 0x20009018: ++ case 0x20009040 ... 0x20009059: + case 0x3fc2ab80 ... 0x3fc2ac4c: + case 0x3fc2b780: + case 0x3fc2bf80 ... 0x3fc2bf83: + case 0x3fc2bfc0 ... 0x3fc2bfc8: +- case 0x3fc2d300 ... 0x3fc2d354: ++ case 0x3fc2d300 ... 0x3fc2d3cc: + case 0x3fc2dfc0 ... 0x3fc2dfc8: + case 0x3fe2e000 ... 0x3fe2e003: + case SDW_SDCA_CTL(FUNC_NUM_MIC, RT1320_SDCA_ENT_PDE11, RT1320_SDCA_CTL_ACTUAL_POWER_STATE, 0): +@@ -892,6 +1300,42 @@ static int rt1320_check_fw_ready(struct rt1320_sdw_priv *rt1320) + return 0; + } + ++static int rt1320_dspfw_status(struct rt1320_sdw_priv *rt1320) ++{ ++ struct device *dev = &rt1320->sdw_slave->dev; ++ unsigned int fw_status_addr, fw_ready; ++ unsigned int dspfw_run; ++ ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ fw_status_addr = RT1320_DSPFW_STATUS_ADDR; ++ break; ++ case RT1321_DEV_ID: ++ fw_status_addr = RT1321_DSPFW_STATUS_ADDR; ++ break; ++ default: ++ dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); ++ return -EINVAL; ++ } ++ ++ regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); ++ fw_ready &= 0x1; ++ ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ regmap_read(rt1320->regmap, 0xf01e, &dspfw_run); ++ dspfw_run &= 0x1; ++ fw_ready = (!dspfw_run && fw_ready); ++ } ++ ++ if (fw_ready) { ++ dev_dbg(dev, "%s, DSP FW was already\n", __func__); ++ return 1; ++ } ++ ++ dev_dbg(dev, "%s, DSP FW is NOT ready. Please load DSP FW first\n", __func__); ++ return 0; ++} ++ + static int rt1320_check_power_state_ready(struct rt1320_sdw_priv *rt1320, enum rt1320_power_state ps) + { + struct device *dev = &rt1320->sdw_slave->dev; +@@ -982,6 +1426,9 @@ static int rt1320_fw_param_protocol(struct rt1320_sdw_priv *rt1320, enum rt1320_ + if (!tempbuf) + return -ENOMEM; + ++ if (rt1320->dev_id == RT1321_DEV_ID && rt1320->version_id == RT1321_VA2) ++ paramid += 0x0bff0000; ++ + paramhr.moudleid = 1; + paramhr.commandtype = cmdid; + /* 8 is "sizeof(paramid) + sizeof(paramlength)" */ +@@ -1120,45 +1567,73 @@ static void rt1320_calc_r0(struct rt1320_sdw_priv *rt1320) + l_calir0, l_calir0_lo, r_calir0, r_calir0_lo); + } + ++static int rt1320_pilot_tone_output(struct rt1320_sdw_priv *rt1320) ++{ ++ struct device *dev = &rt1320->sdw_slave->dev; ++ int l_targetpostgain, r_targetpostgain; ++ unsigned long long factor = (1 << 12); ++ int l_pilotgain[9], r_pilotgain[9]; ++ const int postgain_step = 234; ++ int targetGain; ++ ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ targetGain = -320000; ++ break; ++ case RT1321_DEV_ID: ++ targetGain = -420000; ++ if (rt1320->version_id == RT1321_VA0) ++ targetGain = -320000; ++ break; ++ default: ++ dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); ++ return -EINVAL; ++ } ++ ++ rt1320_fw_param_protocol(rt1320, RT1320_GET_PARAM, 70, &l_pilotgain[0], sizeof(l_pilotgain)); ++ rt1320_fw_param_protocol(rt1320, RT1320_GET_PARAM, 71, &r_pilotgain[0], sizeof(r_pilotgain)); ++ dev_dbg(dev, "%s, LR pilotgain %d, %d\n", __func__, l_pilotgain[2], r_pilotgain[2]); ++ ++ /* calculate pilot tone gain */ ++ l_pilotgain[2] = (l_pilotgain[2] * 10000) / factor; ++ r_pilotgain[2] = (r_pilotgain[2] * 10000) / factor; ++ ++ /* calculate post gain to meet target gain */ ++ l_targetpostgain = abs(targetGain - l_pilotgain[2]) / postgain_step; ++ r_targetpostgain = abs(targetGain - r_pilotgain[2]) / postgain_step; ++ l_targetpostgain = 0xfff - l_targetpostgain; ++ r_targetpostgain = 0xfff - r_targetpostgain; ++ dev_dbg(dev, "%s, LR targetpostgain=0x%x, 0x%x\n", __func__, l_targetpostgain, r_targetpostgain); ++ ++ regmap_write(rt1320->regmap, 0xdd0b, (l_targetpostgain & 0xf00) >> 8); ++ regmap_write(rt1320->regmap, 0xdd0a, l_targetpostgain & 0xff); ++ regmap_write(rt1320->regmap, 0xdd09, (r_targetpostgain & 0xf00) >> 8); ++ regmap_write(rt1320->regmap, 0xdd08, r_targetpostgain & 0xff); ++ ++ return 0; ++} ++ + static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + { + struct device *dev = &rt1320->sdw_slave->dev; + struct rt1320_datafixpoint audfixpoint[2]; + unsigned int reg_c5fb, reg_c570, reg_cd00; +- unsigned int vol_reg[4], fw_ready; ++ unsigned int vol_reg[4]; + unsigned long long l_meanr0, r_meanr0; +- unsigned int fw_status_addr; + int l_re[5], r_re[5]; + int ret, tmp; + unsigned long long factor = (1 << 27); + unsigned short l_advancegain, r_advancegain; + unsigned int delay_s = 7; /* delay seconds for the calibration */ ++ int dspfw_status; + + if (!rt1320->component) + return; + +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return; +- } +- +- /* set volume 0dB */ + regmap_read(rt1320->regmap, 0xdd0b, &vol_reg[3]); + regmap_read(rt1320->regmap, 0xdd0a, &vol_reg[2]); + regmap_read(rt1320->regmap, 0xdd09, &vol_reg[1]); + regmap_read(rt1320->regmap, 0xdd08, &vol_reg[0]); +- regmap_write(rt1320->regmap, 0xdd0b, 0x0f); +- regmap_write(rt1320->regmap, 0xdd0a, 0xff); +- regmap_write(rt1320->regmap, 0xdd09, 0x0f); +- regmap_write(rt1320->regmap, 0xdd08, 0xff); +- + regmap_read(rt1320->regmap, 0xc5fb, ®_c5fb); + regmap_read(rt1320->regmap, 0xc570, ®_c570); + regmap_read(rt1320->regmap, 0xcd00, ®_cd00); +@@ -1171,9 +1646,8 @@ static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + goto _finish_; + } + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (!fw_ready) { ++ dspfw_status = rt1320_dspfw_status(rt1320); ++ if (dspfw_status <= 0) { + dev_dbg(dev, "%s, DSP FW is NOT ready. Please load DSP FW first\n", __func__); + goto _finish_; + } +@@ -1184,8 +1658,19 @@ static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + goto _finish_; + } + +- if (rt1320->dev_id == RT1320_DEV_ID) +- regmap_write(rt1320->regmap, 0xc5fb, 0x00); ++ /* fine tune pilot tone output */ ++ ret = rt1320_pilot_tone_output(rt1320); ++ if (ret < 0) { ++ dev_dbg(dev, "%s, Failed to tune pilot tone output\n", __func__); ++ goto _finish_; ++ } ++ ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x00); ++ regmap_write(rt1320->regmap, 0xc5c4, 0x12); ++ } ++ ++ regmap_write(rt1320->regmap, 0xc5fb, 0x00); + regmap_write(rt1320->regmap, 0xc570, 0x0b); + regmap_write(rt1320->regmap, 0xcd00, 0xc5); + +@@ -1246,6 +1731,11 @@ static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x03); + rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x03); + ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x80); ++ regmap_write(rt1320->regmap, 0xc5c4, 0x10); ++ } ++ + /* advance gain will be set when R0 load, not here */ + regmap_write(rt1320->regmap, 0xdd0b, vol_reg[3]); + regmap_write(rt1320->regmap, 0xdd0a, vol_reg[2]); +@@ -1296,7 +1786,7 @@ static int rt1320_r0_cali_put(struct snd_kcontrol *kcontrol, + static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) + { + struct sdw_slave *slave = rt1320->sdw_slave; +- const struct firmware *patch; ++ const struct firmware *patch __free(firmware) = NULL; + const char *filename; + unsigned int addr, val, min_addr, max_addr; + const unsigned char *ptr; +@@ -1312,6 +1802,9 @@ static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) + max_addr = 0x10007fff; + break; + case RT1321_DEV_ID: ++ if (rt1320->version_id == RT1321_VA2) ++ return; ++ + filename = RT1321_VA_MCU_PATCH; + min_addr = 0x10008000; + max_addr = 0x10008fff; +@@ -1347,17 +1840,15 @@ static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) + + if (addr > max_addr || addr < min_addr) { + dev_err(&slave->dev, "%s: the address 0x%x is wrong", __func__, addr); +- goto _exit_; ++ return; + } + if (val > 0xff) { + dev_err(&slave->dev, "%s: the value 0x%x is wrong", __func__, val); +- goto _exit_; ++ return; + } + regmap_write(rt1320->regmap, addr, val); + } + } +-_exit_: +- release_firmware(patch); + } + } + +@@ -1382,30 +1873,17 @@ static void rt1320_vab_preset(struct rt1320_sdw_priv *rt1320) + static void rt1320_t0_load(struct rt1320_sdw_priv *rt1320, unsigned int l_t0, unsigned int r_t0) + { + struct device *dev = &rt1320->sdw_slave->dev; +- unsigned int factor = (1 << 22), fw_ready; ++ unsigned int factor = (1 << 22); + int l_t0_data[38], r_t0_data[38]; +- unsigned int fw_status_addr; +- +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return; +- } ++ int dspfw_status; + + regmap_write(rt1320->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, + RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x00); + rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x00); + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (!fw_ready) { ++ dspfw_status = rt1320_dspfw_status(rt1320); ++ if (dspfw_status <= 0) { + dev_warn(dev, "%s, DSP FW is NOT ready\n", __func__); + goto _exit_; + } +@@ -1444,7 +1922,7 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + struct device *dev = &rt1320->sdw_slave->dev; + static const char func_tag[] = "FUNC"; + static const char xu_tag[] = "XU"; +- const struct firmware *rae_fw = NULL; ++ const struct firmware *rae_fw __free(firmware) = NULL; + unsigned int fw_offset; + unsigned char *fw_data; + unsigned char *param_data; +@@ -1483,22 +1961,46 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + request_firmware(&rae_fw, rae_filename, dev); + if (rae_fw) { + +- /* RAE CRC clear */ +- regmap_write(rt1320->regmap, 0xe80b, 0x0f); +- +- /* RAE stop & CRC disable */ +- regmap_update_bits(rt1320->regmap, 0xe803, 0xbc, 0x00); +- +- while (--retry) { +- regmap_read(rt1320->regmap, 0xe83f, &value); +- if (value & 0x40) +- break; +- usleep_range(1000, 1100); +- } +- if (!retry && !(value & 0x40)) { +- dev_err(dev, "%s: RAE is not ready to load\n", __func__); +- release_firmware(rae_fw); +- return -ETIMEDOUT; ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ /* RAE CRC clear */ ++ regmap_write(rt1320->regmap, 0xe80b, 0x0f); ++ /* RAE stop & CRC disable */ ++ regmap_update_bits(rt1320->regmap, 0xe803, 0xbc, 0x00); ++ while (--retry) { ++ regmap_read(rt1320->regmap, 0xe83f, &value); ++ if (value & 0x40) ++ break; ++ usleep_range(1000, 1100); ++ } ++ if (!retry && !(value & 0x40)) { ++ dev_err(dev, "%s: RAE is not ready to load\n", __func__); ++ return -ETIMEDOUT; ++ } ++ break; ++ case RT1321_DEV_ID: ++ /* RAE CRC clear */ ++ regmap_write(rt1320->regmap, 0x2000300e, 0xc0); ++ regmap_write(rt1320->regmap, 0x2000300f, 0x0f); ++ /* RAE stop & Phase sync & CRC disable */ ++ regmap_update_bits(rt1320->regmap, 0x20003003, 0xfe, 0x00); ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x01, 0x00); ++ /* check whether write state is ready */ ++ while (--retry) { ++ regmap_read(rt1320->regmap, 0x20003043, &value); ++ if (value & 0x40) ++ break; ++ usleep_range(1000, 1100); ++ } ++ if (!retry && !(value & 0x40)) { ++ dev_err(dev, "%s: RAE is not ready to load\n", __func__); ++ return -ETIMEDOUT; ++ } ++ break; ++ default: ++ dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); ++ return -EINVAL; + } + + dev_dbg(dev, "%s, rae_fw size=0x%zx\n", __func__, rae_fw->size); +@@ -1551,7 +2053,6 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + } + + regcache_cache_bypass(rt1320->regmap, false); +- release_firmware(rae_fw); + + } else { + dev_err(dev, "%s: Failed to load %s firmware\n", __func__, rae_filename); +@@ -1559,18 +2060,34 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + goto _exit_; + } + +- /* RAE CRC enable */ +- regmap_update_bits(rt1320->regmap, 0xe803, 0x0c, 0x0c); +- +- /* RAE update */ +- regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x00); +- regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x80); +- +- /* RAE run */ +- regmap_update_bits(rt1320->regmap, 0xe803, 0x80, 0x80); +- +- regmap_read(rt1320->regmap, 0xe80b, &value); +- dev_dbg(dev, "%s: CAE run => 0xe80b reg = 0x%x\n", __func__, value); ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ /* RAE CRC enable */ ++ regmap_update_bits(rt1320->regmap, 0xe803, 0x0c, 0x0c); ++ /* RAE update */ ++ regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x80); ++ /* RAE run */ ++ regmap_update_bits(rt1320->regmap, 0xe803, 0x80, 0x80); ++ regmap_read(rt1320->regmap, 0xe80b, &value); ++ dev_dbg(dev, "%s: CAE run => 0xe80b reg = 0x%x\n", __func__, value); ++ break; ++ case RT1321_DEV_ID: ++ /* RAE CRC enable */ ++ regmap_update_bits(rt1320->regmap, 0x20003003, 0x30, 0x30); ++ /* RAE update */ ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x80, 0x80); ++ regmap_update_bits(rt1320->regmap, 0x20009018, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x20009018, 0x80, 0x80); ++ regmap_update_bits(rt1320->regmap, 0x20005818, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x20005818, 0x80, 0x80); ++ /* RAE run */ ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x01, 0x01); ++ /* Phase sync eanble */ ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x80); ++ break; ++ } + + rt1320->rae_update_done = true; + +@@ -1598,32 +2115,21 @@ struct rt1320_dspfwheader { + + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(rt1320->component); + struct device *dev = &rt1320->sdw_slave->dev; +- unsigned int val, i, fw_offset, fw_ready; +- unsigned int fw_status_addr; ++ unsigned int val, i, fw_offset; + struct rt1320_dspfwheader *fwheader; + struct rt1320_imageinfo *ptr_img; + struct sdw_bpt_section sec[10]; +- const struct firmware *fw = NULL; ++ const struct firmware *fw __free(firmware) = NULL; + unsigned char *fw_data; + bool dev_fw_match = false; + static const char hdr_sig[] = "AFX"; + unsigned int hdr_size = 0; + const char *dmi_vendor, *dmi_product, *dmi_sku; + int len_vendor, len_product, len_sku; ++ unsigned char boot_mode = 0; /* 0: from RAM; 1: from ROM */ ++ unsigned char has_0x3fc00000 = 0; + char filename[512]; + +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return; +- } +- + dmi_vendor = dmi_get_system_info(DMI_SYS_VENDOR); + dmi_product = dmi_get_system_info(DMI_PRODUCT_NAME); + dmi_sku = dmi_get_system_info(DMI_PRODUCT_SKU); +@@ -1653,17 +2159,12 @@ struct rt1320_dspfwheader { + RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x00); + rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x00); + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (fw_ready) { ++ if (rt1320_dspfw_status(rt1320)) { + dev_dbg(dev, "%s, DSP FW was already\n", __func__); + rt1320->fw_load_done = true; + goto _exit_; + } + +- /* change to IRAM */ +- regmap_update_bits(rt1320->regmap, 0xf01e, 0x80, 0x00); +- + request_firmware(&fw, filename, dev); + if (fw) { + fwheader = (struct rt1320_dspfwheader *)fw->data; +@@ -1672,7 +2173,6 @@ struct rt1320_dspfwheader { + + if (fwheader->sync != 0x0a1c5679) { + dev_err(dev, "%s: FW sync error\n", __func__); +- release_firmware(fw); + goto _exit_; + } + +@@ -1709,9 +2209,11 @@ struct rt1320_dspfwheader { + dev_fw_match = true; + break; + case RT1321_DEV_ID: +- if (ptr_img->addr == 0x3fc00000) +- if (fw_data[9] == '1') ++ if (ptr_img->addr == 0x3fc00000) { ++ if (fw_data[7] == '1') + dev_fw_match = true; ++ has_0x3fc00000 = 1; ++ } + break; + default: + dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +@@ -1721,6 +2223,22 @@ struct rt1320_dspfwheader { + fw_offset += ptr_img->size; + } + ++ if (rt1320->dev_id == RT1321_DEV_ID && rt1320->version_id == RT1321_VA2) { ++ /* ++ * For 1321 VA2, if the FW doesn't include the section for address 0x3fc00000, ++ * it means the FW will boot from ROM and force dev_fw_match to true to download FW by BRA. ++ */ ++ if (!has_0x3fc00000) { ++ boot_mode = 1; ++ dev_fw_match = true; ++ } ++ dev_dbg(dev, "%s: Boot from %s for VA2\n", __func__, (boot_mode ? "ROM" : "RAM")); ++ } ++ ++ /* change to IRAM */ ++ if (!boot_mode) ++ regmap_update_bits(rt1320->regmap, 0xf01e, 0x80, 0x00); ++ + if (dev_fw_match) { + dev_dbg(dev, "%s, starting BRA downloading FW..\n", __func__); + rt1320->bra_msg.dev_num = rt1320->sdw_slave->dev_num; +@@ -1732,7 +2250,6 @@ struct rt1320_dspfwheader { + } + + regcache_cache_bypass(rt1320->regmap, false); +- release_firmware(fw); + + if (!dev_fw_match) { + dev_err(dev, "%s: FW file doesn't match to device\n", __func__); +@@ -1743,24 +2260,39 @@ struct rt1320_dspfwheader { + goto _exit_; + } + +- /* run RAM code */ +- regmap_read(rt1320->regmap, 0x3fc2bfc0, &val); +- val |= 0x8; +- regmap_write(rt1320->regmap, 0x3fc2bfc0, val); +- +- /* clear frame counter */ + switch (rt1320->dev_id) { + case RT1320_DEV_ID: ++ /* run RAM code */ ++ regmap_read(rt1320->regmap, 0x3fc2bfc0, &val); ++ val |= 0x8; ++ regmap_write(rt1320->regmap, 0x3fc2bfc0, val); ++ /* clear frame counter */ + regmap_write(rt1320->regmap, 0x3fc2bfcb, 0x00); + regmap_write(rt1320->regmap, 0x3fc2bfca, 0x00); + regmap_write(rt1320->regmap, 0x3fc2bfc9, 0x00); + regmap_write(rt1320->regmap, 0x3fc2bfc8, 0x00); + break; + case RT1321_DEV_ID: ++ if (!boot_mode) { ++ /* run RAM code */ ++ regmap_read(rt1320->regmap, 0x3fc2dfc0, &val); ++ val |= 0x8; ++ regmap_write(rt1320->regmap, 0x3fc2dfc0, val); ++ } ++ /* clear frame counter */ + regmap_write(rt1320->regmap, 0x3fc2dfcb, 0x00); + regmap_write(rt1320->regmap, 0x3fc2dfca, 0x00); + regmap_write(rt1320->regmap, 0x3fc2dfc9, 0x00); + regmap_write(rt1320->regmap, 0x3fc2dfc8, 0x00); ++ /* enable handshake */ ++ regmap_write(rt1320->regmap, 0x3fc2dfc4, 0x00); ++ regmap_write(rt1320->regmap, 0xd470, 0xad); ++ /* minimum phase settings */ ++ regmap_write(rt1320->regmap, 0xc5c4, 0x10); ++ regmap_write(rt1320->regmap, 0x20003003, 0x31); ++ regmap_update_bits(rt1320->regmap, 0x20003002, 0x40, 0x00); ++ regmap_write(rt1320->regmap, 0xc5b3, 0x01); ++ regmap_write(rt1320->regmap, 0xc052, 0x11); + break; + } + +@@ -1843,14 +2375,35 @@ static void rt1320_vc_preset(struct rt1320_sdw_priv *rt1320) + + static void rt1321_preset(struct rt1320_sdw_priv *rt1320) + { ++ const struct reg_sequence *blindwrite; + unsigned int i, reg, val, delay; ++ unsigned int array_size; + +- for (i = 0; i < ARRAY_SIZE(rt1321_blind_write); i++) { +- reg = rt1321_blind_write[i].reg; +- val = rt1321_blind_write[i].def; +- delay = rt1321_blind_write[i].delay_us; ++ switch (rt1320->version_id) { ++ case RT1321_VA0: ++ blindwrite = rt1321_blind_write; ++ array_size = ARRAY_SIZE(rt1321_blind_write); ++ break; ++ case RT1321_VA1: ++ blindwrite = rt1321_va1_blind_write; ++ array_size = ARRAY_SIZE(rt1321_va1_blind_write); ++ break; ++ case RT1321_VA2: ++ blindwrite = rt1321_va2_blind_write; ++ array_size = ARRAY_SIZE(rt1321_va2_blind_write); ++ break; ++ default: ++ dev_err(&rt1320->sdw_slave->dev, "%s: Unknown version ID %d\n", ++ __func__, rt1320->version_id); ++ return; ++ } + +- if (reg == 0x3fc2dfc3) ++ for (i = 0; i < array_size; i++) { ++ reg = blindwrite[i].reg; ++ val = blindwrite[i].def; ++ delay = blindwrite[i].delay_us; ++ ++ if (reg == 0x1000cd56) + rt1320_load_mcu_patch(rt1320); + + regmap_write(rt1320->regmap, reg, val); +@@ -1892,6 +2445,24 @@ static int rt1320_io_init(struct device *dev, struct sdw_slave *slave) + regmap_read(rt1320->regmap, RT1320_DEV_ID_0, &val); + regmap_read(rt1320->regmap, RT1320_DEV_ID_1, &tmp); + rt1320->dev_id = (val << 8) | tmp; ++ ++ /* This is a workaround that reads the value twice to obtain the correct result. */ ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_0, &val); ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_1, &tmp); ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_0, &val); ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_1, &tmp); ++ val = (val << 8) | tmp; ++ ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ if (rt1320->version_id == 0x01) ++ rt1320->version_id = RT1321_VA2; ++ else if (val == RT1321_DEV_HV_VA0_ID) ++ rt1320->version_id = RT1321_VA0; ++ else if (val == RT1321_DEV_HV_VA1_ID) ++ rt1320->version_id = RT1321_VA1; ++ else ++ dev_err(dev, "%s: Unknown version ID 0x%x for RT1321\n", __func__, rt1320->version_id); ++ } + } + + regmap_read(rt1320->regmap, +@@ -2326,25 +2897,12 @@ static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -1725, 75, 0); + static int rt1320_r0_load(struct rt1320_sdw_priv *rt1320) + { + struct device *dev = regmap_get_device(rt1320->regmap); +- unsigned int fw_status_addr; +- unsigned int fw_ready; ++ int dspfw_status; + int ret = 0; + + if (!rt1320->r0_l_reg || !rt1320->r0_r_reg) + return -EINVAL; + +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return -EINVAL; +- } +- + regmap_write(rt1320->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x00); + ret = rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x00); +@@ -2353,9 +2911,8 @@ static int rt1320_r0_load(struct rt1320_sdw_priv *rt1320) + goto _timeout_; + } + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (!fw_ready) { ++ dspfw_status = rt1320_dspfw_status(rt1320); ++ if (dspfw_status <= 0) { + dev_dbg(dev, "%s, DSP FW is NOT ready\n", __func__); + goto _timeout_; + } +@@ -2458,7 +3015,7 @@ static int rt1320_dspfw_load_put(struct snd_kcontrol *kcontrol, + if (!rt1320->hw_init) + return 0; + +- ret = pm_runtime_resume(component->dev); ++ ret = pm_runtime_resume_and_get(component->dev); + if (ret < 0 && ret != -EACCES) + return ret; + +@@ -2469,6 +3026,8 @@ static int rt1320_dspfw_load_put(struct snd_kcontrol *kcontrol, + if (!ucontrol->value.integer.value[0]) + rt1320->fw_load_done = false; + ++ pm_runtime_mark_last_busy(component->dev); ++ pm_runtime_put_autosuspend(component->dev); + return 0; + } + +@@ -2819,6 +3378,37 @@ static int rt1320_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + return 0; + } + ++static int rt1320_bus_config(struct sdw_slave *slave, ++ struct sdw_bus_params *params) ++{ ++ struct rt1320_sdw_priv *rt1320 = dev_get_drvdata(&slave->dev); ++ unsigned int clk_base; ++ ++ regmap_read(rt1320->regmap, 0x004d, &clk_base); ++ dev_dbg(&rt1320->sdw_slave->dev, "%s clk_base=%x", __func__, clk_base); ++ ++ switch (clk_base) { ++ case RT1320_CLK_FREQ_19_2_MHZ: ++ case RT1320_CLK_FREQ_24MHZ: ++ regmap_write(rt1320->regmap, 0xc600, 0x04); ++ regmap_write(rt1320->regmap, 0xc601, 0x83); ++ regmap_write(rt1320->regmap, 0xc602, 0x1f); ++ regmap_write(rt1320->regmap, 0xc603, 0x40); ++ break; ++ case RT1320_CLK_FREQ_24_576MHZ: ++ case RT1320_CLK_FREQ_22_5792MHZ: ++ regmap_write(rt1320->regmap, 0xc600, 0x07); ++ regmap_write(rt1320->regmap, 0xc601, 0x80); ++ regmap_write(rt1320->regmap, 0xc602, 0x0f); ++ regmap_write(rt1320->regmap, 0xc603, 0x40); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ + /* + * slave_ops: callbacks for get_clock_stop_mode, clock_stop and + * port_prep are not defined for now +@@ -2826,12 +3416,13 @@ static int rt1320_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + static const struct sdw_slave_ops rt1320_slave_ops = { + .read_prop = rt1320_read_prop, + .update_status = rt1320_update_status, ++ .bus_config = rt1320_bus_config, + }; + + static int rt1320_sdw_component_probe(struct snd_soc_component *component) + { +- int ret; + struct rt1320_sdw_priv *rt1320 = snd_soc_component_get_drvdata(component); ++ int ret; + + rt1320->component = component; + +@@ -3062,10 +3653,23 @@ static int rt1320_dev_resume(struct device *dev) + return ret; + + regcache_cache_only(rt1320->regmap, false); +- regcache_sync(rt1320->regmap); ++ ret = regcache_sync(rt1320->regmap); ++ if (ret) ++ goto err_sync; ++ + regcache_cache_only(rt1320->mbq_regmap, false); +- regcache_sync(rt1320->mbq_regmap); ++ ret = regcache_sync(rt1320->mbq_regmap); ++ if (ret) ++ goto err_sync; ++ + return 0; ++ ++err_sync: ++ regcache_cache_only(rt1320->regmap, true); ++ regcache_cache_only(rt1320->mbq_regmap, true); ++ regcache_mark_dirty(rt1320->regmap); ++ regcache_mark_dirty(rt1320->mbq_regmap); ++ return ret; + } + + static const struct dev_pm_ops rt1320_pm = { +diff --git a/sound/soc/codecs/rt1320-sdw.h b/sound/soc/codecs/rt1320-sdw.h +--- a/sound/soc/codecs/rt1320-sdw.h ++++ b/sound/soc/codecs/rt1320-sdw.h +@@ -17,12 +17,17 @@ + + #define RT1320_DEV_ID 0x6981 + #define RT1321_DEV_ID 0x7045 ++#define RT1321_DEV_HV_VA0_ID 0x6997 ++#define RT1321_DEV_HV_VA1_ID 0x7071 + + /* imp-defined registers */ + #define RT1320_DEV_VERSION_ID_1 0xc404 + #define RT1320_DEV_ID_1 0xc405 + #define RT1320_DEV_ID_0 0xc406 + ++#define RT1320_HV_DEV_ID_0 0xf622 ++#define RT1320_HV_DEV_ID_1 0xf623 ++ + #define RT1320_POWER_STATE 0xc560 + + #define RT1321_PATCH_MAIN_VER 0x1000cffe +@@ -94,6 +99,12 @@ enum rt1320_version_id { + RT1320_VC, + }; + ++enum rt1321_version_id { ++ RT1321_VA0, ++ RT1321_VA1, ++ RT1321_VA2, ++}; ++ + #define RT1320_VER_B_ID 0x07392238 + #define RT1320_VAB_MCU_PATCH "realtek/rt1320/rt1320-patch-code-vab.bin" + #define RT1320_VC_MCU_PATCH "realtek/rt1320/rt1320-patch-code-vc.bin" +@@ -121,6 +132,7 @@ struct rt1320_datafixpoint { + int invrs; + }; + ++/* FW parameter id 1300 */ + typedef struct FwPara_HwSwGain { + unsigned int SwAdvGain; + unsigned int SwBasGain; +@@ -163,6 +175,13 @@ enum rt1320_rw_type { + RT1320_PARAM_READ = 3, + }; + ++enum { ++ RT1320_CLK_FREQ_19_2_MHZ = 1, ++ RT1320_CLK_FREQ_24MHZ = 2, ++ RT1320_CLK_FREQ_24_576MHZ = 3, ++ RT1320_CLK_FREQ_22_5792MHZ = 4, ++}; ++ + struct rt1320_sdw_priv { + struct snd_soc_component *component; + struct regmap *regmap; +diff --git a/sound/soc/codecs/rt5514-spi.c b/sound/soc/codecs/rt5514-spi.c +--- a/sound/soc/codecs/rt5514-spi.c ++++ b/sound/soc/codecs/rt5514-spi.c +@@ -6,6 +6,7 @@ + * Author: Oder Chiou + */ + ++#include + #include + #include + #include +@@ -79,17 +80,17 @@ static void rt5514_spi_copy_work(struct work_struct *work) + unsigned int cur_wp, remain_data; + u8 buf[8]; + +- mutex_lock(&rt5514_dsp->dma_lock); ++ guard(mutex)(&rt5514_dsp->dma_lock); + if (!rt5514_dsp->substream) { + dev_err(rt5514_dsp->dev, "No pcm substream\n"); +- goto done; ++ return; + } + + runtime = rt5514_dsp->substream->runtime; + period_bytes = snd_pcm_lib_period_bytes(rt5514_dsp->substream); + if (!period_bytes) { + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +- goto done; ++ return; + } + + if (rt5514_dsp->buf_size % period_bytes) +@@ -111,7 +112,7 @@ static void rt5514_spi_copy_work(struct work_struct *work) + + if (remain_data < period_bytes) { + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +- goto done; ++ return; + } + } + +@@ -146,9 +147,6 @@ static void rt5514_spi_copy_work(struct work_struct *work) + snd_pcm_period_elapsed(rt5514_dsp->substream); + + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +- +-done: +- mutex_unlock(&rt5514_dsp->dma_lock); + } + + static void rt5514_schedule_copy(struct rt5514_dsp *rt5514_dsp) +@@ -216,7 +214,7 @@ static int rt5514_spi_hw_params(struct snd_soc_component *component, + snd_soc_component_get_drvdata(component); + u8 buf[8]; + +- mutex_lock(&rt5514_dsp->dma_lock); ++ guard(mutex)(&rt5514_dsp->dma_lock); + rt5514_dsp->substream = substream; + rt5514_dsp->dma_offset = 0; + +@@ -225,8 +223,6 @@ static int rt5514_spi_hw_params(struct snd_soc_component *component, + if (buf[0] & RT5514_IRQ_STATUS_BIT) + rt5514_schedule_copy(rt5514_dsp); + +- mutex_unlock(&rt5514_dsp->dma_lock); +- + return 0; + } + +@@ -236,9 +232,8 @@ static int rt5514_spi_hw_free(struct snd_soc_component *component, + struct rt5514_dsp *rt5514_dsp = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5514_dsp->dma_lock); +- rt5514_dsp->substream = NULL; +- mutex_unlock(&rt5514_dsp->dma_lock); ++ scoped_guard(mutex, &rt5514_dsp->dma_lock) ++ rt5514_dsp->substream = NULL; + + cancel_delayed_work_sync(&rt5514_dsp->copy_work); + +diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c +--- a/sound/soc/codecs/rt5514.c ++++ b/sound/soc/codecs/rt5514.c +@@ -1073,12 +1073,18 @@ static int rt5514_set_bias_level(struct snd_soc_component *component, + * settings to make sure recording properly. + */ + if (rt5514->dsp_enabled) { +- rt5514->dsp_enabled = 0; +- regmap_multi_reg_write(rt5514->i2c_regmap, +- rt5514_i2c_patch, +- ARRAY_SIZE(rt5514_i2c_patch)); ++ ret = regmap_multi_reg_write(rt5514->i2c_regmap, ++ rt5514_i2c_patch, ++ ARRAY_SIZE(rt5514_i2c_patch)); ++ if (ret) ++ return ret; ++ + regcache_mark_dirty(rt5514->regmap); +- regcache_sync(rt5514->regmap); ++ ret = regcache_sync(rt5514->regmap); ++ if (ret) ++ return ret; ++ ++ rt5514->dsp_enabled = 0; + } + } + break; +diff --git a/sound/soc/codecs/rt5575-spi.c b/sound/soc/codecs/rt5575-spi.c +--- a/sound/soc/codecs/rt5575-spi.c ++++ b/sound/soc/codecs/rt5575-spi.c +@@ -93,7 +93,6 @@ static void rt5575_spi_burst_write(struct spi_device *spi, u32 addr, const u8 *t + int rt5575_spi_fw_load(struct spi_device *spi) + { + struct device *dev = &spi->dev; +- const struct firmware *firmware; + int i, ret; + static const char * const fw_path[] = { + "realtek/rt5575/rt5575_fw1.bin", +@@ -104,6 +103,7 @@ int rt5575_spi_fw_load(struct spi_device *spi) + static const u32 fw_addr[] = { 0x5f400000, 0x5f600000, 0x5f7fe000, 0x5f7ff000 }; + + for (i = 0; i < ARRAY_SIZE(fw_addr); i++) { ++ const struct firmware *firmware __free(firmware) = NULL; + ret = request_firmware(&firmware, fw_path[i], dev); + if (ret) { + dev_err(dev, "Request firmware failure: %d\n", ret); +@@ -111,7 +111,6 @@ int rt5575_spi_fw_load(struct spi_device *spi) + } + + rt5575_spi_burst_write(spi, fw_addr[i], firmware->data, firmware->size); +- release_firmware(firmware); + } + + return 0; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -6,6 +6,7 @@ + * Author: Bard Liao + */ + ++#include + #include + #include + #include +@@ -3323,92 +3324,89 @@ static void rt5645_jack_detect_work(struct work_struct *work) + if (!rt5645->component) + return; + +- mutex_lock(&rt5645->jd_mutex); +- +- switch (rt5645->pdata.jd_mode) { +- case 0: /* Not using rt5645 JD */ +- if (rt5645->gpiod_hp_det) { +- gpio_state = gpiod_get_value(rt5645->gpiod_hp_det); +- if (rt5645->pdata.inv_hp_pol) +- gpio_state ^= 1; +- dev_dbg(rt5645->component->dev, "gpio_state = %d\n", +- gpio_state); +- report = rt5645_jack_detect(rt5645->component, gpio_state); +- } +- snd_soc_jack_report(rt5645->hp_jack, +- report, SND_JACK_HEADPHONE); +- snd_soc_jack_report(rt5645->mic_jack, +- report, SND_JACK_MICROPHONE); +- mutex_unlock(&rt5645->jd_mutex); +- return; +- case 4: +- val = snd_soc_component_read(rt5645->component, RT5645_A_JD_CTRL1) & 0x0020; +- break; +- default: /* read rt5645 jd1_1 status */ +- val = snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x1000; +- break; +- +- } +- +- if (!val && (rt5645->jack_type == 0)) { /* jack in */ +- report = rt5645_jack_detect(rt5645->component, 1); +- } else if (!val && rt5645->jack_type == SND_JACK_HEADSET) { +- /* for push button and jack out */ +- btn_type = 0; +- if (snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x4) { +- /* button pressed */ +- report = SND_JACK_HEADSET; +- btn_type = rt5645_button_detect(rt5645->component); +- /* rt5650 can report three kinds of button behavior, +- one click, double click and hold. However, +- currently we will report button pressed/released +- event. So all the three button behaviors are +- treated as button pressed. */ +- switch (btn_type) { +- case 0x8000: +- case 0x4000: +- case 0x2000: +- report |= SND_JACK_BTN_0; +- break; +- case 0x1000: +- case 0x0800: +- case 0x0400: +- report |= SND_JACK_BTN_1; +- break; +- case 0x0200: +- case 0x0100: +- case 0x0080: +- report |= SND_JACK_BTN_2; +- break; +- case 0x0040: +- case 0x0020: +- case 0x0010: +- report |= SND_JACK_BTN_3; +- break; +- case 0x0000: /* unpressed */ +- break; +- default: +- dev_err(rt5645->component->dev, +- "Unexpected button code 0x%04x\n", +- btn_type); +- break; ++ scoped_guard(mutex, &rt5645->jd_mutex) { ++ switch (rt5645->pdata.jd_mode) { ++ case 0: /* Not using rt5645 JD */ ++ if (rt5645->gpiod_hp_det) { ++ gpio_state = gpiod_get_value(rt5645->gpiod_hp_det); ++ if (rt5645->pdata.inv_hp_pol) ++ gpio_state ^= 1; ++ dev_dbg(rt5645->component->dev, "gpio_state = %d\n", ++ gpio_state); ++ report = rt5645_jack_detect(rt5645->component, gpio_state); + } ++ snd_soc_jack_report(rt5645->hp_jack, ++ report, SND_JACK_HEADPHONE); ++ snd_soc_jack_report(rt5645->mic_jack, ++ report, SND_JACK_MICROPHONE); ++ return; ++ case 4: ++ val = snd_soc_component_read(rt5645->component, RT5645_A_JD_CTRL1) & 0x0020; ++ break; ++ default: /* read rt5645 jd1_1 status */ ++ val = snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x1000; ++ break; + } +- if (btn_type == 0)/* button release */ +- report = rt5645->jack_type; +- else { +- mod_timer(&rt5645->btn_check_timer, +- msecs_to_jiffies(100)); +- } +- } else { +- /* jack out */ +- report = 0; +- snd_soc_component_update_bits(rt5645->component, +- RT5645_INT_IRQ_ST, 0x1, 0x0); +- rt5645_jack_detect(rt5645->component, 0); +- } + +- mutex_unlock(&rt5645->jd_mutex); ++ if (!val && rt5645->jack_type == 0) { /* jack in */ ++ report = rt5645_jack_detect(rt5645->component, 1); ++ } else if (!val && rt5645->jack_type == SND_JACK_HEADSET) { ++ /* for push button and jack out */ ++ btn_type = 0; ++ if (snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x4) { ++ /* button pressed */ ++ report = SND_JACK_HEADSET; ++ btn_type = rt5645_button_detect(rt5645->component); ++ /* ++ * rt5650 can report three kinds of button behavior, ++ * one click, double click and hold. However, ++ * currently we will report button pressed/released ++ * event. So all the three button behaviors are ++ * treated as button pressed. ++ */ ++ switch (btn_type) { ++ case 0x8000: ++ case 0x4000: ++ case 0x2000: ++ report |= SND_JACK_BTN_0; ++ break; ++ case 0x1000: ++ case 0x0800: ++ case 0x0400: ++ report |= SND_JACK_BTN_1; ++ break; ++ case 0x0200: ++ case 0x0100: ++ case 0x0080: ++ report |= SND_JACK_BTN_2; ++ break; ++ case 0x0040: ++ case 0x0020: ++ case 0x0010: ++ report |= SND_JACK_BTN_3; ++ break; ++ case 0x0000: /* unpressed */ ++ break; ++ default: ++ dev_err(rt5645->component->dev, ++ "Unexpected button code 0x%04x\n", ++ btn_type); ++ break; ++ } ++ } ++ if (btn_type == 0)/* button release */ ++ report = rt5645->jack_type; ++ else ++ mod_timer(&rt5645->btn_check_timer, ++ msecs_to_jiffies(100)); ++ } else { ++ /* jack out */ ++ report = 0; ++ snd_soc_component_update_bits(rt5645->component, ++ RT5645_INT_IRQ_ST, 0x1, 0x0); ++ rt5645_jack_detect(rt5645->component, 0); ++ } ++ } + + snd_soc_jack_report(rt5645->hp_jack, report, SND_JACK_HEADPHONE); + snd_soc_jack_report(rt5645->mic_jack, report, SND_JACK_MICROPHONE); +@@ -3522,9 +3520,15 @@ static int rt5645_suspend(struct snd_soc_component *component) + static int rt5645_resume(struct snd_soc_component *component) + { + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); ++ int ret; + + regcache_cache_only(rt5645->regmap, false); +- regcache_sync(rt5645->regmap); ++ ret = regcache_sync(rt5645->regmap); ++ if (ret) { ++ regcache_cache_only(rt5645->regmap, true); ++ regcache_mark_dirty(rt5645->regmap); ++ return ret; ++ } + + return 0; + } +@@ -4335,9 +4339,15 @@ static int rt5645_sys_suspend(struct device *dev) + static int rt5645_sys_resume(struct device *dev) + { + struct rt5645_priv *rt5645 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(rt5645->regmap, false); +- regcache_sync(rt5645->regmap); ++ ret = regcache_sync(rt5645->regmap); ++ if (ret) { ++ regcache_cache_only(rt5645->regmap, true); ++ regcache_mark_dirty(rt5645->regmap); ++ return ret; ++ } + + if (rt5645->hp_jack) { + rt5645->jack_type = 0; +diff --git a/sound/soc/codecs/rt5665.c b/sound/soc/codecs/rt5665.c +--- a/sound/soc/codecs/rt5665.c ++++ b/sound/soc/codecs/rt5665.c +@@ -6,6 +6,7 @@ + * Author: Bard Liao + */ + ++#include + #include + #include + #include +@@ -1208,7 +1209,7 @@ static void rt5665_jack_detect_handler(struct work_struct *work) + usleep_range(10000, 15000); + } + +- mutex_lock(&rt5665->calibrate_mutex); ++ guard(mutex)(&rt5665->calibrate_mutex); + + val = snd_soc_component_read(rt5665->component, RT5665_AJD1_CTRL) & 0x0010; + if (!val) { +@@ -1274,8 +1275,6 @@ static void rt5665_jack_detect_handler(struct work_struct *work) + schedule_delayed_work(&rt5665->jd_check_work, 0); + else + cancel_delayed_work_sync(&rt5665->jd_check_work); +- +- mutex_unlock(&rt5665->calibrate_mutex); + } + + static const char * const rt5665_clk_sync[] = { +@@ -4564,7 +4563,7 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + { + int value, count; + +- mutex_lock(&rt5665->calibrate_mutex); ++ guard(mutex)(&rt5665->calibrate_mutex); + + regcache_cache_bypass(rt5665->regmap, true); + +@@ -4601,7 +4600,8 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + pr_err("HP Calibration Failure\n"); + regmap_write(rt5665->regmap, RT5665_RESET, 0); + regcache_cache_bypass(rt5665->regmap, false); +- goto out_unlock; ++ rt5665->calibration_done = true; ++ return; + } + + count++; +@@ -4620,7 +4620,8 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + pr_err("MONO Calibration Failure\n"); + regmap_write(rt5665->regmap, RT5665_RESET, 0); + regcache_cache_bypass(rt5665->regmap, false); +- goto out_unlock; ++ rt5665->calibration_done = true; ++ return; + } + + count++; +@@ -4635,9 +4636,7 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + regmap_write(rt5665->regmap, RT5665_BIAS_CUR_CTRL_8, 0xa602); + regmap_write(rt5665->regmap, RT5665_ASRC_8, 0x0120); + +-out_unlock: + rt5665->calibration_done = true; +- mutex_unlock(&rt5665->calibrate_mutex); + } + + static void rt5665_calibrate_handler(struct work_struct *work) +diff --git a/sound/soc/codecs/rt5668.c b/sound/soc/codecs/rt5668.c +--- a/sound/soc/codecs/rt5668.c ++++ b/sound/soc/codecs/rt5668.c +@@ -6,6 +6,7 @@ + * Author: Bard Liao + */ + ++#include + #include + #include + #include +@@ -986,7 +987,7 @@ static void rt5668_jack_detect_handler(struct work_struct *work) + return; + } + +- mutex_lock(&rt5668->calibrate_mutex); ++ guard(mutex)(&rt5668->calibrate_mutex); + + val = snd_soc_component_read(rt5668->component, RT5668_AJD1_CTRL) + & RT5668_JDH_RS_MASK; +@@ -1053,8 +1054,6 @@ static void rt5668_jack_detect_handler(struct work_struct *work) + schedule_delayed_work(&rt5668->jd_check_work, 0); + else + cancel_delayed_work_sync(&rt5668->jd_check_work); +- +- mutex_unlock(&rt5668->calibrate_mutex); + } + + static const struct snd_kcontrol_new rt5668_snd_controls[] = { +@@ -2356,7 +2355,7 @@ static void rt5668_calibrate(struct rt5668_priv *rt5668) + { + int value, count; + +- mutex_lock(&rt5668->calibrate_mutex); ++ guard(mutex)(&rt5668->calibrate_mutex); + + rt5668_reset(rt5668->regmap); + regmap_write(rt5668->regmap, RT5668_PWR_ANLG_1, 0xa2bf); +@@ -2400,9 +2399,6 @@ static void rt5668_calibrate(struct rt5668_priv *rt5668) + /* restore settings */ + regmap_write(rt5668->regmap, RT5668_STO1_ADC_MIXER, 0xc0c4); + regmap_write(rt5668->regmap, RT5668_PWR_DIG_1, 0x0000); +- +- mutex_unlock(&rt5668->calibrate_mutex); +- + } + + static int rt5668_i2c_probe(struct i2c_client *i2c) +diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c +--- a/sound/soc/codecs/rt5677-spi.c ++++ b/sound/soc/codecs/rt5677-spi.c +@@ -6,6 +6,7 @@ + * Author: Oder Chiou + */ + ++#include + #include + #include + #include +@@ -133,9 +134,8 @@ static int rt5677_spi_hw_params( + struct rt5677_dsp *rt5677_dsp = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5677_dsp->dma_lock); ++ guard(mutex)(&rt5677_dsp->dma_lock); + rt5677_dsp->substream = substream; +- mutex_unlock(&rt5677_dsp->dma_lock); + + return 0; + } +@@ -147,9 +147,8 @@ static int rt5677_spi_hw_free( + struct rt5677_dsp *rt5677_dsp = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5677_dsp->dma_lock); ++ guard(mutex)(&rt5677_dsp->dma_lock); + rt5677_dsp->substream = NULL; +- mutex_unlock(&rt5677_dsp->dma_lock); + + return 0; + } +@@ -311,17 +310,17 @@ static void rt5677_spi_copy_work(struct work_struct *work) + int ret = 0; + + /* Ensure runtime->dma_area buffer does not go away while copying. */ +- mutex_lock(&rt5677_dsp->dma_lock); ++ guard(mutex)(&rt5677_dsp->dma_lock); + if (!rt5677_dsp->substream) { + dev_err(rt5677_dsp->dev, "No pcm substream\n"); +- goto done; ++ return; + } + + runtime = rt5677_dsp->substream->runtime; + + if (rt5677_spi_mic_write_offset(&mic_write_offset)) { + dev_err(rt5677_dsp->dev, "No mic_write_offset\n"); +- goto done; ++ return; + } + + /* If this is the first time that we've asked for streaming data after +@@ -355,7 +354,7 @@ static void rt5677_spi_copy_work(struct work_struct *work) + ret = rt5677_spi_copy(rt5677_dsp, copy_bytes); + if (ret) { + dev_err(rt5677_dsp->dev, "Copy failed %d\n", ret); +- goto done; ++ return; + } + rt5677_dsp->avail_bytes += copy_bytes; + if (rt5677_dsp->avail_bytes >= period_bytes) { +@@ -367,8 +366,6 @@ static void rt5677_spi_copy_work(struct work_struct *work) + + delay = bytes_to_frames(runtime, period_bytes) / runtime->rate; + schedule_delayed_work(&rt5677_dsp->copy_work, secs_to_jiffies(delay)); +-done: +- mutex_unlock(&rt5677_dsp->dma_lock); + } + + static int rt5677_spi_pcm_new(struct snd_soc_component *component, +@@ -507,10 +504,8 @@ int rt5677_spi_read(u32 addr, void *rxbuf, size_t len) + header[3] = ((addr + offset) & 0x0000ff00) >> 8; + header[4] = ((addr + offset) & 0x000000ff) >> 0; + +- mutex_lock(&spi_mutex); +- status |= spi_sync(g_spi, &m); +- mutex_unlock(&spi_mutex); +- ++ scoped_guard(mutex, &spi_mutex) ++ status |= spi_sync(g_spi, &m); + + /* Copy data back to caller buffer */ + rt5677_spi_reverse(cb + offset, len - offset, body, t[1].len); +@@ -564,9 +559,8 @@ int rt5677_spi_write(u32 addr, const void *txbuf, size_t len) + offset += t.len; + t.len += RT5677_SPI_HEADER + 1; + +- mutex_lock(&spi_mutex); +- status |= spi_sync(g_spi, &m); +- mutex_unlock(&spi_mutex); ++ scoped_guard(mutex, &spi_mutex) ++ status |= spi_sync(g_spi, &m); + } + return status; + } +@@ -591,10 +585,10 @@ void rt5677_spi_hotword_detected(void) + return; + } + +- mutex_lock(&rt5677_dsp->dma_lock); +- dev_info(rt5677_dsp->dev, "Hotword detected\n"); +- rt5677_dsp->new_hotword = true; +- mutex_unlock(&rt5677_dsp->dma_lock); ++ scoped_guard(mutex, &rt5677_dsp->dma_lock) { ++ dev_info(rt5677_dsp->dev, "Hotword detected\n"); ++ rt5677_dsp->new_hotword = true; ++ } + + schedule_delayed_work(&rt5677_dsp->copy_work, 0); + } +diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c +--- a/sound/soc/codecs/rt5677.c ++++ b/sound/soc/codecs/rt5677.c +@@ -6,6 +6,8 @@ + * Author: Oder Chiou + */ + ++#include ++#include + #include + #include + #include +@@ -563,46 +565,43 @@ static int rt5677_dsp_mode_i2c_write_addr(struct rt5677_priv *rt5677, + struct snd_soc_component *component = rt5677->component; + int ret; + +- mutex_lock(&rt5677->dsp_cmd_lock); ++ guard(mutex)(&rt5677->dsp_cmd_lock); + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_MSB, + addr >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr msb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_LSB, + addr & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr lsb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_MSB, + value >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set data msb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_LSB, + value & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set data lsb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_OP_CODE, + opcode); + if (ret < 0) { + dev_err(component->dev, "Failed to set op code value: %d\n", ret); +- goto err; ++ return ret; + } + +-err: +- mutex_unlock(&rt5677->dsp_cmd_lock); +- + return ret; + } + +@@ -622,36 +621,33 @@ static int rt5677_dsp_mode_i2c_read_addr( + int ret; + unsigned int msb, lsb; + +- mutex_lock(&rt5677->dsp_cmd_lock); ++ guard(mutex)(&rt5677->dsp_cmd_lock); + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_MSB, + addr >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr msb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_LSB, + addr & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr lsb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_OP_CODE, + 0x0002); + if (ret < 0) { + dev_err(component->dev, "Failed to set op code value: %d\n", ret); +- goto err; ++ return ret; + } + + regmap_read(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_MSB, &msb); + regmap_read(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_LSB, &lsb); + *value = (msb << 16) | lsb; + +-err: +- mutex_unlock(&rt5677->dsp_cmd_lock); +- + return ret; + } + +@@ -853,11 +849,11 @@ static int rt5677_parse_and_load_dsp(struct rt5677_priv *rt5677, const u8 *buf, + + static int rt5677_load_dsp_from_file(struct rt5677_priv *rt5677) + { +- const struct firmware *fwp; + struct device *dev = rt5677->component->dev; +- int ret = 0; ++ int ret; + + /* Load dsp firmware from rt5677_elf_vad file */ ++ const struct firmware *fwp __free(firmware) = NULL; + ret = request_firmware(&fwp, "rt5677_elf_vad", dev); + if (ret) { + dev_err(dev, "Request rt5677_elf_vad failed %d\n", ret); +@@ -865,9 +861,7 @@ static int rt5677_load_dsp_from_file(struct rt5677_priv *rt5677) + } + dev_info(dev, "Requested rt5677_elf_vad (%zu)\n", fwp->size); + +- ret = rt5677_parse_and_load_dsp(rt5677, fwp->data, fwp->size); +- release_firmware(fwp); +- return ret; ++ return rt5677_parse_and_load_dsp(rt5677, fwp->data, fwp->size); + } + + static int rt5677_set_dsp_vad(struct snd_soc_component *component, bool on) +@@ -940,21 +934,20 @@ static void rt5677_dsp_work(struct work_struct *work) + activity = false; + + /* Don't turn off the DSP while handling irqs */ +- mutex_lock(&rt5677->irq_lock); +- /* Set DSP CPU to Stop */ +- regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, +- RT5677_PWR_DSP_CPU, RT5677_PWR_DSP_CPU); ++ scoped_guard(mutex, &rt5677->irq_lock) { ++ /* Set DSP CPU to Stop */ ++ regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, ++ RT5677_PWR_DSP_CPU, RT5677_PWR_DSP_CPU); + +- rt5677_set_dsp_mode(rt5677, false); ++ rt5677_set_dsp_mode(rt5677, false); + +- /* Disable and clear VAD interrupt */ +- regmap_write(rt5677->regmap, RT5677_VAD_CTRL1, 0x2184); ++ /* Disable and clear VAD interrupt */ ++ regmap_write(rt5677->regmap, RT5677_VAD_CTRL1, 0x2184); + +- /* Set GPIO1 pin back to be IRQ output for jack detect */ +- regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, +- RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ); +- +- mutex_unlock(&rt5677->irq_lock); ++ /* Set GPIO1 pin back to be IRQ output for jack detect */ ++ regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, ++ RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ); ++ } + } + } + +@@ -4767,6 +4760,21 @@ static int rt5677_gpio_direction_in(struct gpio_chip *chip, unsigned offset) + return rt5677_update_gpio_bits(rt5677, offset, m, v); + } + ++static int rt5677_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) ++{ ++ struct rt5677_priv *rt5677 = gpiochip_get_data(chip); ++ unsigned int shift = RT5677_GPIOx_DIR_SFT + (offset % 5) * 3; ++ unsigned int bank = offset / 5; ++ unsigned int reg = bank ? RT5677_GPIO_CTRL3 : RT5677_GPIO_CTRL2; ++ int ret; ++ ++ ret = regmap_test_bits(rt5677->regmap, reg, BIT(shift)); ++ if (ret < 0) ++ return ret; ++ ++ return ret ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; ++} ++ + /* + * Configures the GPIO as + * 0 - floating +@@ -4834,6 +4842,7 @@ static int rt5677_to_irq(struct gpio_chip *chip, unsigned offset) + static const struct gpio_chip rt5677_template_chip = { + .label = RT5677_DRV_NAME, + .owner = THIS_MODULE, ++ .get_direction = rt5677_gpio_get_direction, + .direction_output = rt5677_gpio_direction_out, + .set = rt5677_gpio_set, + .direction_input = rt5677_gpio_direction_in, +@@ -4980,11 +4989,11 @@ static int rt5677_read(void *context, unsigned int reg, unsigned int *val) + + if (rt5677->is_dsp_mode) { + if (reg > 0xff) { +- mutex_lock(&rt5677->dsp_pri_lock); +- rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, +- reg & 0xff); +- rt5677_dsp_mode_i2c_read(rt5677, RT5677_PRIV_DATA, val); +- mutex_unlock(&rt5677->dsp_pri_lock); ++ scoped_guard(mutex, &rt5677->dsp_pri_lock) { ++ rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, ++ reg & 0xff); ++ rt5677_dsp_mode_i2c_read(rt5677, RT5677_PRIV_DATA, val); ++ } + } else { + rt5677_dsp_mode_i2c_read(rt5677, reg, val); + } +@@ -5002,12 +5011,12 @@ static int rt5677_write(void *context, unsigned int reg, unsigned int val) + + if (rt5677->is_dsp_mode) { + if (reg > 0xff) { +- mutex_lock(&rt5677->dsp_pri_lock); +- rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, +- reg & 0xff); +- rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_DATA, +- val); +- mutex_unlock(&rt5677->dsp_pri_lock); ++ scoped_guard(mutex, &rt5677->dsp_pri_lock) { ++ rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, ++ reg & 0xff); ++ rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_DATA, ++ val); ++ } + } else { + rt5677_dsp_mode_i2c_write(rt5677, reg, val); + } +@@ -5399,7 +5408,7 @@ static void rt5677_resume_irq_check(struct work_struct *work) + * Without this explicit check, unplug the headset right after suspend + * starts, then after resume the headset is still shown as plugged in. + */ +- mutex_lock(&rt5677->irq_lock); ++ guard(mutex)(&rt5677->irq_lock); + for (i = 0; i < RT5677_IRQ_NUM; i++) { + if (rt5677->irq_en & rt5677_irq_descs[i].enable_mask) { + virq = irq_find_mapping(rt5677->domain, i); +@@ -5407,7 +5416,6 @@ static void rt5677_resume_irq_check(struct work_struct *work) + handle_nested_irq(virq); + } + } +- mutex_unlock(&rt5677->irq_lock); + } + + static void rt5677_irq_bus_lock(struct irq_data *data) +diff --git a/sound/soc/codecs/rt5682-sdw.c b/sound/soc/codecs/rt5682-sdw.c +--- a/sound/soc/codecs/rt5682-sdw.c ++++ b/sound/soc/codecs/rt5682-sdw.c +@@ -6,6 +6,7 @@ + // Author: Oder Chiou + // + ++#include + #include + #include + #include +@@ -410,7 +411,9 @@ static int rt5682_io_init(struct device *dev, struct sdw_slave *slave) + if (rt5682->first_hw_init) { + regcache_cache_bypass(rt5682->regmap, false); + regcache_mark_dirty(rt5682->regmap); +- regcache_sync(rt5682->regmap); ++ ret = regcache_sync(rt5682->regmap); ++ if (ret) ++ goto err_sync; + + /* volatile registers */ + regmap_update_bits(rt5682->regmap, RT5682_CBJ_CTRL_2, +@@ -472,8 +475,16 @@ static int rt5682_io_init(struct device *dev, struct sdw_slave *slave) + /* Mark Slave initialization complete */ + rt5682->hw_init = true; + rt5682->first_hw_init = true; ++ goto out; ++ ++err_sync: ++ regcache_cache_bypass(rt5682->regmap, false); ++ regcache_cache_only(rt5682->sdw_regmap, true); ++ regcache_cache_only(rt5682->regmap, true); ++ regcache_mark_dirty(rt5682->regmap); + + err_nodev: ++out: + pm_runtime_put_autosuspend(&slave->dev); + + dev_dbg(&slave->dev, "%s hw_init complete: %d\n", __func__, ret); +@@ -660,12 +671,11 @@ static int rt5682_interrupt_callback(struct sdw_slave *slave, + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + +- mutex_lock(&rt5682->disable_irq_lock); ++ guard(mutex)(&rt5682->disable_irq_lock); + if (status->control_port & 0x4 && !rt5682->disable_irq) { + mod_delayed_work(system_power_efficient_wq, + &rt5682->jack_detect_work, msecs_to_jiffies(rt5682->irq_work_delay_time)); + } +- mutex_unlock(&rt5682->disable_irq_lock); + + return 0; + } +@@ -736,11 +746,11 @@ static int rt5682_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt5682->disable_irq_lock); +- rt5682->disable_irq = true; +- ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, +- SDW_SCP_INT1_IMPL_DEF, 0); +- mutex_unlock(&rt5682->disable_irq_lock); ++ scoped_guard(mutex, &rt5682->disable_irq_lock) { ++ rt5682->disable_irq = true; ++ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, ++ SDW_SCP_INT1_IMPL_DEF, 0); ++ } + + if (ret < 0) { + /* log but don't prevent suspend from happening */ +@@ -760,12 +770,12 @@ static int rt5682_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt5682->disable_irq_lock); +- if (rt5682->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); +- rt5682->disable_irq = false; ++ scoped_guard(mutex, &rt5682->disable_irq_lock) { ++ if (rt5682->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); ++ rt5682->disable_irq = false; ++ } + } +- mutex_unlock(&rt5682->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT5682_PROBE_TIMEOUT); +@@ -776,7 +786,13 @@ static int rt5682_dev_resume(struct device *dev) + + regcache_cache_only(rt5682->sdw_regmap, false); + regcache_cache_only(rt5682->regmap, false); +- regcache_sync(rt5682->regmap); ++ ret = regcache_sync(rt5682->regmap); ++ if (ret) { ++ regcache_cache_only(rt5682->sdw_regmap, true); ++ regcache_cache_only(rt5682->regmap, true); ++ regcache_mark_dirty(rt5682->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt5682.c b/sound/soc/codecs/rt5682.c +--- a/sound/soc/codecs/rt5682.c ++++ b/sound/soc/codecs/rt5682.c +@@ -6,6 +6,7 @@ + // Author: Bard Liao + // + ++#include + #include + #include + #include +@@ -3125,7 +3126,7 @@ void rt5682_calibrate(struct rt5682_priv *rt5682) + { + int value, count; + +- mutex_lock(&rt5682->calibrate_mutex); ++ guard(mutex)(&rt5682->calibrate_mutex); + + rt5682_reset(rt5682); + regmap_write(rt5682->regmap, RT5682_I2C_CTRL, 0x000f); +@@ -3175,8 +3176,6 @@ void rt5682_calibrate(struct rt5682_priv *rt5682) + regmap_write(rt5682->regmap, RT5682_CALIB_ADC_CTRL, 0x2005); + regmap_write(rt5682->regmap, RT5682_STO1_ADC_MIXER, 0xc0c4); + regmap_write(rt5682->regmap, RT5682_CAL_REC, 0x0c0c); +- +- mutex_unlock(&rt5682->calibrate_mutex); + } + EXPORT_SYMBOL_GPL(rt5682_calibrate); + +diff --git a/sound/soc/codecs/rt5682s.c b/sound/soc/codecs/rt5682s.c +--- a/sound/soc/codecs/rt5682s.c ++++ b/sound/soc/codecs/rt5682s.c +@@ -6,6 +6,7 @@ + // Author: Derek Fang + // + ++#include + #include + #include + #include +@@ -648,7 +649,7 @@ static void rt5682s_sar_power_mode(struct snd_soc_component *component, int mode + { + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5682s->sar_mutex); ++ guard(mutex)(&rt5682s->sar_mutex); + + switch (mode) { + case SAR_PWR_SAVING: +@@ -695,8 +696,6 @@ static void rt5682s_sar_power_mode(struct snd_soc_component *component, int mode + dev_err(component->dev, "Invalid SAR Power mode: %d\n", mode); + break; + } +- +- mutex_unlock(&rt5682s->sar_mutex); + } + + static void rt5682s_enable_push_button_irq(struct snd_soc_component *component) +@@ -2534,7 +2533,7 @@ static int rt5682s_wclk_prepare(struct clk_hw *hw) + if (!rt5682s_clk_check(rt5682s)) + return -EINVAL; + +- mutex_lock(&rt5682s->wclk_mutex); ++ guard(mutex)(&rt5682s->wclk_mutex); + + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, + RT5682S_PWR_VREF2 | RT5682S_PWR_FV2 | RT5682S_PWR_MB, +@@ -2556,8 +2555,6 @@ static int rt5682s_wclk_prepare(struct clk_hw *hw) + + rt5682s->wclk_enabled = 1; + +- mutex_unlock(&rt5682s->wclk_mutex); +- + return 0; + } + +@@ -2570,7 +2567,7 @@ static void rt5682s_wclk_unprepare(struct clk_hw *hw) + if (!rt5682s_clk_check(rt5682s)) + return; + +- mutex_lock(&rt5682s->wclk_mutex); ++ guard(mutex)(&rt5682s->wclk_mutex); + + if (!rt5682s->jack_type) + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, +@@ -2585,8 +2582,6 @@ static void rt5682s_wclk_unprepare(struct clk_hw *hw) + rt5682s_set_pllb_power(rt5682s, 0); + + rt5682s->wclk_enabled = 0; +- +- mutex_unlock(&rt5682s->wclk_mutex); + } + + static unsigned long rt5682s_wclk_recalc_rate(struct clk_hw *hw, +@@ -2997,7 +2992,7 @@ static void rt5682s_calibrate(struct rt5682s_priv *rt5682s) + { + unsigned int count, value; + +- mutex_lock(&rt5682s->calibrate_mutex); ++ guard(mutex)(&rt5682s->calibrate_mutex); + + regmap_write(rt5682s->regmap, RT5682S_PWR_ANLG_1, 0xaa80); + usleep_range(15000, 20000); +@@ -3034,8 +3029,6 @@ static void rt5682s_calibrate(struct rt5682s_priv *rt5682s) + regmap_write(rt5682s->regmap, RT5682S_PWR_DIG_1, 0x00c0); + regmap_write(rt5682s->regmap, RT5682S_PWR_ANLG_1, 0x0800); + regmap_write(rt5682s->regmap, RT5682S_GLB_CLK, 0x0000); +- +- mutex_unlock(&rt5682s->calibrate_mutex); + } + + static const struct regmap_config rt5682s_regmap = { +diff --git a/sound/soc/codecs/rt700-sdw.c b/sound/soc/codecs/rt700-sdw.c +--- a/sound/soc/codecs/rt700-sdw.c ++++ b/sound/soc/codecs/rt700-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -415,12 +416,11 @@ static int rt700_interrupt_callback(struct sdw_slave *slave, + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + +- mutex_lock(&rt700->disable_irq_lock); ++ guard(mutex)(&rt700->disable_irq_lock); + if (status->control_port & 0x4 && !rt700->disable_irq) { + mod_delayed_work(system_power_efficient_wq, + &rt700->jack_detect_work, msecs_to_jiffies(250)); + } +- mutex_unlock(&rt700->disable_irq_lock); + + return 0; + } +@@ -458,10 +458,8 @@ static void rt700_sdw_remove(struct sdw_slave *slave) + { + struct rt700_priv *rt700 = dev_get_drvdata(&slave->dev); + +- if (rt700->hw_init) { +- cancel_delayed_work_sync(&rt700->jack_detect_work); +- cancel_delayed_work_sync(&rt700->jack_btn_check_work); +- } ++ cancel_delayed_work_sync(&rt700->jack_detect_work); ++ cancel_delayed_work_sync(&rt700->jack_btn_check_work); + + pm_runtime_disable(&slave->dev); + } +@@ -501,11 +499,11 @@ static int rt700_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt700->disable_irq_lock); +- rt700->disable_irq = true; +- ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, +- SDW_SCP_INT1_IMPL_DEF, 0); +- mutex_unlock(&rt700->disable_irq_lock); ++ scoped_guard(mutex, &rt700->disable_irq_lock) { ++ rt700->disable_irq = true; ++ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, ++ SDW_SCP_INT1_IMPL_DEF, 0); ++ } + + if (ret < 0) { + /* log but don't prevent suspend from happening */ +diff --git a/sound/soc/codecs/rt711-sdca-sdw.c b/sound/soc/codecs/rt711-sdca-sdw.c +--- a/sound/soc/codecs/rt711-sdca-sdw.c ++++ b/sound/soc/codecs/rt711-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -415,13 +416,13 @@ static int rt711_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt711_sdca->disable_irq_lock); +- rt711_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt711_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt711_sdca->disable_irq_lock) { ++ rt711_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -443,13 +444,15 @@ static int rt711_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt711->disable_irq_lock); +- if (rt711->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt711->disable_irq = false; ++ scoped_guard(mutex, &rt711->disable_irq_lock) { ++ if (rt711->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt711->disable_irq = false; ++ } + } +- mutex_unlock(&rt711->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT711_PROBE_TIMEOUT); +diff --git a/sound/soc/codecs/rt711-sdca.c b/sound/soc/codecs/rt711-sdca.c +--- a/sound/soc/codecs/rt711-sdca.c ++++ b/sound/soc/codecs/rt711-sdca.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -450,7 +451,7 @@ static void rt711_sdca_btn_check_handler(struct work_struct *work) + + static void rt711_sdca_jack_init(struct rt711_sdca_priv *rt711) + { +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + + if (rt711->hs_jack) { + /* Enable HID1 event & set button RTC mode */ +@@ -515,8 +516,6 @@ static void rt711_sdca_jack_init(struct rt711_sdca_priv *rt711) + + dev_dbg(&rt711->slave->dev, "in %s disable\n", __func__); + } +- +- mutex_unlock(&rt711->calibrate_mutex); + } + + static int rt711_sdca_set_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/rt711-sdw.c b/sound/soc/codecs/rt711-sdw.c +--- a/sound/soc/codecs/rt711-sdw.c ++++ b/sound/soc/codecs/rt711-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -422,12 +423,11 @@ static int rt711_interrupt_callback(struct sdw_slave *slave, + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + +- mutex_lock(&rt711->disable_irq_lock); ++ guard(mutex)(&rt711->disable_irq_lock); + if (status->control_port & 0x4 && !rt711->disable_irq) { + mod_delayed_work(system_power_efficient_wq, + &rt711->jack_detect_work, msecs_to_jiffies(250)); + } +- mutex_unlock(&rt711->disable_irq_lock); + + return 0; + } +@@ -509,11 +509,11 @@ static int rt711_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt711->disable_irq_lock); +- rt711->disable_irq = true; +- ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, +- SDW_SCP_INT1_IMPL_DEF, 0); +- mutex_unlock(&rt711->disable_irq_lock); ++ scoped_guard(mutex, &rt711->disable_irq_lock) { ++ rt711->disable_irq = true; ++ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, ++ SDW_SCP_INT1_IMPL_DEF, 0); ++ } + + if (ret < 0) { + /* log but don't prevent suspend from happening */ +@@ -535,12 +535,12 @@ static int rt711_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt711->disable_irq_lock); +- if (rt711->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); +- rt711->disable_irq = false; ++ scoped_guard(mutex, &rt711->disable_irq_lock) { ++ if (rt711->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); ++ rt711->disable_irq = false; ++ } + } +- mutex_unlock(&rt711->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT711_PROBE_TIMEOUT); +diff --git a/sound/soc/codecs/rt711.c b/sound/soc/codecs/rt711.c +--- a/sound/soc/codecs/rt711.c ++++ b/sound/soc/codecs/rt711.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -89,24 +90,24 @@ static int rt711_calibration(struct rt711_priv *rt711) + struct regmap *regmap = rt711->regmap; + int ret = 0; + +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + dev = regmap_get_device(regmap); + + /* Calibration manual mode */ + rt711_index_update_bits(regmap, RT711_VENDOR_REG, RT711_FSM_CTL, +- 0xf, 0x0); ++ 0xf, 0x0); + + /* trigger */ + rt711_index_update_bits(regmap, RT711_VENDOR_CALI, +- RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_CALI_TRIGGER, +- RT711_DAC_DC_CALI_TRIGGER); ++ RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_CALI_TRIGGER, ++ RT711_DAC_DC_CALI_TRIGGER); + + /* wait for calibration process */ + rt711_index_read(regmap, RT711_VENDOR_CALI, +- RT711_DAC_DC_CALI_CTL1, &val); ++ RT711_DAC_DC_CALI_CTL1, &val); + + while (val & RT711_DAC_DC_CALI_TRIGGER) { + if (loop >= 500) { +@@ -119,16 +120,15 @@ static int rt711_calibration(struct rt711_priv *rt711) + + usleep_range(10000, 11000); + rt711_index_read(regmap, RT711_VENDOR_CALI, +- RT711_DAC_DC_CALI_CTL1, &val); ++ RT711_DAC_DC_CALI_CTL1, &val); + } + + /* depop mode */ + rt711_index_update_bits(regmap, RT711_VENDOR_REG, +- RT711_FSM_CTL, 0xf, RT711_DEPOP_CTL); ++ RT711_FSM_CTL, 0xf, RT711_DEPOP_CTL); + + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); +- mutex_unlock(&rt711->calibrate_mutex); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + + dev_dbg(dev, "%s calibration complete, ret=%d\n", __func__, ret); + return ret; +@@ -362,24 +362,24 @@ static void rt711_jack_init(struct rt711_priv *rt711) + { + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(rt711->component); + +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + /* power on */ + if (snd_soc_dapm_get_bias_level(dapm) <= SND_SOC_BIAS_STANDBY) + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + if (rt711->hs_jack) { + /* unsolicited response & IRQ control */ + regmap_write(rt711->regmap, +- RT711_SET_MIC2_UNSOLICITED_ENABLE, 0x82); ++ RT711_SET_MIC2_UNSOLICITED_ENABLE, 0x82); + regmap_write(rt711->regmap, +- RT711_SET_HP_UNSOLICITED_ENABLE, 0x81); ++ RT711_SET_HP_UNSOLICITED_ENABLE, 0x81); + regmap_write(rt711->regmap, +- RT711_SET_INLINE_UNSOLICITED_ENABLE, 0x83); ++ RT711_SET_INLINE_UNSOLICITED_ENABLE, 0x83); + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, +- 0x10, 0x2420); ++ 0x10, 0x2420); + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, +- 0x19, 0x2e11); ++ 0x19, 0x2e11); + + switch (rt711->jd_src) { + case RT711_JD1: +@@ -449,8 +449,7 @@ static void rt711_jack_init(struct rt711_priv *rt711) + /* power off */ + if (snd_soc_dapm_get_bias_level(dapm) <= SND_SOC_BIAS_STANDBY) + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); +- mutex_unlock(&rt711->calibrate_mutex); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + } + + static int rt711_set_jack_detect(struct snd_soc_component *component, +@@ -511,7 +510,7 @@ static int rt711_set_amp_gain_put(struct snd_kcontrol *kcontrol, + unsigned int read_ll, read_rl; + int i; + +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + + /* Can't use update bit function, so read the original value first */ + addr_h = mc->reg; +@@ -599,7 +598,6 @@ static int rt711_set_amp_gain_put(struct snd_kcontrol *kcontrol, + regmap_write(rt711->regmap, + RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + +- mutex_unlock(&rt711->calibrate_mutex); + return 0; + } + +@@ -908,11 +906,11 @@ static int rt711_set_bias_level(struct snd_soc_component *component, + break; + + case SND_SOC_BIAS_STANDBY: +- mutex_lock(&rt711->calibrate_mutex); +- regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, +- AC_PWRST_D3); +- mutex_unlock(&rt711->calibrate_mutex); ++ scoped_guard(mutex, &rt711->calibrate_mutex) { ++ regmap_write(rt711->regmap, ++ RT711_SET_AUDIO_POWER_STATE, ++ AC_PWRST_D3); ++ } + break; + + default: +diff --git a/sound/soc/codecs/rt712-sdca-dmic.c b/sound/soc/codecs/rt712-sdca-dmic.c +--- a/sound/soc/codecs/rt712-sdca-dmic.c ++++ b/sound/soc/codecs/rt712-sdca-dmic.c +@@ -916,10 +916,23 @@ static int rt712_sdca_dmic_dev_resume(struct device *dev) + } + + regcache_cache_only(rt712->regmap, false); +- regcache_sync(rt712->regmap); ++ ret = regcache_sync(rt712->regmap); ++ if (ret) ++ goto err_sync; ++ + regcache_cache_only(rt712->mbq_regmap, false); +- regcache_sync(rt712->mbq_regmap); ++ ret = regcache_sync(rt712->mbq_regmap); ++ if (ret) ++ goto err_sync; ++ + return 0; ++ ++err_sync: ++ regcache_cache_only(rt712->regmap, true); ++ regcache_cache_only(rt712->mbq_regmap, true); ++ regcache_mark_dirty(rt712->regmap); ++ regcache_mark_dirty(rt712->mbq_regmap); ++ return ret; + } + + static const struct dev_pm_ops rt712_sdca_dmic_pm = { +diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c +--- a/sound/soc/codecs/rt712-sdca-sdw.c ++++ b/sound/soc/codecs/rt712-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -427,13 +428,13 @@ static int rt712_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt712_sdca->disable_irq_lock); +- rt712_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt712_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt712_sdca->disable_irq_lock) { ++ rt712_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -455,14 +456,15 @@ static int rt712_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt712->disable_irq_lock); +- if (rt712->disable_irq == true) { +- +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt712->disable_irq = false; ++ scoped_guard(mutex, &rt712->disable_irq_lock) { ++ if (rt712->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt712->disable_irq = false; ++ } + } +- mutex_unlock(&rt712->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT712_PROBE_TIMEOUT); +@@ -472,9 +474,20 @@ static int rt712_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt712->regmap, false); +- regcache_sync(rt712->regmap); ++ ret = regcache_sync(rt712->regmap); ++ if (ret) { ++ regcache_cache_only(rt712->regmap, true); ++ return ret; ++ } ++ + regcache_cache_only(rt712->mbq_regmap, false); +- regcache_sync(rt712->mbq_regmap); ++ ret = regcache_sync(rt712->mbq_regmap); ++ if (ret) { ++ regcache_cache_only(rt712->mbq_regmap, true); ++ regcache_cache_only(rt712->regmap, true); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -7,6 +7,7 @@ + // + + #include ++#include + #include + #include + #include +@@ -403,7 +404,7 @@ static void rt712_sdca_btn_check_handler(struct work_struct *work) + + static void rt712_sdca_jack_init(struct rt712_sdca_priv *rt712) + { +- mutex_lock(&rt712->calibrate_mutex); ++ guard(mutex)(&rt712->calibrate_mutex); + + if (rt712->hs_jack) { + /* Enable HID1 event & set button RTC mode */ +@@ -450,8 +451,6 @@ static void rt712_sdca_jack_init(struct rt712_sdca_priv *rt712) + + dev_dbg(&rt712->slave->dev, "in %s disable\n", __func__); + } +- +- mutex_unlock(&rt712->calibrate_mutex); + } + + static int rt712_sdca_set_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c +--- a/sound/soc/codecs/rt721-sdca-sdw.c ++++ b/sound/soc/codecs/rt721-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -466,13 +467,13 @@ static int rt721_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt721_sdca->disable_irq_lock); +- rt721_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt721_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt721_sdca->disable_irq_lock) { ++ rt721_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -494,13 +495,15 @@ static int rt721_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt721->disable_irq_lock); +- if (rt721->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt721->disable_irq = false; ++ scoped_guard(mutex, &rt721->disable_irq_lock) { ++ if (rt721->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt721->disable_irq = false; ++ } + } +- mutex_unlock(&rt721->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT721_PROBE_TIMEOUT); +@@ -510,9 +513,20 @@ static int rt721_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt721->regmap, false); +- regcache_sync(rt721->regmap); ++ ret = regcache_sync(rt721->regmap); ++ if (ret) { ++ regcache_cache_only(rt721->regmap, true); ++ return ret; ++ } ++ + regcache_cache_only(rt721->mbq_regmap, false); +- regcache_sync(rt721->mbq_regmap); ++ ret = regcache_sync(rt721->mbq_regmap); ++ if (ret) { ++ regcache_cache_only(rt721->mbq_regmap, true); ++ regcache_cache_only(rt721->regmap, true); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c +--- a/sound/soc/codecs/rt721-sdca.c ++++ b/sound/soc/codecs/rt721-sdca.c +@@ -5,7 +5,7 @@ + // Copyright(c) 2024 Realtek Semiconductor Corp. + // + // +- ++#include + #include + #include + #include +@@ -289,7 +289,7 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) + + static void rt721_sdca_jack_init(struct rt721_sdca_priv *rt721) + { +- mutex_lock(&rt721->calibrate_mutex); ++ guard(mutex)(&rt721->calibrate_mutex); + if (rt721->hs_jack) { + sdw_write_no_pm(rt721->slave, SDW_SCP_SDCA_INTMASK1, + SDW_SCP_SDCA_INTMASK_SDCA_0); +@@ -309,7 +309,6 @@ static void rt721_sdca_jack_init(struct rt721_sdca_priv *rt721) + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_GE_REL_CTRL1, 0x4000, 0x4000); + } +- mutex_unlock(&rt721->calibrate_mutex); + } + + static int rt721_sdca_set_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/rt722-sdca-sdw.c b/sound/soc/codecs/rt722-sdca-sdw.c +--- a/sound/soc/codecs/rt722-sdca-sdw.c ++++ b/sound/soc/codecs/rt722-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -517,13 +518,13 @@ static int rt722_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt722_sdca->disable_irq_lock); +- rt722_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt722_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt722_sdca->disable_irq_lock) { ++ rt722_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -545,13 +546,15 @@ static int rt722_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt722->disable_irq_lock); +- if (rt722->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt722->disable_irq = false; ++ scoped_guard(mutex, &rt722->disable_irq_lock) { ++ if (rt722->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt722->disable_irq = false; ++ } + } +- mutex_unlock(&rt722->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT722_PROBE_TIMEOUT); +@@ -561,7 +564,13 @@ static int rt722_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt722->regmap, false); +- regcache_sync(rt722->regmap); ++ ret = regcache_sync(rt722->regmap); ++ if (ret) { ++ regcache_cache_only(rt722->regmap, true); ++ regcache_mark_dirty(rt722->regmap); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c +--- a/sound/soc/codecs/rt722-sdca.c ++++ b/sound/soc/codecs/rt722-sdca.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -294,7 +295,7 @@ static void rt722_sdca_btn_check_handler(struct work_struct *work) + + static void rt722_sdca_jack_init(struct rt722_sdca_priv *rt722) + { +- mutex_lock(&rt722->calibrate_mutex); ++ guard(mutex)(&rt722->calibrate_mutex); + if (rt722->hs_jack) { + /* set SCP_SDCA_IntMask1[0]=1 */ + sdw_write_no_pm(rt722->slave, SDW_SCP_SDCA_INTMASK1, +@@ -317,7 +318,6 @@ static void rt722_sdca_jack_init(struct rt722_sdca_priv *rt722) + rt722_sdca_index_update_bits(rt722, RT722_VENDOR_HDA_CTL, + RT722_GE_RELATED_CTL2, 0x4000, 0x4000); + } +- mutex_unlock(&rt722->calibrate_mutex); + } + + static int rt722_sdca_set_jack_detect(struct snd_soc_component *component, +@@ -352,8 +352,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + static const char func_tag[] = "FUNC"; + static const char xu_tag[] = "XU"; + const char *dmi_vendor, *dmi_product, *dmi_sku; +- char *cae_filename; +- const struct firmware *cae_fw = NULL; + unsigned int cae_st_spk, cae_st_hp, cae_st_mic; + unsigned int func, value; + unsigned int combined_val; +@@ -385,7 +383,8 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + space = strchr(dmi_sku, ' '); + s_len = space ? space - dmi_sku : strlen(dmi_sku); + +- cae_filename = kasprintf(GFP_KERNEL, ++ char *cae_filename __free(kfree) = ++ kasprintf(GFP_KERNEL, + "realtek/rt722/rt722_RAE_%.*s_%.*s_%.*s.dat", + v_len, dmi_vendor, + p_len, dmi_product, +@@ -399,8 +398,8 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + regmap_write(rt722->regmap, RT722_MIC_CAE_PARAM39, 0x5f); + usleep_range(50000, 60000); + ++ const struct firmware *cae_fw __free(firmware) = NULL; + request_firmware(&cae_fw, cae_filename, dev); +- kfree(cae_filename); + if (!cae_fw) { + dev_err(dev, "%s: Failed to load CAE firmware\n", __func__); + return -ENOENT; +@@ -555,7 +554,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + regcache_cache_bypass(rt722->regmap, false); + rt722->cae_update_done = 1; + dev_dbg(dev, "%s: CAE FW update done.\n", __func__); +- release_firmware(cae_fw); + return 0; + + verify_abort: +@@ -565,7 +563,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + out_release: + rt722_sdca_index_update_bits(rt722, RT722_VENDOR_REG, + RT722_MISC_CTRL1, 0x8000, 0x0000); +- release_firmware(cae_fw); + dev_err(dev, "%s: CAE FW update aborted (ret=%d).\n", __func__, ret); + return ret; + } +@@ -1906,6 +1903,9 @@ int rt722_sdca_io_init(struct device *dev, struct sdw_slave *slave) + rt722_sdca_amp_preset(rt722); + rt722_sdca_jack_preset(rt722); + ++ if (rt722->hs_jack && (!rt722->first_hw_init)) ++ rt722_sdca_jack_init(rt722); ++ + if (rt722->first_hw_init) { + regcache_cache_bypass(rt722->regmap, false); + regcache_mark_dirty(rt722->regmap); +diff --git a/sound/soc/codecs/rt766-sdca-sdw.c b/sound/soc/codecs/rt766-sdca-sdw.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca-sdw.c +@@ -0,0 +1,339 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// rt766-sdca-sdw.c -- rt766 SDCA ALSA SoC audio driver ++// ++// Copyright(c) 2026 Realtek Semiconductor Corp. ++// ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "rt766-sdca.h" ++#include "rt766-sdca-sdw.h" ++ ++#define RT766_PROBE_TIMEOUT 5000 ++ ++static bool rt766_sdca_readable_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4: ++ case RT766_VERSION_ID ... RT766_BOND_LATCH_ID: ++ case 0xc344 ... 0xc345: ++ case 0xc900: ++ case 0xc920: ++ case 0xd540 ... 0xd542: ++ case 0xf01e: ++ case RT766_HP_POWER_STATE ... RT766_HP_FSM_CTL2_1: ++ case 0x310100: ++ case RT766_MCU_PATCH_ADDR1_START ... RT766_MCU_PATCH_ADDR1_END: ++ case RT766_MCU_PATCH_ADDR2_START ... RT766_MCU_PATCH_ADDR2_END: ++ case RT766_MUTE_REG(UAJ, USER_FU41, 1): ++ case RT766_MUTE_REG(UAJ, USER_FU41, 2): ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 2): ++ case RT766_MUTE_REG(UAJ, USER_FU36, 1): ++ case RT766_MUTE_REG(UAJ, USER_FU36, 2): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 2): ++ case RT766_PDE_REQ_REG(UAJ, PDE47): ++ case RT766_PDE_REQ_REG(UAJ, PDE34): ++ case RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_FUNC_STATUS_REG(UAJ): /* 0x40480000 */ ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE47): /* 0x40481400 */ ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE34): /* 0x40481480 */ ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1): ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2): ++ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE): ++ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE): /* 0x40600490 */ ++ case RT766_PDE_REQ_REG(MIC, PDE11): ++ case RT766_MUTE_REG(MIC, USER_FU113, 1): ++ case RT766_MUTE_REG(MIC, USER_FU113, 2): ++ case RT766_MUTE_REG(MIC, USER_FU113, 3): ++ case RT766_MUTE_REG(MIC, USER_FU113, 4): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 1): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 2): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 3): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 4): ++ case RT766_FUNC_STATUS_REG(MIC): /* 0x40880000 */ ++ case RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_PDE_ACTUAL_REG(MIC, PDE11): /* 0x40881500 */ ++ case RT766_FUNC_STATUS_REG(HID): /* 0x40c80000 */ ++ /* 0x40c80080 - 0x40c80098 */ ++ case RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER) ... ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH): ++ case RT766_MUTE_REG(AMP, USER_FU21, 1): ++ case RT766_MUTE_REG(AMP, USER_FU21, 2): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 1): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 2): ++ case RT766_PDE_REQ_REG(AMP, PDE23): ++ case RT766_FUNC_STATUS_REG(AMP): /* 0x41080000 */ ++ case RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER): ++ case RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_PDE_ACTUAL_REG(AMP, PDE23): /* 0x41081980 */ ++ case RT766_BUF_ADDR_HID1 ... RT766_BUF_ADDR_HID2: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static bool rt766_sdca_volatile_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4: ++ case RT766_VERSION_ID ... RT766_BOND_LATCH_ID: ++ case 0xc344 ... 0xc345: ++ case 0xc900: ++ case 0xc920: ++ case 0xd540 ... 0xd542: ++ case 0xf01e: ++ case RT766_HP_POWER_STATE ... RT766_HP_FSM_CTL2_1: ++ case 0x310100: ++ case RT766_MCU_PATCH_ADDR1_START ... RT766_MCU_PATCH_ADDR1_END: ++ case RT766_MCU_PATCH_ADDR2_START ... RT766_MCU_PATCH_ADDR2_END: ++ case RT766_FUNC_STATUS_REG(UAJ): ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE47): ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE34): ++ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE): ++ case RT766_FUNC_STATUS_REG(MIC): ++ case RT766_PDE_ACTUAL_REG(MIC, PDE11): ++ case RT766_FUNC_STATUS_REG(HID): ++ case RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER) ... ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH): ++ case RT766_FUNC_STATUS_REG(AMP): ++ case RT766_PDE_ACTUAL_REG(AMP, PDE23): ++ case RT766_BUF_ADDR_HID1 ... RT766_BUF_ADDR_HID2: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static int rt766_sdca_mbq_size(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 2): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 2): ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1): ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 1): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 2): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 3): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 4): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 1): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 2): ++ return 2; ++ default: ++ return 1; ++ } ++} ++ ++static const struct regmap_sdw_mbq_cfg rt766_sdca_mbq_cfg = { ++ .mbq_size = rt766_sdca_mbq_size, ++}; ++ ++static const struct regmap_config rt766_sdca_regmap = { ++ .reg_bits = 32, ++ .val_bits = 16, ++ .readable_reg = rt766_sdca_readable_register, ++ .volatile_reg = rt766_sdca_volatile_register, ++ .reg_defaults = rt766_sdca_defaults, ++ .num_reg_defaults = ARRAY_SIZE(rt766_sdca_defaults), ++ .max_register = SDW_SDCA_MAX_REGISTER, ++ .cache_type = REGCACHE_MAPLE, ++ .use_single_read = true, ++ .use_single_write = true, ++}; ++ ++static int rt766_sdca_update_status(struct sdw_slave *slave, ++ enum sdw_slave_status status) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(&slave->dev); ++ ++ if (status == SDW_SLAVE_UNATTACHED) ++ rt766->hw_init = false; ++ ++ if (status == SDW_SLAVE_ATTACHED) { ++ if (rt766->hs_jack) { ++ /* ++ * Due to the SCP_SDCA_INTMASK will be cleared by any reset, and then ++ * if the device attached again, we will need to set the setting back. ++ * It could avoid losing the jack detection interrupt. ++ * This also could sync with the cache value as the rt766_sdca_jack_init set. ++ */ ++ sdw_write_no_pm(rt766->slave, SDW_SCP_SDCA_INTMASK3, ++ SDW_SCP_SDCA_INTMASK_SDCA_16); ++ sdw_write_no_pm(rt766->slave, SDW_SCP_SDCA_INTMASK4, ++ SDW_SCP_SDCA_INTMASK_SDCA_24); ++ } ++ } ++ ++ /* ++ * Perform initialization only if slave status is present and ++ * hw_init flag is false ++ */ ++ if (rt766->hw_init || status != SDW_SLAVE_ATTACHED) ++ return 0; ++ ++ /* perform I/O transfers required for Slave initialization */ ++ return rt766_sdca_io_init(&slave->dev, slave); ++} ++ ++static int rt766_sdca_read_prop(struct sdw_slave *slave) ++{ ++ struct sdw_slave_prop *prop = &slave->prop; ++ int ret; ++ ++ ret = sdw_slave_read_prop(slave); ++ if (ret < 0) ++ return ret; ++ ++ prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; ++ prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; ++ /* ++ * SDCA interrupts are routed through SoundWire domain IRQ. ++ */ ++ prop->use_domain_irq = true; ++ ++ return 0; ++} ++ ++static const struct sdw_slave_ops rt766_sdca_slave_ops = { ++ .read_prop = rt766_sdca_read_prop, ++ .update_status = rt766_sdca_update_status, ++}; ++ ++static int rt766_sdca_sdw_probe(struct sdw_slave *slave, ++ const struct sdw_device_id *id) ++{ ++ struct regmap *regmap; ++ ++ /* Regmap Initialization */ ++ regmap = devm_regmap_init_sdw_mbq_cfg(&slave->dev, slave, ++ &rt766_sdca_regmap, &rt766_sdca_mbq_cfg); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); ++ ++ return rt766_sdca_init(&slave->dev, regmap, slave); ++} ++ ++static void rt766_sdca_sdw_remove(struct sdw_slave *slave) ++{ ++ pm_runtime_disable(&slave->dev); ++} ++ ++static const struct sdw_device_id rt766_sdca_id[] = { ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x3, 0x1, 0), ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x3, 0x1, 0), ++ {}, ++}; ++MODULE_DEVICE_TABLE(sdw, rt766_sdca_id); ++ ++static int rt766_sdca_dev_suspend(struct device *dev) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ ++ if (!rt766->hw_init) ++ return 0; ++ ++ regcache_cache_only(rt766->regmap, true); ++ return 0; ++} ++ ++static int rt766_sdca_dev_system_suspend(struct device *dev) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ struct sdw_slave *slave = dev_to_sdw_dev(dev); ++ int ret1, ret2; ++ ++ if (!rt766->hw_init) ++ return 0; ++ ++ /* ++ * prevent new interrupts from being handled after the ++ * deferred work completes and before the parent disables ++ * interrupts on the link ++ */ ++ mutex_lock(&rt766->disable_irq_lock); ++ rt766->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK3, ++ SDW_SCP_SDCA_INTMASK_SDCA_16, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK4, ++ SDW_SCP_SDCA_INTMASK_SDCA_24, 0); ++ mutex_unlock(&rt766->disable_irq_lock); ++ ++ if (ret1 < 0 || ret2 < 0) { ++ /* log but don't prevent suspend from happening */ ++ dev_dbg(&slave->dev, "%s: could not disable SDCA interrupts\n:", __func__); ++ } ++ ++ return rt766_sdca_dev_suspend(dev); ++} ++ ++static int rt766_sdca_dev_resume(struct device *dev) ++{ ++ struct sdw_slave *slave = dev_to_sdw_dev(dev); ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ int ret; ++ ++ if (!rt766->first_hw_init) ++ return 0; ++ ++ if (!slave->unattach_request) { ++ mutex_lock(&rt766->disable_irq_lock); ++ if (rt766->disable_irq == true) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK3, SDW_SCP_SDCA_INTMASK_SDCA_16); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK4, SDW_SCP_SDCA_INTMASK_SDCA_24); ++ rt766->disable_irq = false; ++ } ++ mutex_unlock(&rt766->disable_irq_lock); ++ goto regmap_sync; ++ } ++ ++ ret = sdw_slave_wait_for_init(slave, RT766_PROBE_TIMEOUT); ++ if (ret) { ++ sdw_show_ping_status(slave->bus, true); ++ return ret; ++ } ++ ++regmap_sync: ++ regcache_cache_only(rt766->regmap, false); ++ ret = regcache_sync(rt766->regmap); ++ if (ret) { ++ regcache_cache_only(rt766->regmap, true); ++ regcache_mark_dirty(rt766->regmap); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops rt766_sdca_pm = { ++ SYSTEM_SLEEP_PM_OPS(rt766_sdca_dev_system_suspend, rt766_sdca_dev_resume) ++ RUNTIME_PM_OPS(rt766_sdca_dev_suspend, rt766_sdca_dev_resume, NULL) ++}; ++ ++static struct sdw_driver rt766_sdca_sdw_driver = { ++ .driver = { ++ .name = "rt766-sdca", ++ .pm = pm_ptr(&rt766_sdca_pm), ++ }, ++ .probe = rt766_sdca_sdw_probe, ++ .remove = rt766_sdca_sdw_remove, ++ .ops = &rt766_sdca_slave_ops, ++ .id_table = rt766_sdca_id, ++}; ++module_sdw_driver(rt766_sdca_sdw_driver); ++ ++MODULE_DESCRIPTION("ASoC RT766 SDCA SDW driver"); ++MODULE_AUTHOR("Shuming Fan "); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("SND_SOC_SDCA"); +diff --git a/sound/soc/codecs/rt766-sdca-sdw.h b/sound/soc/codecs/rt766-sdca-sdw.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca-sdw.h +@@ -0,0 +1,61 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * rt766-sdca-sdw.h -- RT766 SDCA ALSA SoC audio driver header ++ * ++ * Copyright(c) 2026 Realtek Semiconductor Corp. ++ */ ++ ++#ifndef __RT766_SDW_H__ ++#define __RT766_SDW_H__ ++ ++#include ++#include ++ ++static const struct reg_default rt766_sdca_defaults[] = { ++ /* 0x40400289 - 0x4040028a */ ++ { RT766_MUTE_REG(UAJ, USER_FU41, 1), 0x01 }, ++ { RT766_MUTE_REG(UAJ, USER_FU41, 2), 0x01 }, ++ /* 0x40400291 - 0x40400292 */ ++ { RT766_VOLUME_REG(UAJ, USER_FU41, 1), 0x0000 }, ++ { RT766_VOLUME_REG(UAJ, USER_FU41, 2), 0x0000 }, ++ /* 0x40400789 - 0x4040078a */ ++ { RT766_MUTE_REG(UAJ, USER_FU36, 1), 0x01 }, ++ { RT766_MUTE_REG(UAJ, USER_FU36, 2), 0x01 }, ++ /* 0x40400791 - 0x40400792 */ ++ { RT766_VOLUME_REG(UAJ, USER_FU36, 1), 0x0000 }, ++ { RT766_VOLUME_REG(UAJ, USER_FU36, 2), 0x0000 }, ++ { RT766_PDE_REQ_REG(UAJ, PDE47), 0x03 }, /* 0x40401408 */ ++ { RT766_PDE_REQ_REG(UAJ, PDE34), 0x03 }, /* 0x40401488 */ ++ { RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x40480080 */ ++ { RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x40480880 */ ++ /* 0x40600259 - 0x4060025a */ ++ { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1), 0xfe00 }, ++ { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2), 0xfe00 }, ++ { RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE), 0x00 }, /* 0x40600488 */ ++ ++ { RT766_PDE_REQ_REG(MIC, PDE11), 0x03 }, /* 0x40801508 */ ++ /* 0x40801809 - 0x4080180c */ ++ { RT766_MUTE_REG(MIC, USER_FU113, 1), 0x01 }, ++ { RT766_MUTE_REG(MIC, USER_FU113, 2), 0x01 }, ++ { RT766_MUTE_REG(MIC, USER_FU113, 3), 0x01 }, ++ { RT766_MUTE_REG(MIC, USER_FU113, 4), 0x01 }, ++ /* 0x40801811 - 0x40801814 */ ++ { RT766_VOLUME_REG(MIC, USER_FU113, 1), 0x0000 }, ++ { RT766_VOLUME_REG(MIC, USER_FU113, 2), 0x0000 }, ++ { RT766_VOLUME_REG(MIC, USER_FU113, 3), 0x0000 }, ++ { RT766_VOLUME_REG(MIC, USER_FU113, 4), 0x0000 }, ++ { RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x40880900 */ ++ ++ /* 0x41000189 - 0x4100018a */ ++ { RT766_MUTE_REG(AMP, USER_FU21, 1), 0x01 }, ++ { RT766_MUTE_REG(AMP, USER_FU21, 2), 0x01 }, ++ /* 0x41000191 - 0x41000192 */ ++ { RT766_VOLUME_REG(AMP, USER_FU21, 1), 0x0000 }, ++ { RT766_VOLUME_REG(AMP, USER_FU21, 2), 0x0000 }, ++ { RT766_PDE_REQ_REG(AMP, PDE23), 0x03 }, /* 0x41001988 */ ++ { RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER), 0x00 }, /* 0x41080200 */ ++ { RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x41081080 */ ++ ++}; ++ ++#endif /* __RT766_SDW_H__ */ +diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca.c +@@ -0,0 +1,1378 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// rt766-sdca.c -- rt766 SDCA ALSA SoC audio driver ++// ++// Copyright(c) 2026 Realtek Semiconductor Corp. ++// ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "rt766-sdca.h" ++#include "rt-sdw-common.h" ++ ++static int rt766_sdca_btn_detect(struct sdca_interrupt *interrupt) ++{ ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ unsigned char *buf = NULL; ++ unsigned int offset, owner, length; ++ unsigned int det_mode, idx, val; ++ int ret; ++ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE), ++ &det_mode); ++ if (ret < 0) ++ goto io_error; ++ ++ /* get current UMP message owner */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER), ++ &owner); ++ if (ret < 0) ++ goto io_error; ++ ++ /* if owner is device then there is no button event from device */ ++ if (owner == 1) ++ return 0; ++ ++ if (det_mode) { ++ /* read UMP message length */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH), ++ &length); ++ if (ret < 0) ++ goto _end_btn_det_; ++ ++ /* read UMP message offset */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGEOFFSET), ++ &offset); ++ if (ret < 0) ++ goto _end_btn_det_; ++ ++ buf = devm_kzalloc(&rt766->slave->dev, length, GFP_KERNEL); ++ if (!buf) { ++ dev_err(&rt766->slave->dev, "%s: alloc buf failed\n", __func__); ++ goto _end_btn_det_; ++ } ++ ++ for (idx = 0; idx < length; idx++) { ++ ret = regmap_read(rt766->regmap, ++ RT766_BUF_ADDR_HID1 + offset + idx, &val); ++ if (ret < 0) ++ goto _end_btn_det_; ++ buf[idx] = val & 0xff; ++ } ++ ++ if (rt766->hid) ++ hid_input_report(rt766->hid, HID_INPUT_REPORT, ++ buf, length, 1); ++ } ++ ++_end_btn_det_: ++ if (buf) ++ devm_kfree(&rt766->slave->dev, buf); ++ ++ /* Host is owner, so set back to device */ ++ if (owner == 0) { ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER), 0x01); ++ } ++ ++ return 0; ++ ++io_error: ++ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); ++ return ret; ++} ++ ++static irqreturn_t rt766_sdca_irq_btn_handler(int irq, void *data) ++{ ++ struct sdca_interrupt *interrupt = data; ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ ++ if (!rt766->hs_jack) ++ return IRQ_HANDLED; ++ ++ if (!rt766->component->card || !rt766->component->card->instantiated) ++ return IRQ_HANDLED; ++ ++ mutex_lock(&rt766->disable_irq_lock); ++ if (!rt766->disable_irq) ++ rt766_sdca_btn_detect(interrupt); ++ mutex_unlock(&rt766->disable_irq_lock); ++ return IRQ_HANDLED; ++} ++ ++static int rt766_sdca_headset_detect(struct rt766_sdca_priv *rt766) ++{ ++ unsigned int det_mode; ++ int ret; ++ ++ /* get detected_mode */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE), ++ &det_mode); ++ if (ret < 0) ++ goto io_error; ++ ++ switch (det_mode) { ++ case 0x00: ++ rt766->jack_type = 0; ++ break; ++ case 0x03: ++ rt766->jack_type = SND_JACK_HEADPHONE; ++ break; ++ case 0x05: ++ rt766->jack_type = SND_JACK_HEADSET; ++ break; ++ } ++ ++ /* write selected_mode */ ++ if (det_mode) { ++ ret = regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE), ++ det_mode); ++ if (ret < 0) ++ goto io_error; ++ } ++ ++ dev_dbg(&rt766->slave->dev, ++ "%s, detected_mode=0x%x\n", __func__, det_mode); ++ ++ return 0; ++ ++io_error: ++ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); ++ return ret; ++} ++ ++static irqreturn_t rt766_sdca_irq_jd_handler(int irq, void *data) ++{ ++ struct sdca_interrupt *interrupt = data; ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ ++ if (!rt766->hs_jack) ++ return IRQ_HANDLED; ++ ++ if (!rt766->component->card || !rt766->component->card->instantiated) ++ return IRQ_HANDLED; ++ ++ mutex_lock(&rt766->disable_irq_lock); ++ if (!rt766->disable_irq) ++ rt766_sdca_headset_detect(rt766); ++ mutex_unlock(&rt766->disable_irq_lock); ++ ++ dev_dbg(&rt766->slave->dev, ++ "in %s, jack_type=%d\n", __func__, rt766->jack_type); ++ ++ snd_soc_jack_report(rt766->hs_jack, rt766->jack_type, SND_JACK_HEADSET); ++ return IRQ_HANDLED; ++} ++ ++static void rt766_sdca_destroy_hid_device(struct sdca_interrupt *interrupt) ++{ ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ ++ hid_destroy_device(rt766->hid); ++} ++ ++static int rt766_sdca_irq_ctl(struct rt766_sdca_priv *rt766, ++ struct sdca_function_data *function, ++ struct snd_soc_component *component, ++ struct sdca_interrupt_info *info, ++ bool enabled) ++{ ++ struct device *dev = &rt766->slave->dev; ++ struct sdca_interrupt *interrupt; ++ struct sdca_control *control; ++ struct sdca_entity *entity; ++ irq_handler_t handler; ++ int i, j, irq, ret; ++ ++ for (i = 0; i < function->num_entities; i++) { ++ entity = &function->entities[i]; ++ ++ for (j = 0; j < entity->num_controls; j++) { ++ control = &entity->controls[j]; ++ irq = control->interrupt_position; ++ ++ switch (SDCA_CTL_TYPE(entity->type, control->sel)) { ++ case SDCA_CTL_TYPE_S(GE, DETECTED_MODE): ++ handler = rt766_sdca_irq_jd_handler; ++ break; ++ case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER): ++ handler = rt766_sdca_irq_btn_handler; ++ break; ++ default: ++ continue; ++ } ++ ++ interrupt = &info->irqs[irq]; ++ ++ if (enabled) { ++ ret = sdca_irq_data_populate(dev, rt766->regmap, component, ++ function, entity, control, ++ interrupt); ++ if (ret) ++ return ret; ++ ++ if (handler == rt766_sdca_irq_btn_handler) { ++ ret = sdca_add_hid_device(interrupt); ++ if (ret) ++ return ret; ++ ++ interrupt->free_priv = rt766_sdca_destroy_hid_device; ++ rt766->hid = interrupt->priv; ++ } ++ ++ interrupt->priv = rt766; ++ ret = sdca_irq_request(dev, info, irq, interrupt->name, ++ handler, interrupt); ++ if (ret) { ++ dev_err(dev, "failed to request irq %s: %d\n", ++ interrupt->name, ret); ++ sdca_irq_cleanup_late(dev, function, info); ++ return ret; ++ } ++ dev_dbg(dev, "Requesting IRQ %d InterruptName=%s\n", irq, interrupt->name); ++ } else { ++ sdca_irq_cleanup_late(dev, function, info); ++ dev_dbg(dev, "Freeing IRQ %d\n", irq); ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++static int rt766_sdca_set_jack_detect(struct snd_soc_component *component, ++ struct snd_soc_jack *hs_jack, void *data) ++{ ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int ret; ++ ++ if (!rt766->uaj_func_data) { ++ dev_err(&rt766->slave->dev, "The SDCA UAJ function is not supported.\n"); ++ return -EINVAL; ++ } ++ ++ rt766->hs_jack = hs_jack; ++ ++ if (!rt766->first_hw_init) ++ return 0; ++ ++ ret = pm_runtime_resume_and_get(component->dev); ++ if (ret < 0) { ++ if (ret != -EACCES) { ++ dev_err(component->dev, "%s: failed to resume %d\n", __func__, ret); ++ return ret; ++ } ++ ++ /* pm_runtime not enabled yet */ ++ dev_dbg(component->dev, "%s: skipping jack init for now\n", __func__); ++ return 0; ++ } ++ ++ /* disable interrupts if hs_jack is not set */ ++ if (!rt766->hs_jack) { ++ if (rt766->uaj_func_data) ++ rt766_sdca_irq_ctl(rt766, rt766->uaj_func_data, ++ rt766->component, rt766->irq_info, false); ++ ++ if (rt766->hid_func_data) ++ rt766_sdca_irq_ctl(rt766, rt766->hid_func_data, ++ rt766->component, rt766->irq_info, false); ++ } ++ ++ pm_runtime_put_autosuspend(component->dev); ++ ++ return 0; ++} ++ ++static int rt766_sdca_set_fu_ctl(struct rt766_sdca_priv *rt766, int func_num, int fu_num) ++{ ++ unsigned int fu01_reg, fu02_reg; ++ unsigned int ch_01, ch_02; ++ unsigned int ch_mute; ++ unsigned int fu_reg; ++ int err, i; ++ ++ switch (fu_num) { ++ case RT766_SDCA_ENT_USER_FU41: ++ ch_01 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_l_mute) ? 0x01 : 0x00; ++ ch_02 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_r_mute) ? 0x01 : 0x00; ++ break; ++ case RT766_SDCA_ENT_USER_FU36: ++ ch_01 = (rt766->fu36_dapm_mute || rt766->fu36_mixer_l_mute) ? 0x01 : 0x00; ++ ch_02 = (rt766->fu36_dapm_mute || rt766->fu36_mixer_r_mute) ? 0x01 : 0x00; ++ break; ++ case RT766_SDCA_ENT_USER_FU21: ++ ch_01 = (rt766->fu21_dapm_mute || rt766->fu21_mixer_l_mute) ? 0x01 : 0x00; ++ ch_02 = (rt766->fu21_dapm_mute || rt766->fu21_mixer_r_mute) ? 0x01 : 0x00; ++ break; ++ case RT766_SDCA_ENT_USER_FU113: ++ for (i = 0; i < ARRAY_SIZE(rt766->fu113_mixer_mute); i++) { ++ ch_mute = (rt766->fu113_dapm_mute || rt766->fu113_mixer_mute[i]) ? 0x01 : 0x00; ++ fu_reg = SDW_SDCA_CTL(func_num, fu_num, SDCA_CTL_FU_MUTE, 1) + i; ++ err = regmap_write(rt766->regmap, fu_reg, ch_mute); ++ if (err < 0) ++ return err; ++ } ++ return 0; ++ } ++ ++ fu01_reg = SDW_SDCA_CTL(func_num, fu_num, SDCA_CTL_FU_MUTE, 1); ++ fu02_reg = SDW_SDCA_CTL(func_num, fu_num, SDCA_CTL_FU_MUTE, 2); ++ err = regmap_write(rt766->regmap, fu01_reg, ch_01); ++ if (err < 0) ++ return err; ++ err = regmap_write(rt766->regmap, fu02_reg, ch_02); ++ if (err < 0) ++ return err; ++ ++ return 0; ++} ++ ++static int rt766_sdca_fu41_playback_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = !rt766->fu41_mixer_l_mute; ++ ucontrol->value.integer.value[1] = !rt766->fu41_mixer_r_mute; ++ return 0; ++} ++ ++static int rt766_sdca_fu41_playback_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err; ++ ++ if (rt766->fu41_mixer_l_mute == !ucontrol->value.integer.value[0] && ++ rt766->fu41_mixer_r_mute == !ucontrol->value.integer.value[1]) ++ return 0; ++ ++ rt766->fu41_mixer_l_mute = !ucontrol->value.integer.value[0]; ++ rt766->fu41_mixer_r_mute = !ucontrol->value.integer.value[1]; ++ ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU41); ++ if (err < 0) ++ return err; ++ ++ return 1; ++} ++ ++static int rt766_sdca_fu36_capture_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = !rt766->fu36_mixer_l_mute; ++ ucontrol->value.integer.value[1] = !rt766->fu36_mixer_r_mute; ++ return 0; ++} ++ ++static int rt766_sdca_fu36_capture_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err; ++ ++ if (rt766->fu36_mixer_l_mute == !ucontrol->value.integer.value[0] && ++ rt766->fu36_mixer_r_mute == !ucontrol->value.integer.value[1]) ++ return 0; ++ ++ rt766->fu36_mixer_l_mute = !ucontrol->value.integer.value[0]; ++ rt766->fu36_mixer_r_mute = !ucontrol->value.integer.value[1]; ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU36); ++ if (err < 0) ++ return err; ++ ++ return 1; ++} ++ ++static int rt766_sdca_fu21_playback_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = !rt766->fu21_mixer_l_mute; ++ ucontrol->value.integer.value[1] = !rt766->fu21_mixer_r_mute; ++ return 0; ++} ++ ++static int rt766_sdca_fu21_playback_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err; ++ ++ if (rt766->fu21_mixer_l_mute == !ucontrol->value.integer.value[0] && ++ rt766->fu21_mixer_r_mute == !ucontrol->value.integer.value[1]) ++ return 0; ++ ++ rt766->fu21_mixer_l_mute = !ucontrol->value.integer.value[0]; ++ rt766->fu21_mixer_r_mute = !ucontrol->value.integer.value[1]; ++ ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_USER_FU21); ++ if (err < 0) ++ return err; ++ ++ return 1; ++} ++ ++static int rt766_sdca_fu113_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu113_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_USER_FU113); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu113_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_USER_FU113); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_dmic_set_gain_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct rt_sdca_dmic_kctrl_priv *p = ++ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value; ++ const unsigned int interval_offset = 0xc0; ++ unsigned int regvalue, ctl, i; ++ ++ /* check all channels */ ++ for (i = 0; i < p->count; i++) { ++ regmap_read(rt766->regmap, p->reg_base + i, ®value); ++ ctl = p->max - (((0x1e00 - regvalue) & 0xffff) / interval_offset); ++ ++ ucontrol->value.integer.value[i] = ctl; ++ } ++ ++ return 0; ++} ++ ++static int rt766_sdca_dmic_set_gain_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt_sdca_dmic_kctrl_priv *p = ++ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value; ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ const unsigned int interval_offset = 0xc0; ++ unsigned int gain_val[4]; ++ unsigned int i, changed = 0; ++ unsigned int regvalue[4]; ++ int err; ++ ++ /* check all channels */ ++ for (i = 0; i < p->count; i++) { ++ regmap_read(rt766->regmap, p->reg_base + i, ®value[i]); ++ ++ gain_val[i] = ucontrol->value.integer.value[i]; ++ if (gain_val[i] > p->max) ++ gain_val[i] = p->max; ++ ++ gain_val[i] = 0x1e00 - ((p->max - gain_val[i]) * interval_offset); ++ gain_val[i] &= 0xffff; ++ ++ if (regvalue[i] != gain_val[i]) ++ changed = 1; ++ } ++ ++ if (!changed) ++ return 0; ++ ++ for (i = 0; i < p->count; i++) { ++ err = regmap_write(rt766->regmap, p->reg_base + i, gain_val[i]); ++ if (err < 0) ++ dev_err(&rt766->slave->dev, "0x%08x can't be set\n", p->reg_base + i); ++ } ++ ++ return changed; ++} ++ ++static int rt766_sdca_dmic_fu113_capture_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ unsigned int i; ++ ++ for (i = 0; i < 4; i++) ++ ucontrol->value.integer.value[i] = !rt766->fu113_mixer_mute[i]; ++ return 0; ++} ++ ++static int rt766_sdca_dmic_fu113_capture_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err, changed = 0, i; ++ ++ for (i = 0; i < 4; i++) { ++ if (rt766->fu113_mixer_mute[i] != !ucontrol->value.integer.value[i]) ++ changed = 1; ++ rt766->fu113_mixer_mute[i] = !ucontrol->value.integer.value[i]; ++ } ++ ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_USER_FU113); ++ if (err < 0) ++ return err; ++ return changed; ++} ++ ++static int rt766_sdca_fu41_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu41_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU41); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu41_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU41); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_pde_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event, int func_num, int pde_num, const char *pde_ent) ++{ ++ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct sdca_function_data *func_data; ++ unsigned char ps0 = 0x0, ps3 = 0x3; ++ const struct sdca_entity *entity; ++ unsigned int pde_req_reg; ++ int from_ps, to_ps; ++ int ret; ++ ++ pde_req_reg = SDW_SDCA_CTL(func_num, pde_num, SDCA_CTL_PDE_REQUESTED_PS, 0); ++ ++ switch (func_num) { ++ case RT766_FUNC_NUM_UAJ: ++ func_data = rt766->uaj_func_data; ++ break; ++ case RT766_FUNC_NUM_AMP: ++ func_data = rt766->sa_func_data; ++ break; ++ case RT766_FUNC_NUM_MIC: ++ func_data = rt766->sm_func_data; ++ break; ++ default: ++ dev_err(component->dev, "%s: unsupported func_num %d\n", ++ __func__, func_num); ++ return -EINVAL; ++ } ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ regmap_write(rt766->regmap, pde_req_reg, ps0); ++ from_ps = ps3; ++ to_ps = ps0; ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ regmap_write(rt766->regmap, pde_req_reg, ps3); ++ from_ps = ps0; ++ to_ps = ps3; ++ break; ++ } ++ ++ entity = sdca_find_entity_by_label(func_data, pde_ent); ++ if (!entity) { ++ dev_err(component->dev, "%s: failed to find entity %s\n", ++ __func__, pde_ent); ++ return -EINVAL; ++ } ++ ++ ret = sdca_asoc_pde_poll_actual_ps(rt766->regmap, ++ func_num, ++ pde_num, ++ from_ps, to_ps, ++ entity->pde.max_delay, ++ entity->pde.num_max_delay); ++ if (ret) ++ dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n", ++ __func__, from_ps, to_ps, ret); ++ ++ return ret; ++} ++ ++static int rt766_sdca_pde47_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_PDE47, "PDE 47"); ++} ++ ++static int rt766_sdca_fu36_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu36_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU36); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu36_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU36); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_pde34_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_PDE34, "PDE 34"); ++} ++ ++static int rt766_sdca_fu21_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu21_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_USER_FU21); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu21_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_USER_FU21); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_pde23_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_PDE23, "PDE 23"); ++} ++ ++static int rt766_sdca_pde11_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_PDE11, "PDE 11"); ++} ++ ++static int rt766_dmic_fu_info(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_info *uinfo) ++{ ++ struct rt_sdca_dmic_kctrl_priv *p = ++ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value; ++ ++ if (p->max == 1) ++ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; ++ else ++ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; ++ uinfo->count = p->count; ++ uinfo->value.integer.min = 0; ++ uinfo->value.integer.max = p->max; ++ return 0; ++} ++ ++static const char * const rt766_rx_data_ch_select[] = { ++ "L,R", ++ "R,L", ++ "L,L", ++ "R,R", ++ "L,L+R", ++ "R,L+R", ++ "L+R,L", ++ "L+R,R", ++ "L+R,L+R", ++}; ++ ++static SOC_ENUM_SINGLE_DECL(rt766_rx_data_ch_enum, ++ RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER), 0, ++ rt766_rx_data_ch_select); ++ ++static const DECLARE_TLV_DB_SCALE(hp_vol_tlv, -9525, 75, 0); ++static const DECLARE_TLV_DB_SCALE(spk_vol_tlv, -6525, 75, 0); ++static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, -1725, 75, 0); ++static const DECLARE_TLV_DB_SCALE(boost_vol_tlv, -200, 200, 0); ++ ++#define RT766_P75DB_STEP 0xC0 /* 0.75 dB in Q7.8 format */ ++#define RT766_2DB_STEP 0x200 /* 2 dB in Q7.8 format */ ++#define RT766_HP_VOL_MIN (-127) /* -95.25 dB / 0.75 dB step */ ++#define RT766_HS_VOL_MIN (-23) /* -17.25 dB / 0.75 dB step */ ++#define RT766_HS_BOOST_VOL_MIN (-1) /* -2 dB / 2 dB step */ ++#define RT766_SPK_VOL_MIN (-87) /* -65.25 dB / 0.75 dB step */ ++#define RT766_P_VOL_MAX 0 /* 0 dB / 0.75 dB step */ ++#define RT766_HS_VOL_MAX 40 /* 30 dB / 0.75 dB step */ ++#define RT766_HS_BOOST_VOL_MAX 20 /* 40 dB / 2 dB step */ ++ ++static const struct snd_kcontrol_new rt766_sdca_controls[] = { ++ SOC_DOUBLE_EXT("FU41 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0, ++ rt766_sdca_fu41_playback_get, rt766_sdca_fu41_playback_put), ++ SDCA_DOUBLE_Q78_TLV("FU41 Playback Volume", ++ RT766_VOLUME_REG(UAJ, USER_FU41, 1), ++ RT766_VOLUME_REG(UAJ, USER_FU41, 2), ++ RT766_HP_VOL_MIN, RT766_P_VOL_MAX, RT766_P75DB_STEP, hp_vol_tlv), ++ SOC_DOUBLE_EXT("FU36 Capture Switch", SND_SOC_NOPM, 0, 1, 1, 0, ++ rt766_sdca_fu36_capture_get, rt766_sdca_fu36_capture_put), ++ SDCA_DOUBLE_Q78_TLV("FU36 Capture Volume", ++ RT766_VOLUME_REG(UAJ, USER_FU36, 1), ++ RT766_VOLUME_REG(UAJ, USER_FU36, 2), ++ RT766_HS_VOL_MIN, RT766_HS_VOL_MAX, RT766_P75DB_STEP, mic_vol_tlv), ++ SDCA_DOUBLE_Q78_TLV("FU33 Boost Volume", ++ RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1), ++ RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2), ++ RT766_HS_BOOST_VOL_MIN, RT766_HS_BOOST_VOL_MAX, RT766_2DB_STEP, boost_vol_tlv), ++ ++ SOC_DOUBLE_EXT("FU21 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0, ++ rt766_sdca_fu21_playback_get, rt766_sdca_fu21_playback_put), ++ SDCA_DOUBLE_Q78_TLV("FU21 Playback Volume", ++ RT766_VOLUME_REG(AMP, USER_FU21, 1), ++ RT766_VOLUME_REG(AMP, USER_FU21, 2), ++ RT766_SPK_VOL_MIN, RT766_P_VOL_MAX, RT766_P75DB_STEP, spk_vol_tlv), ++ SOC_ENUM("RX Channel Select", rt766_rx_data_ch_enum), ++ ++ RT_SDCA_FU_CTRL("FU113 Capture Switch", ++ RT766_MUTE_REG(MIC, USER_FU113, 1), 1, 1, 4, rt766_dmic_fu_info, ++ rt766_sdca_dmic_fu113_capture_get, rt766_sdca_dmic_fu113_capture_put), ++ RT_SDCA_EXT_TLV("FU113 Capture Volume", ++ RT766_VOLUME_REG(MIC, USER_FU113, 1), ++ rt766_sdca_dmic_set_gain_get, rt766_sdca_dmic_set_gain_put, ++ 4, 0x3f, mic_vol_tlv, rt766_dmic_fu_info), ++}; ++ ++static const struct snd_soc_dapm_widget rt766_sdca_dapm_widgets[] = { ++ /* UAJ */ ++ SND_SOC_DAPM_OUTPUT("HP"), ++ SND_SOC_DAPM_INPUT("MIC2"), ++ SND_SOC_DAPM_SUPPLY("PDE 47", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde47_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_SUPPLY("PDE 34", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde34_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_DAC_E("FU 41", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu41_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_ADC_E("FU 36", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu36_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_AIF_IN("DP3RX", "DP3 Playback", 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("DP12TX", "DP12 Capture", 0, SND_SOC_NOPM, 0, 0), ++ ++ /* AMP */ ++ SND_SOC_DAPM_OUTPUT("SPOL"), ++ SND_SOC_DAPM_OUTPUT("SPOR"), ++ SND_SOC_DAPM_DAC_E("FU 21", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu21_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_SUPPLY("PDE 23", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde23_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_AIF_IN("DP1RX", "DP1 Playback", 0, SND_SOC_NOPM, 0, 0), ++ ++ /* DMIC */ ++ SND_SOC_DAPM_INPUT("DMIC1"), ++ SND_SOC_DAPM_INPUT("DMIC2"), ++ SND_SOC_DAPM_SUPPLY("PDE 11", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde11_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_ADC_E("FU 113", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu113_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_AIF_OUT("DP8TX", "DP8 Capture", 0, SND_SOC_NOPM, 0, 0), ++}; ++ ++static const struct snd_soc_dapm_route rt766_sdca_audio_map[] = { ++ { "FU 41", NULL, "DP3RX" }, ++ { "DP12TX", NULL, "FU 36" }, ++ { "FU 36", NULL, "PDE 34" }, ++ { "FU 36", NULL, "MIC2" }, ++ { "HP", NULL, "PDE 47" }, ++ { "HP", NULL, "FU 41" }, ++ ++ { "FU 21", NULL, "DP1RX" }, ++ { "FU 21", NULL, "PDE 23" }, ++ { "SPOL", NULL, "FU 21" }, ++ { "SPOR", NULL, "FU 21" }, ++ ++ {"DP8TX", NULL, "FU 113"}, ++ {"FU 113", NULL, "PDE 11"}, ++ {"FU 113", NULL, "DMIC1"}, ++ {"FU 113", NULL, "DMIC2"}, ++}; ++ ++static int rt766_sdca_probe(struct snd_soc_component *component) ++{ ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct device *dev = &rt766->slave->dev; ++ int ret; ++ ++ rt766->component = component; ++ ++ ret = pm_runtime_resume(component->dev); ++ if (ret < 0 && ret != -EACCES) ++ return ret; ++ ++ if (rt766->uaj_func_data) { ++ dev_dbg(dev, "%s : irq %d\n", __func__, rt766->slave->irq); ++ ++ rt766->irq_info = devm_sdca_irq_allocate(dev, rt766->regmap, rt766->slave->irq); ++ if (IS_ERR(rt766->irq_info)) ++ return PTR_ERR(rt766->irq_info); ++ ++ ret = rt766_sdca_irq_ctl(rt766, rt766->uaj_func_data, ++ component, rt766->irq_info, true); ++ if (ret < 0) { ++ dev_err(dev, "Failed to request UAJ SDCA IRQ: %d\n", ret); ++ return ret; ++ } ++ ++ if (rt766->hid_func_data) { ++ ret = rt766_sdca_irq_ctl(rt766, rt766->hid_func_data, ++ component, rt766->irq_info, true); ++ if (ret < 0) { ++ dev_err(dev, "Failed to request HID SDCA IRQ: %d\n", ret); ++ return ret; ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++static void rt766_sdca_remove(struct snd_soc_component *component) ++{ ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ sdca_irq_cleanup_late(component->dev, rt766->uaj_func_data, rt766->irq_info); ++ sdca_irq_cleanup_late(component->dev, rt766->hid_func_data, rt766->irq_info); ++} ++ ++static const struct snd_soc_component_driver soc_sdca_dev_rt766 = { ++ .probe = rt766_sdca_probe, ++ .remove = rt766_sdca_remove, ++ .controls = rt766_sdca_controls, ++ .num_controls = ARRAY_SIZE(rt766_sdca_controls), ++ .dapm_widgets = rt766_sdca_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(rt766_sdca_dapm_widgets), ++ .dapm_routes = rt766_sdca_audio_map, ++ .num_dapm_routes = ARRAY_SIZE(rt766_sdca_audio_map), ++ .set_jack = rt766_sdca_set_jack_detect, ++ .endianness = 1, ++}; ++ ++static int rt766_sdca_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, ++ int direction) ++{ ++ snd_soc_dai_dma_data_set(dai, direction, sdw_stream); ++ ++ return 0; ++} ++ ++static void rt766_sdca_shutdown(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ snd_soc_dai_set_dma_data(dai, substream, NULL); ++} ++ ++static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_component *component = dai->component; ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct sdw_stream_config stream_config; ++ struct sdw_port_config port_config; ++ enum sdw_data_direction direction; ++ struct sdw_stream_runtime *sdw_stream; ++ unsigned int sampling_rate; ++ int retval, port; ++ ++ dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); ++ sdw_stream = snd_soc_dai_get_dma_data(dai, substream); ++ ++ if (!sdw_stream) ++ return -EINVAL; ++ ++ if (!rt766->slave) ++ return -EINVAL; ++ ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); ++ ++ /* SoundWire specific configuration */ ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ direction = SDW_DATA_DIR_RX; ++ if (dai->id == RT766_AIF1) ++ port = 3; ++ else if (dai->id == RT766_AIF2) ++ port = 1; ++ else ++ return -EINVAL; ++ } else { ++ direction = SDW_DATA_DIR_TX; ++ if (dai->id == RT766_AIF1) ++ port = 12; ++ else if (dai->id == RT766_AIF3) ++ port = 8; ++ else ++ return -EINVAL; ++ } ++ ++ port_config.num = port; ++ retval = sdw_stream_add_slave(rt766->slave, &stream_config, ++ &port_config, 1, sdw_stream); ++ if (retval) { ++ dev_err(dai->dev, "%s: Unable to configure port\n", __func__); ++ return retval; ++ } ++ ++ if (params_channels(params) > 16) { ++ dev_err(component->dev, "%s: Unsupported channels %d\n", ++ __func__, params_channels(params)); ++ return -EINVAL; ++ } ++ ++ /* sampling rate configuration */ ++ switch (params_rate(params)) { ++ case 44100: ++ sampling_rate = RT766_SDCA_RATE_44100HZ; ++ break; ++ case 48000: ++ sampling_rate = RT766_SDCA_RATE_48000HZ; ++ break; ++ case 96000: ++ sampling_rate = RT766_SDCA_RATE_96000HZ; ++ break; ++ case 192000: ++ sampling_rate = RT766_SDCA_RATE_192000HZ; ++ break; ++ default: ++ dev_err(component->dev, "%s: Rate %d is not supported\n", ++ __func__, params_rate(params)); ++ return -EINVAL; ++ } ++ ++ /* set sampling frequency */ ++ switch (dai->id) { ++ case RT766_AIF1: ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ break; ++ case RT766_AIF2: ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ break; ++ case RT766_AIF3: ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ break; ++ default: ++ dev_err(component->dev, "%s: Wrong DAI id\n", __func__); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int rt766_sdca_pcm_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_component *component = dai->component; ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct sdw_stream_runtime *sdw_stream = ++ snd_soc_dai_get_dma_data(dai, substream); ++ ++ if (!rt766->slave) ++ return -EINVAL; ++ ++ sdw_stream_remove_slave(rt766->slave, sdw_stream); ++ return 0; ++} ++ ++#define RT766_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \ ++ SNDRV_PCM_RATE_192000) ++#define RT766_DAC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) ++#define RT766_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ ++ SNDRV_PCM_FMTBIT_S32_LE) ++ ++static const struct snd_soc_dai_ops rt766_sdca_ops = { ++ .hw_params = rt766_sdca_pcm_hw_params, ++ .hw_free = rt766_sdca_pcm_hw_free, ++ .set_stream = rt766_sdca_set_sdw_stream, ++ .shutdown = rt766_sdca_shutdown, ++}; ++ ++static struct snd_soc_dai_driver rt766_sdca_dai[] = { ++ { ++ .name = "rt766-sdca-aif1", ++ .id = RT766_AIF1, ++ .playback = { ++ .stream_name = "DP3 Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_DAC_FORMATS, ++ }, ++ .capture = { ++ .stream_name = "DP12 Capture", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_ADC_FORMATS, ++ }, ++ .ops = &rt766_sdca_ops, ++ .symmetric_rate = 1, ++ }, ++ { ++ .name = "rt766-sdca-aif2", ++ .id = RT766_AIF2, ++ .playback = { ++ .stream_name = "DP1 Playback", ++ .channels_min = 1, ++ .channels_max = 4, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_DAC_FORMATS, ++ }, ++ .ops = &rt766_sdca_ops, ++ }, ++ { ++ .name = "rt766-sdca-aif3", ++ .id = RT766_AIF3, ++ .capture = { ++ .stream_name = "DP8 Capture", ++ .channels_min = 1, ++ .channels_max = 4, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_ADC_FORMATS, ++ }, ++ .ops = &rt766_sdca_ops, ++ } ++}; ++ ++static unsigned int rt766_find_dt_rates(struct device *dev, struct sdca_function_data *function, ++ const char *label) ++{ ++ struct snd_soc_pcm_stream stream; ++ struct sdca_entity *entity; ++ int i, ret; ++ ++ for (i = 0; i < function->num_entities; i++) { ++ entity = &function->entities[i]; ++ ++ if (strcmp(entity->label, label)) ++ continue; ++ ++ /* Can't check earlier as only terminals have an iot member. */ ++ if (!entity->iot.is_dataport) ++ continue; ++ ++ ret = sdca_asoc_populate_rate_format(dev, function, entity, &stream); ++ if (ret < 0) { ++ dev_dbg(dev, "%s: failed to parse rates for entity %s\n", ++ __func__, entity->label); ++ return 0; ++ } ++ ++ dev_dbg(dev, "%s: %s supports rates 0x%08x\n", __func__, entity->label, stream.rates); ++ } ++ ++ return stream.rates; ++} ++ ++int rt766_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave *slave) ++{ ++ struct sdca_function_data *func_data_ptr; ++ struct snd_soc_dai_driver *dai_drv; ++ struct rt766_sdca_priv *rt766; ++ unsigned int rates; ++ int ret; ++ int i; ++ ++ rt766 = devm_kzalloc(dev, sizeof(*rt766), GFP_KERNEL); ++ if (!rt766) ++ return -ENOMEM; ++ ++ dev_set_drvdata(dev, rt766); ++ rt766->slave = slave; ++ rt766->regmap = regmap; ++ ++ regcache_cache_only(rt766->regmap, true); ++ ++ ret = devm_mutex_init(dev, &rt766->disable_irq_lock); ++ if (ret < 0) { ++ dev_err(dev, "Failed to initialize mutex\n"); ++ return ret; ++ } ++ ++ /* ++ * Mark hw_init to false ++ * HW init will be performed when device reports present ++ */ ++ rt766->hw_init = false; ++ rt766->first_hw_init = false; ++ rt766->fu41_dapm_mute = true; ++ rt766->fu41_mixer_l_mute = rt766->fu41_mixer_r_mute = false; ++ rt766->fu36_dapm_mute = true; ++ rt766->fu36_mixer_l_mute = rt766->fu36_mixer_r_mute = true; ++ rt766->fu21_dapm_mute = true; ++ rt766->fu21_mixer_l_mute = rt766->fu21_mixer_r_mute = false; ++ rt766->fu113_dapm_mute = true; ++ rt766->fu113_mixer_mute[0] = rt766->fu113_mixer_mute[1] = ++ rt766->fu113_mixer_mute[2] = rt766->fu113_mixer_mute[3] = true; ++ ++ dai_drv = devm_kzalloc(dev, sizeof(struct snd_soc_dai_driver) * ARRAY_SIZE(rt766_sdca_dai), GFP_KERNEL); ++ if (!dai_drv) { ++ dev_err(dev, "Failed to allocate memory for DAI driver\n"); ++ ret = -ENOMEM; ++ goto _sdw_init_err_; ++ } ++ memcpy(dai_drv, rt766_sdca_dai, sizeof(struct snd_soc_dai_driver) * ARRAY_SIZE(rt766_sdca_dai)); ++ ++ /* get SDCA function data */ ++ dev_dbg(dev, "SDCA functions found: %d", slave->sdca_data.num_functions); ++ for (i = 0; i < slave->sdca_data.num_functions; i++) { ++ func_data_ptr = devm_kzalloc(dev, sizeof(*func_data_ptr), GFP_KERNEL); ++ if (!func_data_ptr) { ++ dev_err(dev, "Failed to allocate memory for function data\n"); ++ ret = -ENOMEM; ++ goto _free_dai_drv_; ++ } ++ ++ func_data_ptr->desc = &slave->sdca_data.function[i]; ++ ret = sdca_parse_function(dev, func_data_ptr); ++ if (ret) { ++ devm_kfree(dev, func_data_ptr); ++ goto _free_dai_drv_; ++ } ++ dev_dbg(dev, "Function type=%d, num_entities=%d", ++ slave->sdca_data.function[i].type, func_data_ptr->num_entities); ++ ++ switch (slave->sdca_data.function[i].type) { ++ case SDCA_FUNCTION_TYPE_UAJ: ++ rt766->uaj_func_data = func_data_ptr; ++ /* ++ * Some machines may only support a subset of the sample rates supported by the codec. ++ * Therefore, we need to parse the supported sample rates from the DisCo table and ++ * configure them in the DAI. If the DisCo table does not provide sample rate information, ++ * we will fall back to the default supported rates defined in the codec driver. ++ */ ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 41"); ++ if (rates) ++ dai_drv[RT766_DAI_UAJ].playback.rates = rates; ++ ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 36"); ++ if (rates) ++ dai_drv[RT766_DAI_UAJ].capture.rates = rates; ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_AMP: ++ rt766->sa_func_data = func_data_ptr; ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 21"); ++ if (rates) ++ dai_drv[RT766_DAI_AMP].playback.rates = rates; ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_MIC: ++ rt766->sm_func_data = func_data_ptr; ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 113"); ++ if (rates) ++ dai_drv[RT766_DAI_MIC].capture.rates = rates; ++ break; ++ case SDCA_FUNCTION_TYPE_HID: ++ rt766->hid_func_data = func_data_ptr; ++ break; ++ default: ++ dev_dbg(dev, "Unexpected SDCA function type found: %d", ++ slave->sdca_data.function[i].type); ++ } ++ } ++ ++ ret = devm_snd_soc_register_component(dev, ++ &soc_sdca_dev_rt766, dai_drv, ARRAY_SIZE(rt766_sdca_dai)); ++ if (ret < 0) ++ goto _free_dai_drv_; ++ ++ /* set autosuspend parameters */ ++ pm_runtime_set_autosuspend_delay(dev, 3000); ++ pm_runtime_use_autosuspend(dev); ++ ++ /* make sure the device does not suspend immediately */ ++ pm_runtime_mark_last_busy(dev); ++ ++ pm_runtime_enable(dev); ++ dev_dbg(dev, "%s\n", __func__); ++ return 0; ++ ++_free_dai_drv_: ++ if (dai_drv) ++ devm_kfree(dev, dai_drv); ++ ++_sdw_init_err_: ++ return ret; ++} ++ ++static int rt766_func_initialize(struct rt766_sdca_priv *rt766, struct sdca_function_data *func_data) ++{ ++ struct device *dev = &rt766->slave->dev; ++ unsigned int func_status_reg; ++ unsigned int func_status; ++ int ret; ++ ++ switch (func_data->desc->type) { ++ case SDCA_FUNCTION_TYPE_UAJ: ++ func_status_reg = RT766_FUNC_STATUS_REG(UAJ); ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_AMP: ++ func_status_reg = RT766_FUNC_STATUS_REG(AMP); ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_MIC: ++ func_status_reg = RT766_FUNC_STATUS_REG(MIC); ++ break; ++ case SDCA_FUNCTION_TYPE_HID: ++ func_status_reg = RT766_FUNC_STATUS_REG(HID); ++ break; ++ default: ++ dev_dbg(dev, "Unexpected SDCA function type found: %d", ++ func_data->desc->type); ++ return -EINVAL; ++ } ++ ++ regmap_read(rt766->regmap, func_status_reg, &func_status); ++ dev_dbg(dev, "%s, %s func_status=0x%x\n", __func__, func_data->desc->name, func_status); ++ ++ if ((func_status & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) || (!rt766->first_hw_init)) { ++ ret = sdca_regmap_write_init(dev, rt766->regmap, func_data); ++ if (ret) { ++ dev_err(dev, "%s initialization table update failed\n", func_data->desc->name); ++ goto _func_init_err_; ++ } ++ ++ regmap_write(rt766->regmap, func_status_reg, ++ SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION); ++ } ++ ++ return 0; ++ ++_func_init_err_: ++ dev_err(dev, "%s: %s init writes failed, err=%d", __func__, func_data->desc->name, ret); ++ return ret; ++} ++ ++int rt766_sdca_io_init(struct device *dev, struct sdw_slave *slave) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ unsigned int val; ++ ++ rt766->disable_irq = false; ++ ++ if (rt766->hw_init) ++ return 0; ++ ++ regcache_cache_only(rt766->regmap, false); ++ if (rt766->first_hw_init) { ++ regcache_cache_bypass(rt766->regmap, true); ++ } else { ++ /* ++ * PM runtime status is marked as 'active' only when a Slave reports as Attached ++ */ ++ ++ /* update count of parent 'active' children */ ++ pm_runtime_set_active(&slave->dev); ++ } ++ ++ pm_runtime_get_noresume(&slave->dev); ++ ++ regmap_read(rt766->regmap, RT766_BOND_LATCH_ID, &val); ++ dev_dbg(&slave->dev, "%s bond ID=0x%x (%s)\n", __func__, val, (val == 0x1) ? "RT767" : "RT766"); ++ ++ /* check function status and initialize if needed */ ++ if (rt766->uaj_func_data) ++ rt766_func_initialize(rt766, rt766->uaj_func_data); ++ if (rt766->sa_func_data) ++ rt766_func_initialize(rt766, rt766->sa_func_data); ++ if (rt766->sm_func_data) ++ rt766_func_initialize(rt766, rt766->sm_func_data); ++ if (rt766->hid_func_data) ++ rt766_func_initialize(rt766, rt766->hid_func_data); ++ ++ if (rt766->first_hw_init) { ++ regcache_cache_bypass(rt766->regmap, false); ++ regcache_mark_dirty(rt766->regmap); ++ } else { ++ rt766->first_hw_init = true; ++ } ++ ++ /* Mark Slave initialization complete */ ++ rt766->hw_init = true; ++ ++ dev_dbg(&slave->dev, "%s hw_init complete\n", __func__); ++ ++ pm_runtime_put_autosuspend(&slave->dev); ++ ++ return 0; ++} ++ ++MODULE_DESCRIPTION("ASoC RT766 SDCA SDW driver"); ++MODULE_AUTHOR("Shuming Fan "); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("SND_SOC_SDCA"); +diff --git a/sound/soc/codecs/rt766-sdca.h b/sound/soc/codecs/rt766-sdca.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca.h +@@ -0,0 +1,140 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * rt766-sdca.h -- RT766 SDCA ALSA SoC audio driver header ++ * ++ * Copyright(c) 2026 Realtek Semiconductor Corp. ++ */ ++ ++#ifndef __RT766_H__ ++#define __RT766_H__ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++struct rt766_sdca_priv { ++ struct regmap *regmap; ++ struct snd_soc_component *component; ++ struct sdw_slave *slave; ++ bool hw_init; ++ bool first_hw_init; ++ struct snd_soc_jack *hs_jack; ++ struct mutex disable_irq_lock; /* SDCA irq lock protection */ ++ bool disable_irq; ++ int jack_type; ++ bool fu41_dapm_mute; ++ bool fu41_mixer_l_mute; ++ bool fu41_mixer_r_mute; ++ bool fu113_dapm_mute; ++ bool fu113_mixer_mute[4]; ++ bool fu21_dapm_mute; ++ bool fu21_mixer_l_mute; ++ bool fu21_mixer_r_mute; ++ bool fu36_dapm_mute; ++ bool fu36_mixer_l_mute; ++ bool fu36_mixer_r_mute; ++ struct sdca_function_data *uaj_func_data; ++ struct sdca_function_data *sm_func_data; ++ struct sdca_function_data *sa_func_data; ++ struct sdca_function_data *hid_func_data; ++ struct sdca_interrupt_info *irq_info; ++ struct hid_device *hid; ++}; ++ ++/* vendor registers */ ++#define RT766_VERSION_ID 0xc404 ++#define RT766_DEV_ID1 0xc405 ++#define RT766_DEV_ID0 0xc406 ++#define RT766_BOND_LATCH_ID 0xc407 ++ ++#define RT766_HP_POWER_STATE 0x1000004 ++#define RT766_HP_FSM_CTL2_1 0x100000d ++ ++/* MCU Patch address */ ++#define RT766_MCU_PATCH_ADDR1_START 0x10010000 ++#define RT766_MCU_PATCH_ADDR1_END 0x10011fff ++#define RT766_MCU_PATCH_ADDR2_START 0x10020000 ++#define RT766_MCU_PATCH_ADDR2_END 0x10023fff ++ ++/* Buffer address for HID */ ++#define RT766_BUF_ADDR_HID1 0x44030000 ++#define RT766_BUF_ADDR_HID2 0x44030020 ++ ++/* SDCA (Channel) */ ++#define RT766_CH_1 0x01 ++#define RT766_CH_2 0x02 ++#define RT766_CH_3 0x03 ++#define RT766_CH_4 0x04 ++ ++/* RT766 SDCA Control - function number */ ++#define RT766_FUNC_NUM_UAJ 0x01 ++#define RT766_FUNC_NUM_MIC 0x02 ++#define RT766_FUNC_NUM_HID 0x03 ++#define RT766_FUNC_NUM_AMP 0x04 ++ ++/* RT766 SDCA entity */ ++#define RT766_SDCA_ENT_0 0x00 ++#define RT766_SDCA_ENT_HID101 0x01 ++#define RT766_SDCA_ENT_GE49 0x49 ++#define RT766_SDCA_ENT_USER_FU41 0x05 ++#define RT766_SDCA_ENT_USER_FU36 0x0f ++#define RT766_SDCA_ENT_USER_FU21 0x03 ++#define RT766_SDCA_ENT_USER_FU113 0x30 ++#define RT766_SDCA_ENT_PDE23 0x33 ++#define RT766_SDCA_ENT_PDE47 0x28 ++#define RT766_SDCA_ENT_PDE11 0x2a ++#define RT766_SDCA_ENT_PDE34 0x29 ++#define RT766_SDCA_ENT_CS41 0x01 ++#define RT766_SDCA_ENT_CS36 0x11 ++#define RT766_SDCA_ENT_CS113 0x12 ++#define RT766_SDCA_ENT_CS21 0x21 ++#define RT766_SDCA_ENT_PLATFORM_FU33 0x44 ++#define RT766_SDCA_ENT_PPU21 0x04 ++ ++/* sample frequency index */ ++#define RT766_SDCA_RATE_44100HZ 0x08 ++#define RT766_SDCA_RATE_48000HZ 0x09 ++#define RT766_SDCA_RATE_96000HZ 0x0b ++#define RT766_SDCA_RATE_192000HZ 0x0d ++ ++/* SDCA Register macros */ ++#define RT766_MUTE_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_MUTE, RT766_CH_##ch) ++ ++#define RT766_VOLUME_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_CHANNEL_VOLUME, RT766_CH_##ch) ++ ++#define RT766_GAIN_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_GAIN, RT766_CH_##ch) ++ ++#define RT766_PDE_REQ_REG(func, pde) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##pde, SDCA_CTL_PDE_REQUESTED_PS, 0) ++ ++#define RT766_PDE_ACTUAL_REG(func, pde) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##pde, SDCA_CTL_PDE_ACTUAL_PS, 0) ++ ++#define RT766_FUNC_STATUS_REG(func) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_0, SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0) ++ ++#define RT766_SDCA_CTL(func, ent, ctl) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##ent, ctl, 0) ++ ++enum { ++ RT766_AIF1, ++ RT766_AIF2, ++ RT766_AIF3, ++}; ++ ++enum { ++ RT766_DAI_UAJ, ++ RT766_DAI_AMP, ++ RT766_DAI_MIC, ++}; ++ ++int rt766_sdca_io_init(struct device *dev, struct sdw_slave *slave); ++int rt766_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave *slave); ++#endif /* __RT766_H__ */ +diff --git a/sound/soc/codecs/rt9120.c b/sound/soc/codecs/rt9120.c +--- a/sound/soc/codecs/rt9120.c ++++ b/sound/soc/codecs/rt9120.c +@@ -606,12 +606,19 @@ static int rt9120_runtime_suspend(struct device *dev) + static int rt9120_runtime_resume(struct device *dev) + { + struct rt9120_data *data = dev_get_drvdata(dev); ++ int ret; + + if (data->pwdnn_gpio) { + gpiod_set_value(data->pwdnn_gpio, 1); + msleep(RT9120_CHIPON_WAITMS); + regcache_cache_only(data->regmap, false); +- regcache_sync(data->regmap); ++ ret = regcache_sync(data->regmap); ++ if (ret) { ++ regcache_cache_only(data->regmap, true); ++ regcache_mark_dirty(data->regmap); ++ gpiod_set_value(data->pwdnn_gpio, 0); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/sigmadsp.c b/sound/soc/codecs/sigmadsp.c +--- a/sound/soc/codecs/sigmadsp.c ++++ b/sound/soc/codecs/sigmadsp.c +@@ -5,6 +5,7 @@ + * Copyright 2009-2014 Analog Devices Inc. + */ + ++#include + #include + #include + #include +@@ -135,7 +136,7 @@ static int sigmadsp_ctrl_put(struct snd_kcontrol *kcontrol, + uint8_t *data; + int ret = 0; + +- mutex_lock(&sigmadsp->lock); ++ guard(mutex)(&sigmadsp->lock); + + data = ucontrol->value.bytes.data; + +@@ -148,8 +149,6 @@ static int sigmadsp_ctrl_put(struct snd_kcontrol *kcontrol, + ctrl->cached = true; + } + +- mutex_unlock(&sigmadsp->lock); +- + return ret; + } + +@@ -160,7 +159,7 @@ static int sigmadsp_ctrl_get(struct snd_kcontrol *kcontrol, + struct sigmadsp *sigmadsp = snd_kcontrol_chip(kcontrol); + int ret = 0; + +- mutex_lock(&sigmadsp->lock); ++ guard(mutex)(&sigmadsp->lock); + + if (!ctrl->cached) { + ret = sigmadsp_read(sigmadsp, ctrl->addr, ctrl->cache, +@@ -174,8 +173,6 @@ static int sigmadsp_ctrl_get(struct snd_kcontrol *kcontrol, + ctrl->num_bytes); + } + +- mutex_unlock(&sigmadsp->lock); +- + return ret; + } + +@@ -487,7 +484,7 @@ static void devm_sigmadsp_release(struct device *dev, void *res) + static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + { + const struct sigma_firmware_header *ssfw_head; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + u32 crc; + +@@ -495,7 +492,7 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + ret = request_firmware(&fw, name, sigmadsp->dev); + if (ret) { + pr_debug("%s: request_firmware() failed with %i\n", __func__, ret); +- goto done; ++ return ret; + } + + /* then verify the header */ +@@ -509,13 +506,13 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + */ + if (fw->size < sizeof(*ssfw_head) || fw->size >= 0x4000000) { + dev_err(sigmadsp->dev, "Failed to load firmware: Invalid size\n"); +- goto done; ++ return -EINVAL; + } + + ssfw_head = (void *)fw->data; + if (memcmp(ssfw_head->magic, SIGMA_MAGIC, ARRAY_SIZE(ssfw_head->magic))) { + dev_err(sigmadsp->dev, "Failed to load firmware: Invalid magic\n"); +- goto done; ++ return -EINVAL; + } + + crc = crc32(0, fw->data + sizeof(*ssfw_head), +@@ -524,7 +521,7 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + if (crc != le32_to_cpu(ssfw_head->crc)) { + dev_err(sigmadsp->dev, "Failed to load firmware: Wrong crc checksum: expected %x got %x\n", + le32_to_cpu(ssfw_head->crc), crc); +- goto done; ++ return -EINVAL; + } + + switch (ssfw_head->version) { +@@ -545,9 +542,6 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + if (ret) + sigmadsp_firmware_release(sigmadsp); + +-done: +- release_firmware(fw); +- + return ret; + } + +@@ -677,10 +671,10 @@ static void sigmadsp_activate_ctrl(struct sigmadsp *sigmadsp, + return; + changed = snd_ctl_activate_id(card, &ctrl->kcontrol->id, active); + if (active && changed > 0) { +- mutex_lock(&sigmadsp->lock); +- if (ctrl->cached) +- sigmadsp_ctrl_write(sigmadsp, ctrl, ctrl->cache); +- mutex_unlock(&sigmadsp->lock); ++ scoped_guard(mutex, &sigmadsp->lock) { ++ if (ctrl->cached) ++ sigmadsp_ctrl_write(sigmadsp, ctrl, ctrl->cache); ++ } + } + } + +diff --git a/sound/soc/codecs/sma1303.c b/sound/soc/codecs/sma1303.c +--- a/sound/soc/codecs/sma1303.c ++++ b/sound/soc/codecs/sma1303.c +@@ -1777,6 +1777,8 @@ static void sma1303_i2c_remove(struct i2c_client *client) + struct sma1303_priv *sma1303 = + (struct sma1303_priv *) i2c_get_clientdata(client); + ++ if (sma1303->attr_grp) ++ sysfs_remove_group(sma1303->kobj, sma1303->attr_grp); + cancel_delayed_work_sync(&sma1303->check_fault_work); + } + +diff --git a/sound/soc/codecs/sma1307.c b/sound/soc/codecs/sma1307.c +--- a/sound/soc/codecs/sma1307.c ++++ b/sound/soc/codecs/sma1307.c +@@ -1690,7 +1690,7 @@ static void sma1307_check_fault_worker(struct work_struct *work) + + static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *file) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int size, offset, num_mode; + int ret; + +@@ -1703,22 +1703,18 @@ static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *fil + return; + } else if ((fw->size) < SMA1307_SETTING_HEADER_SIZE) { + dev_err(sma1307->dev, "%s: Invalid file\n", __func__); +- release_firmware(fw); + sma1307->set.status = false; + return; + } + + int *data __free(kfree) = kzalloc(fw->size, GFP_KERNEL); + if (!data) { +- release_firmware(fw); + sma1307->set.status = false; + return; + } + size = fw->size >> 2; + memcpy(data, fw->data, fw->size); + +- release_firmware(fw); +- + /* HEADER */ + sma1307->set.header_size = SMA1307_SETTING_HEADER_SIZE; + sma1307->set.checksum = data[sma1307->set.header_size - 2]; +diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c +--- a/sound/soc/codecs/sta32x.c ++++ b/sound/soc/codecs/sta32x.c +@@ -17,6 +17,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -269,9 +270,9 @@ static int sta32x_coefficient_get(struct snd_kcontrol *kcontrol, + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud, val; +- int i, ret = 0; ++ int i; + +- mutex_lock(&sta32x->coeff_lock); ++ guard(mutex)(&sta32x->coeff_lock); + + /* preserve reserved bits in STA32X_CFUD */ + regmap_read(sta32x->regmap, STA32X_CFUD, &cfud); +@@ -283,24 +284,20 @@ static int sta32x_coefficient_get(struct snd_kcontrol *kcontrol, + regmap_write(sta32x->regmap, STA32X_CFUD, cfud); + + regmap_write(sta32x->regmap, STA32X_CFADDR2, index); +- if (numcoef == 1) { ++ if (numcoef == 1) + regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x04); +- } else if (numcoef == 5) { ++ else if (numcoef == 5) + regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x08); +- } else { +- ret = -EINVAL; +- goto exit_unlock; +- } ++ else ++ return -EINVAL; ++ + + for (i = 0; i < 3 * numcoef; i++) { + regmap_read(sta32x->regmap, STA32X_B1CF1 + i, &val); + ucontrol->value.bytes.data[i] = val; + } + +-exit_unlock: +- mutex_unlock(&sta32x->coeff_lock); +- +- return ret; ++ return 0; + } + + static int sta32x_coefficient_put(struct snd_kcontrol *kcontrol, +diff --git a/sound/soc/codecs/sta350.c b/sound/soc/codecs/sta350.c +--- a/sound/soc/codecs/sta350.c ++++ b/sound/soc/codecs/sta350.c +@@ -16,6 +16,7 @@ + + #define pr_fmt(fmt) KBUILD_MODNAME ":%s:%d: " fmt, __func__, __LINE__ + ++#include + #include + #include + #include +@@ -306,9 +307,9 @@ static int sta350_coefficient_get(struct snd_kcontrol *kcontrol, + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud, val; +- int i, ret = 0; ++ int i; + +- mutex_lock(&sta350->coeff_lock); ++ guard(mutex)(&sta350->coeff_lock); + + /* preserve reserved bits in STA350_CFUD */ + regmap_read(sta350->regmap, STA350_CFUD, &cfud); +@@ -320,24 +321,19 @@ static int sta350_coefficient_get(struct snd_kcontrol *kcontrol, + regmap_write(sta350->regmap, STA350_CFUD, cfud); + + regmap_write(sta350->regmap, STA350_CFADDR2, index); +- if (numcoef == 1) { ++ if (numcoef == 1) + regmap_write(sta350->regmap, STA350_CFUD, cfud | 0x04); +- } else if (numcoef == 5) { ++ else if (numcoef == 5) + regmap_write(sta350->regmap, STA350_CFUD, cfud | 0x08); +- } else { +- ret = -EINVAL; +- goto exit_unlock; +- } ++ else ++ return -EINVAL; + + for (i = 0; i < 3 * numcoef; i++) { + regmap_read(sta350->regmap, STA350_B1CF1 + i, &val); + ucontrol->value.bytes.data[i] = val; + } + +-exit_unlock: +- mutex_unlock(&sta350->coeff_lock); +- +- return ret; ++ return 0; + } + + static int sta350_coefficient_put(struct snd_kcontrol *kcontrol, +diff --git a/sound/soc/codecs/tac5xx2-sdw.c b/sound/soc/codecs/tac5xx2-sdw.c +--- a/sound/soc/codecs/tac5xx2-sdw.c ++++ b/sound/soc/codecs/tac5xx2-sdw.c +@@ -88,6 +88,9 @@ + #define TAC_FW_FILE_HDR 20 + #define TAC_MAX_FW_CHUNKS 512 + ++#define TAC_UAJ_PREP_CONNECTED 0xff ++#define TAC_UAJ_PREP_DISCONNECTED 0xdf ++ + struct tac_fw_hdr { + u32 size; + u32 version_offset; +@@ -138,6 +141,7 @@ struct tac5xx2_prv { + bool hw_init; + bool first_hw_init_done; + u32 part_id; ++ u32 rev_id; + struct snd_soc_jack *hs_jack; + int jack_type; + /* Custom fw binary. UMP File Download is not used. */ +@@ -261,7 +265,6 @@ static const struct reg_default tac_reg_default[] = { + {TAC_REG_SDW(0, 0, 0x78), 0x0}, + {TAC_REG_SDW(0, 0, 0x7b), 0x0}, + {TAC_REG_SDW(0, 0, 0x7c), 0xd0}, +- {TAC_REG_SDW(0, 0, 0x7d), 0x0}, + {TAC_REG_SDW(0, 0, 0x7e), 0x0}, + {TAC_REG_SDW(0, 1, 0x1), 0x0}, + {TAC_REG_SDW(0, 1, 0x2), 0x0}, +@@ -507,18 +510,6 @@ static const struct regmap_config tac_regmap = { + .use_single_write = true, + }; + +-/* Check if device has DSP algo that needs status monitoring */ +-static bool tac_has_dsp_algo(struct tac5xx2_prv *tac_dev) +-{ +- switch (tac_dev->part_id) { +- case 0x5682: +- case 0x2883: +- return true; +- default: +- return false; +- } +-} +- + /* Check if device has UAJ (Universal Audio Jack) support */ + static bool tac_has_uaj_support(struct tac5xx2_prv *tac_dev) + { +@@ -800,7 +791,7 @@ static int tac_sdw_hw_params(struct snd_pcm_substream *substream, + return ret; + } + +- ret = sdca_asoc_pde_poll_actual_ps(tac_dev->dev, tac_dev->regmap, function_id, pde_entity, ++ ret = sdca_asoc_pde_poll_actual_ps(tac_dev->regmap, function_id, pde_entity, + SDCA_PDE_PS3, SDCA_PDE_PS0, NULL, 0); + if (ret) + dev_err(tac_dev->dev, "failed to transition func %d, pde %d from PS3 -> PS0, err=%d\n", +@@ -847,7 +838,7 @@ static int tac_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + return ret; + } + +- ret = sdca_asoc_pde_poll_actual_ps(tac_dev->dev, tac_dev->regmap, function_id, ++ ret = sdca_asoc_pde_poll_actual_ps(tac_dev->regmap, function_id, + pde_entity, SDCA_PDE_PS0, SDCA_PDE_PS3, + NULL, 0); + if (ret) +@@ -942,6 +933,7 @@ static int tac5xx2_sdca_button_detect(struct tac5xx2_prv *tac_dev) + static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + { + int val, ret; ++ u8 jack_prep; + + ret = regmap_read(tac_dev->regmap, + SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_GE35, +@@ -951,6 +943,8 @@ static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + return ret; + } + ++ jack_prep = TAC_UAJ_PREP_CONNECTED; ++ + switch (val) { + case 4: + tac_dev->jack_type = SND_JACK_MICROPHONE; +@@ -964,6 +958,7 @@ static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + case 0: + default: + tac_dev->jack_type = 0; ++ jack_prep = TAC_UAJ_PREP_DISCONNECTED; + break; + } + +@@ -973,26 +968,44 @@ static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + if (ret) + dev_err(tac_dev->dev, "Failed to update the jack type to device"); + ++ /* ++ * When uaj is uplugged and booted, we end up with the channel prepare ++ * timeout error for the uaj ports. This writes allows the channel prepare ++ * to succeed when the uaj is not plugged in. ++ */ ++ if (tac_dev->rev_id >= 0x30) { ++ ret = regmap_write(tac_dev->regmap, TAC_REG_SDW(0, 3, 127), jack_prep); ++ if (ret) ++ dev_warn(tac_dev->dev, "Failed to write jack_prep register: %d\n", ret); ++ } ++ + return 0; + } + + static int tac5xx2_jack_init(struct tac5xx2_prv *tac_dev) + { ++ u32 jd_int_mask, hid_int_mask; + int ret = 0; + ++ if (tac_dev->rev_id >= 0x30) { ++ jd_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_12; ++ hid_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_17; ++ } else { ++ jd_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_11; ++ hid_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_16; ++ } ++ + if (!tac_dev->hs_jack) + goto disable_interrupts; + +- ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_11); ++ ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK2, jd_int_mask); + if (ret) { + dev_err(tac_dev->dev, + "Failed to register jack detection interrupt: %d\n", ret); + goto disable_interrupts; + } + +- ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK3, +- SDW_SCP_SDCA_INTMASK_SDCA_16); ++ ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK3, hid_int_mask); + if (ret) { + dev_err(tac_dev->dev, + "Failed to register for button detect interrupt: %d\n", ret); +@@ -1051,6 +1064,7 @@ static int tac_interrupt_callback(struct sdw_slave *slave, + unsigned int sdca_int2, sdca_int3, jack_report_mask = 0; + struct tac5xx2_prv *tac_dev = dev_get_drvdata(&slave->dev); + struct device *dev = &slave->dev; ++ u32 headset_detect_chk, hid_detect_chk; + int btn_type = 0; + int ret = 0; + +@@ -1081,14 +1095,22 @@ static int tac_interrupt_callback(struct sdw_slave *slave, + dev_dbg(dev, "SDCA_INT2: 0x%02x, SDCA_INT3: 0x%02x\n", + sdca_int2, sdca_int3); + +- if (sdca_int2 & SDW_SCP_SDCA_INT_SDCA_11) { ++ if (tac_dev->rev_id >= 0x30) { ++ headset_detect_chk = SDW_SCP_SDCA_INT_SDCA_12; ++ hid_detect_chk = SDW_SCP_SDCA_INT_SDCA_17; ++ } else { ++ headset_detect_chk = SDW_SCP_SDCA_INT_SDCA_11; ++ hid_detect_chk = SDW_SCP_SDCA_INT_SDCA_16; ++ } ++ ++ if (sdca_int2 & headset_detect_chk) { + ret = tac5xx2_sdca_headset_detect(tac_dev); + if (ret < 0) + goto clear; + jack_report_mask |= SND_JACK_HEADSET; + } + +- if (sdca_int3 & SDW_SCP_SDCA_INT_SDCA_16) { ++ if (sdca_int3 & hid_detect_chk) { + btn_type = tac5xx2_sdca_button_detect(tac_dev); + if (btn_type < 0) + btn_type = 0; +@@ -1695,6 +1717,15 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) + goto io_init_err; + } + ++ if (!tac_dev->rev_id) { ++ ret = regmap_read(tac_dev->regmap, TAC_REV_ID, &tac_dev->rev_id); ++ if (ret) { ++ dev_err(tac_dev->dev, "failed to rev id, err=%d\n", ret); ++ goto io_init_err; ++ } ++ dev_dbg(tac_dev->dev, "detected rev_id 0x%x", tac_dev->rev_id); ++ } ++ + if (tac_dev->fw_files && tac_dev->fw_file_cnt > 0) { + ret = tac_download_fw_to_hw(tac_dev); + if (ret) { +@@ -1940,7 +1971,7 @@ static s32 tac_sdw_probe(struct sdw_slave *peripheral, + "failed to allocate %s function data", + func_name); + function_data->desc = &peripheral->sdca_data.function[i]; +- ret = sdca_parse_function(dev, peripheral, function_data); ++ ret = sdca_parse_function(dev, function_data); + if (!ret) + *func_ptr = function_data; + else +@@ -1959,6 +1990,7 @@ static s32 tac_sdw_probe(struct sdw_slave *peripheral, + tac_dev->hw_init = false; + tac_dev->first_hw_init_done = false; + tac_dev->part_id = id->part_id; ++ tac_dev->rev_id = 0x0; + dev_set_drvdata(dev, tac_dev); + + regmap = devm_regmap_init_sdw_mbq_cfg(&peripheral->dev, peripheral, +@@ -1972,17 +2004,13 @@ static s32 tac_sdw_probe(struct sdw_slave *peripheral, + tac_dev->jack_type = 0; + init_completion(&tac_dev->fw_caching_complete); + +- if (tac_has_dsp_algo(tac_dev)) { +- tac_generate_fw_name(peripheral, tac_dev->fw_binaryname, +- sizeof(tac_dev->fw_binaryname)); ++ tac_generate_fw_name(peripheral, tac_dev->fw_binaryname, ++ sizeof(tac_dev->fw_binaryname)); + +- ret = tac_load_and_cache_firmware_async(tac_dev); +- if (ret) { +- complete_all(&tac_dev->fw_caching_complete); +- dev_dbg(dev, "failed to load fw: %d, use rom mode\n", ret); +- } +- } else { ++ ret = tac_load_and_cache_firmware_async(tac_dev); ++ if (ret) { + complete_all(&tac_dev->fw_caching_complete); ++ dev_dbg(dev, "failed to load fw: %d, use rom mode\n", ret); + } + + ret = tac_init(tac_dev); +diff --git a/sound/soc/codecs/tas2552.c b/sound/soc/codecs/tas2552.c +--- a/sound/soc/codecs/tas2552.c ++++ b/sound/soc/codecs/tas2552.c +@@ -495,13 +495,21 @@ static int tas2552_runtime_suspend(struct device *dev) + static int tas2552_runtime_resume(struct device *dev) + { + struct tas2552_data *tas2552 = dev_get_drvdata(dev); ++ int ret; + + gpiod_set_value_cansleep(tas2552->enable_gpio, 1); + + tas2552_sw_shutdown(tas2552, 0); + + regcache_cache_only(tas2552->regmap, false); +- regcache_sync(tas2552->regmap); ++ ret = regcache_sync(tas2552->regmap); ++ if (ret) { ++ regcache_cache_only(tas2552->regmap, true); ++ regcache_mark_dirty(tas2552->regmap); ++ tas2552_sw_shutdown(tas2552, 1); ++ gpiod_set_value_cansleep(tas2552->enable_gpio, 0); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/tas2781-comlib-i2c.c b/sound/soc/codecs/tas2781-comlib-i2c.c +--- a/sound/soc/codecs/tas2781-comlib-i2c.c ++++ b/sound/soc/codecs/tas2781-comlib-i2c.c +@@ -6,6 +6,7 @@ + // + // Author: Shenghao Ding + ++#include + #include + #include + #include +@@ -342,7 +343,7 @@ int tascodec_init(struct tasdevice_priv *tas_priv, void *codec, + /* Codec Lock Hold to ensure that codec_probe and firmware parsing and + * loading do not simultaneously execute. + */ +- mutex_lock(&tas_priv->codec_lock); ++ guard(mutex)(&tas_priv->codec_lock); + + if (tas_priv->name_prefix) + scnprintf(tas_priv->rca_binaryname, 64, "%s-%sRCA%d.bin", +@@ -360,8 +361,6 @@ int tascodec_init(struct tasdevice_priv *tas_priv, void *codec, + dev_err(tas_priv->dev, "request_firmware_nowait err:0x%08x\n", + ret); + +- /* Codec Lock Release*/ +- mutex_unlock(&tas_priv->codec_lock); + return ret; + } + EXPORT_SYMBOL_GPL(tascodec_init); +diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c +--- a/sound/soc/codecs/tas2781-fmwlib.c ++++ b/sound/soc/codecs/tas2781-fmwlib.c +@@ -1849,16 +1849,26 @@ static int tasdev_load_blk(struct tasdevice_priv *tas_priv, + } + } + if (ret == -EAGAIN) { +- if (block->nr_retry > 0) ++ if (block->nr_retry > 0) { ++ /* Give the hardware time to stabilize before ++ * next block re-transmission attempt. ++ */ ++ usleep_range(2000, 2500); + continue; ++ } + } else if (ret < 0) /*err in current device, skip it*/ + break; + + if (block->is_pchksum_present) { + ret = tasdev_block_chksum(tas_priv, block, chn); + if (ret == -EAGAIN) { +- if (block->nr_retry > 0) ++ if (block->nr_retry > 0) { ++ /* Give the bus time to recover after ++ * a checksum mismatch error. ++ */ ++ usleep_range(2000, 2500); + continue; ++ } + } else if (ret < 0) /*err in current device, skip it*/ + break; + } +@@ -2243,7 +2253,7 @@ int tas2781_load_calibration(void *context, char *file_name, + { + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; + struct tasdevice *tasdev = &(tas_priv->tasdevice[i]); +- const struct firmware *fw_entry = NULL; ++ const struct firmware *fw_entry __free(firmware) = NULL; + struct tasdevice_fw *tas_fmw; + struct firmware fmw; + int offset = 0; +@@ -2253,60 +2263,50 @@ int tas2781_load_calibration(void *context, char *file_name, + if (ret) { + dev_err(tas_priv->dev, "%s: Request firmware %s failed\n", + __func__, file_name); +- goto out; ++ return ret; + } + + if (!fw_entry->size) { + dev_err(tas_priv->dev, "%s: file read error: size = %lu\n", + __func__, (unsigned long)fw_entry->size); +- ret = -EINVAL; +- goto out; ++ return -EINVAL; + } + fmw.size = fw_entry->size; + fmw.data = fw_entry->data; + + tas_fmw = tasdev->cali_data_fmw = kzalloc_obj(struct tasdevice_fw); +- if (!tasdev->cali_data_fmw) { +- ret = -ENOMEM; +- goto out; +- } ++ if (!tasdev->cali_data_fmw) ++ return -ENOMEM; ++ + tas_fmw->dev = tas_priv->dev; + offset = fw_parse_header(tas_priv, tas_fmw, &fmw, offset); + if (offset == -EINVAL) { + dev_err(tas_priv->dev, "fw_parse_header EXIT!\n"); +- ret = offset; +- goto out; ++ return -EINVAL; + } + offset = fw_parse_variable_hdr_cal(tas_priv, tas_fmw, &fmw, offset); + if (offset == -EINVAL) { + dev_err(tas_priv->dev, + "%s: fw_parse_variable_header_cal EXIT!\n", __func__); +- ret = offset; +- goto out; ++ return -EINVAL; + } + offset = fw_parse_program_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_program_data EXIT!\n"); +- ret = offset; +- goto out; ++ return offset; + } + offset = fw_parse_configuration_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_configuration_data EXIT!\n"); +- ret = offset; +- goto out; ++ return offset; + } + offset = fw_parse_calibration_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_calibration_data EXIT!\n"); +- ret = offset; +- goto out; ++ return offset; + } + +-out: +- release_firmware(fw_entry); +- +- return ret; ++ return 0; + } + EXPORT_SYMBOL_NS_GPL(tas2781_load_calibration, "SND_SOC_TAS2781_FMWLIB"); + +@@ -2399,7 +2399,7 @@ static int tasdevice_dspfw_ready(const struct firmware *fmw, + int tasdevice_dsp_parser(void *context) + { + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; +- const struct firmware *fw_entry; ++ const struct firmware *fw_entry __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw_entry, tas_priv->coef_binaryname, +@@ -2407,15 +2407,10 @@ int tasdevice_dsp_parser(void *context) + if (ret) { + dev_err(tas_priv->dev, "%s: load %s error\n", __func__, + tas_priv->coef_binaryname); +- goto out; ++ return ret; + } + +- ret = tasdevice_dspfw_ready(fw_entry, tas_priv); +- release_firmware(fw_entry); +- fw_entry = NULL; +- +-out: +- return ret; ++ return tasdevice_dspfw_ready(fw_entry, tas_priv); + } + EXPORT_SYMBOL_NS_GPL(tasdevice_dsp_parser, "SND_SOC_TAS2781_FMWLIB"); + +@@ -2797,11 +2792,12 @@ int tasdevice_prmg_load(void *context, int prm_no) + } + EXPORT_SYMBOL_NS_GPL(tasdevice_prmg_load, "SND_SOC_TAS2781_FMWLIB"); + +-void tasdevice_tuning_switch(void *context, int state) ++void tasdevice_tuning_switch(void *context, int state, bool is_cap) + { + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *) context; + struct tasdevice_fw *tas_fmw = tas_priv->fmw; +- int profile_cfg_id = tas_priv->rcabin.profile_cfg_id; ++ int profile_cfg_id = is_cap ? tas_priv->rcabin.capture_profile_id : ++ tas_priv->rcabin.profile_cfg_id; + + /* + * Only RCA-based Playback can still work with no dsp program running +@@ -2818,7 +2814,6 @@ void tasdevice_tuning_switch(void *context, int state) + if (state == 0) { + if (tas_fmw && tas_priv->cur_prog < tas_fmw->nr_programs) { + /* dsp mode or tuning mode */ +- profile_cfg_id = tas_priv->rcabin.profile_cfg_id; + tasdevice_select_tuningprm_cfg(tas_priv, + tas_priv->cur_prog, tas_priv->cur_conf, + profile_cfg_id); +diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c +--- a/sound/soc/codecs/tas2781-i2c.c ++++ b/sound/soc/codecs/tas2781-i2c.c +@@ -13,6 +13,7 @@ + // Author: Kevin Lu + // + ++#include + #include + #include + #include +@@ -617,6 +618,7 @@ static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *priv = snd_soc_component_get_drvdata(comp); ++ int i; + + guard(mutex)(&priv->codec_lock); + if (priv->chip_id == TAS2563) +@@ -624,6 +626,14 @@ static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, + else + tas2781_calib_stop_put(priv); + ++ /* ++ * Set reloading-firmware flag after calibration, the flag will work ++ * during next playback, then set to the program id after reloading ++ * firmware. ++ */ ++ for (i = 0; i < priv->ndev; i++) ++ priv->tasdevice[i].cur_prog = -1; ++ + return 1; + } + +@@ -843,12 +853,12 @@ static int tasdevice_digital_gain_get( + unsigned char data[4]; + int ret; + +- mutex_lock(&tas_dev->codec_lock); ++ guard(mutex)(&tas_dev->codec_lock); + /* Read the primary device */ + ret = tasdevice_dev_bulk_read(tas_dev, 0, reg, data, 4); + if (ret) { + dev_err(tas_dev->dev, "%s, get AMP vol error\n", __func__); +- goto out; ++ return ret; + } + + target = get_unaligned_be32(&data[0]); +@@ -868,8 +878,7 @@ static int tasdevice_digital_gain_get( + /* find out the member same as or closer to the current volume */ + ucontrol->value.integer.value[0] = + abs(target - ar_l) <= abs(target - ar_r) ? l : r; +-out: +- mutex_unlock(&tas_dev->codec_lock); ++ + return 0; + } + +@@ -882,29 +891,26 @@ static int tasdevice_digital_gain_put( + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *tas_dev = snd_soc_component_get_drvdata(codec); + int vol = ucontrol->value.integer.value[0]; +- int status = 0, max = mc->max, rc = 1; ++ int status = 0, max = mc->max; + int i, ret; + unsigned int reg = mc->reg; + unsigned int volrd, volwr; + unsigned char data[4]; + + vol = clamp(vol, 0, max); +- mutex_lock(&tas_dev->codec_lock); ++ guard(mutex)(&tas_dev->codec_lock); + /* Read the primary device */ + ret = tasdevice_dev_bulk_read(tas_dev, 0, reg, data, 4); + if (ret) { + dev_err(tas_dev->dev, "%s, get AMP vol error\n", __func__); +- rc = -1; +- goto out; ++ return -1; + } + + volrd = get_unaligned_be32(&data[0]); + volwr = get_unaligned_be32(tas_dev->dvc_tlv_table[vol]); + +- if (volrd == volwr) { +- rc = 0; +- goto out; +- } ++ if (volrd == volwr) ++ return 0; + + for (i = 0; i < tas_dev->ndev; i++) { + ret = tasdevice_dev_bulk_write(tas_dev, i, reg, +@@ -918,10 +924,9 @@ static int tasdevice_digital_gain_put( + } + + if (status) +- rc = -1; +-out: +- mutex_unlock(&tas_dev->codec_lock); +- return rc; ++ return -1; ++ ++ return 1; + } + + static const struct snd_kcontrol_new tasdevice_cali_controls[] = { +@@ -995,6 +1000,57 @@ static int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol, + return ret; + } + ++/** ++ * tasdevice_set_capture_profile_id - Set runtime capture profile index via ++ * ALSA control ++ * @kcontrol: ALSA kcontrol handle that triggers this operation ++ * @ucontrol: User space control value carrying the new profile index ++ * ++ * This mixer control handler validates the user-provided capture profile ID ++ * against the maximum valid index parsed from the loaded DSP firmware, ++ * then updates the runtime stored capture profile ID only if the new value ++ * differs from the current active one. It will immediately return -EINVAL ++ * if the submitted profile ID falls outside the valid range, including the ++ * edge case that no valid configuration blocks are detected in firmware. ++ * ++ * No actual DSP register write is performed in this handler. The updated ++ * profile ID will be applied to the hardware when the next ALSA capture ++ * stream starts up. Caller does not need to take extra codec lock here, ++ * as the ALSA control core already guarantees serialized execution. ++ * ++ * Return: 1 if profile ID value was changed, 0 if no modification needed, ++ * -EINVAL if the input profile ID is out of valid range ++ */ ++static int tasdevice_set_capture_profile_id(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); ++ struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); ++ unsigned int user_prof_id = ucontrol->value.integer.value[0]; ++ unsigned int max_valid_id; ++ int ret = 0; ++ ++ /* ++ * Align valid range with the bound defined in ++ * tasdevice_info_profile() ++ */ ++ max_valid_id = tas_priv->rcabin.ncfgs - 1; ++ ++ /* ++ * Reject invalid input including zero total configuration edge ++ * case ++ */ ++ if (tas_priv->rcabin.ncfgs == 0 || user_prof_id > max_valid_id) ++ return -EINVAL; ++ ++ if (tas_priv->rcabin.capture_profile_id != user_prof_id) { ++ tas_priv->rcabin.capture_profile_id = user_prof_id; ++ ret = 1; ++ } ++ ++ return ret; ++} ++ + static int tasdevice_info_active_num(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) + { +@@ -1075,6 +1131,41 @@ static int tasdevice_get_profile_id(struct snd_kcontrol *kcontrol, + return 0; + } + ++/** ++ * tasdevice_get_capture_profile_id - Report current active capture profile ++ * ID to user space ++ * @kcontrol: ALSA kcontrol structure passed from ALSA core ++ * @ucontrol: User-space control element value buffer to write the result back ++ * ++ * This function ensures the returned profile ID is always clamped inside the ++ * valid range advertised by the info callback, preventing accidental invalid ++ * values from being exposed to applications even if internal driver state is ++ * temporarily inconsistent. ++ * ++ * Returns 0 on successful fill of the control value, no error conditions ++ * are defined for this getter callback. ++ */ ++static int tasdevice_get_capture_profile_id(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); ++ struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); ++ unsigned int max_valid_id, current_prof_id; ++ ++ max_valid_id = tas_priv->rcabin.ncfgs > 0 ? ++ (tas_priv->rcabin.ncfgs - 1U) : 0; ++ ++ /* ++ * Cast current profile id to unsigned to match type with max_valid_id, ++ * avoid signedness mismatch; ++ */ ++ current_prof_id = (unsigned int)tas_priv->rcabin.capture_profile_id; ++ /* Prevent underflow when there are no loaded capture profiles. */ ++ ucontrol->value.integer.value[0] = min(current_prof_id, max_valid_id); ++ ++ return 0; ++} ++ + static int tasdevice_get_chip_id(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) + { +@@ -1117,6 +1208,41 @@ static int tasdevice_create_control(struct tasdevice_priv *tas_priv) + ret = snd_soc_add_component_controls(tas_priv->codec, + prof_ctrls, nr_controls < mix_index ? nr_controls : mix_index); + ++ mix_index = 0; ++ switch (tas_priv->chip_id) { ++ case TAS2563: ++ case TAS2568: ++ case TAS2570: ++ case TAS2572: ++ case TAS2573: ++ case TAS2574: ++ case TAS2781: ++ prof_ctrls = devm_kcalloc(tas_priv->dev, nr_controls, ++ sizeof(prof_ctrls[0]), GFP_KERNEL); ++ if (!prof_ctrls) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ /* Create a mixer item for selecting the capture profile */ ++ name = devm_kstrdup(tas_priv->dev, "Speaker Capture Profile Id", ++ GFP_KERNEL); ++ if (!name) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ prof_ctrls[mix_index].name = name; ++ prof_ctrls[mix_index].iface = SNDRV_CTL_ELEM_IFACE_MIXER; ++ prof_ctrls[mix_index].info = tasdevice_info_profile; ++ prof_ctrls[mix_index].get = tasdevice_get_capture_profile_id; ++ prof_ctrls[mix_index].put = tasdevice_set_capture_profile_id; ++ mix_index++; ++ ++ ret = snd_soc_add_component_controls(tas_priv->codec, ++ prof_ctrls, nr_controls < mix_index ? nr_controls : mix_index); ++ break; ++ } ++ + out: + return ret; + } +@@ -1330,6 +1456,43 @@ static void alpa_cali_update(struct bulk_reg_val *p, + p->val_len = 4; + } + ++static int create_tas2781_cali_start_ktrl(struct tasdevice_priv ++ *priv, struct snd_kcontrol_new *cali_ctrl) ++{ ++ struct soc_bytes_ext *ext_cali_start; ++ char *cali_start_name; ++ ++ ext_cali_start = devm_kzalloc(priv->dev, ++ sizeof(*ext_cali_start), GFP_KERNEL); ++ if (!ext_cali_start) ++ return -ENOMEM; ++ ++ cali_start_name = devm_kstrdup(priv->dev, ++ "Calibration Start", GFP_KERNEL); ++ if (!cali_start_name) ++ return -ENOMEM; ++ /* ++ * package structure for tas2781 ftc start: ++ * Pkg len (1 byte) ++ * Reg id (1 byte, constant 'r') ++ * book, page, register for pilot threshold, pilot tone ++ * and sine gain (12 bytes) ++ * for (i = 0; i < Device-Sum; i++) { ++ * Device #i index_info (1 byte) ++ * Sine gain for Device #i (8 bytes) ++ * } ++ */ ++ ext_cali_start->max = 14 + priv->ndev * 9; ++ cali_ctrl->name = cali_start_name; ++ cali_ctrl->iface = SNDRV_CTL_ELEM_IFACE_MIXER; ++ cali_ctrl->info = snd_soc_bytes_info_ext; ++ cali_ctrl->put = tas2781_calib_start_put; ++ cali_ctrl->get = tasdev_nop_get; ++ cali_ctrl->private_value = (unsigned long)ext_cali_start; ++ ++ return 0; ++} ++ + static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) + { + struct calidata *cali_data = &priv->cali_data; +@@ -1448,37 +1611,11 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) + */ + cali_data->data[0] = 0xff; + if (priv->chip_id == TAS2781) { +- struct soc_bytes_ext *ext_cali_start; +- char *cali_start_name; +- +- ext_cali_start = devm_kzalloc(priv->dev, +- sizeof(*ext_cali_start), GFP_KERNEL); +- if (!ext_cali_start) +- return -ENOMEM; +- +- cali_start_name = devm_kstrdup(priv->dev, +- "Calibration Start", GFP_KERNEL); +- if (!cali_start_name) +- return -ENOMEM; +- /* +- * package structure for tas2781 ftc start: +- * Pkg len (1 byte) +- * Reg id (1 byte, constant 'r') +- * book, page, register for pilot threshold, pilot tone +- * and sine gain (12 bytes) +- * for (i = 0; i < Device-Sum; i++) { +- * Device #i index_info (1 byte) +- * Sine gain for Device #i (8 bytes) +- * } +- */ +- ext_cali_start->max = 14 + priv->ndev * 9; +- cali_ctrls[i].name = cali_start_name; +- cali_ctrls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; +- cali_ctrls[i].info = snd_soc_bytes_info_ext; +- cali_ctrls[i].put = tas2781_calib_start_put; +- cali_ctrls[i].get = tasdev_nop_get; +- cali_ctrls[i].private_value = (unsigned long)ext_cali_start; ++ rc = create_tas2781_cali_start_ktrl(priv, &cali_ctrls[i]); ++ if (rc != 0) ++ return rc; + i++; ++ + } + + return snd_soc_add_component_controls(priv->codec, cali_ctrls, +@@ -1765,13 +1902,25 @@ static int tasdevice_dapm_event(struct snd_soc_dapm_widget *w, + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + int state = 0; + +- /* Codec Lock Hold */ +- mutex_lock(&tas_priv->codec_lock); ++ guard(mutex)(&tas_priv->codec_lock); + if (event == SND_SOC_DAPM_PRE_PMD) + state = 1; +- tasdevice_tuning_switch(tas_priv, state); +- /* Codec Lock Release*/ +- mutex_unlock(&tas_priv->codec_lock); ++ tasdevice_tuning_switch(tas_priv, state, false); ++ ++ return 0; ++} ++ ++static int tasdevice_capture_dapm_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *codec = snd_soc_dapm_to_component(w->dapm); ++ struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); ++ int state = 0; ++ ++ guard(mutex)(&tas_priv->codec_lock); ++ if (event == SND_SOC_DAPM_PRE_PMD) ++ state = 1; ++ tasdevice_tuning_switch(tas_priv, state, true); + + return 0; + } +@@ -1779,7 +1928,7 @@ static int tasdevice_dapm_event(struct snd_soc_dapm_widget *w, + static const struct snd_soc_dapm_widget tasdevice_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("ASI", "ASI Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT_E("ASI OUT", "ASI Capture", 0, SND_SOC_NOPM, +- 0, 0, tasdevice_dapm_event, ++ 0, 0, tasdevice_capture_dapm_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SPK("SPK", tasdevice_dapm_event), + SND_SOC_DAPM_OUTPUT("OUT"), +diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c +--- a/sound/soc/codecs/tas2783-sdw.c ++++ b/sound/soc/codecs/tas2783-sdw.c +@@ -12,6 +12,7 @@ + // Author: Baojun Xu + // Author: Kevin Lu + ++#include + #include + #include + #include +@@ -494,6 +495,56 @@ static bool tas2783_readable_register(struct device *dev, unsigned int reg) + return tas2783_sdca_mbq_size(dev, reg) > 0; + } + ++static bool tas2783_writeable_register(struct device *dev, unsigned int reg) ++{ ++ /* ++ * The Latency Control of every Entity, together with the Power Domain ++ * actual state and the protection status, is read-only. They are ++ * listed in tas2783_reg_default[] with a placeholder value, so without ++ * this a regcache_sync() would try to write them back and the ++ * peripheral would reject the transaction, aborting the sync. ++ */ ++ switch (reg) { ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU21, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU23, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU26, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x07, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS24, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS21, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS25, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS26, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS28, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PDE23, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_UDMPU23, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x05, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x11, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU21, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU26, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT21, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT29, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT26, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT28, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT24, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT23, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT25, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT28, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MU26, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT127, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU127, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS127, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x04, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x04, 0): ++ return false; ++ ++ default: ++ return tas2783_sdca_mbq_size(dev, reg) > 0; ++ } ++} ++ + static bool tas2783_volatile_register(struct device *dev, u32 reg) + { + switch (reg) { +@@ -515,6 +566,7 @@ static const struct regmap_config tas_regmap = { + .reg_bits = 32, + .val_bits = 8, + .readable_reg = tas2783_readable_register, ++ .writeable_reg = tas2783_writeable_register, + .volatile_reg = tas2783_volatile_register, + .reg_defaults = tas2783_reg_default, + .num_reg_defaults = ARRAY_SIZE(tas2783_reg_default), +@@ -692,12 +744,12 @@ static s32 tas2783_update_calibdata(struct tas2783_prv *tas_dev) + return 0; + } + +- mutex_lock(&tas_dev->calib_lock); +- ret = tas2783_validate_calibdata(tas_dev, tas_dev->cali_data.data, +- tas_dev->cali_data.read_sz); +- if (!ret) +- tas2783_set_calib_params_to_device(tas_dev, tmp_val); +- mutex_unlock(&tas_dev->calib_lock); ++ scoped_guard(mutex, &tas_dev->calib_lock) { ++ ret = tas2783_validate_calibdata(tas_dev, tas_dev->cali_data.data, ++ tas_dev->cali_data.read_sz); ++ if (!ret) ++ tas2783_set_calib_params_to_device(tas_dev, tmp_val); ++ } + + return ret; + } +@@ -926,22 +978,23 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, + dev_err(tas_dev->dev, + "clear latch failed, err=%d", ret); + +- mutex_lock(&tas_dev->pde_lock); +- /* +- * Sometimes, there is error returned during power on. +- * So added retry logic to ensure power on so that +- * port prepare succeeds +- */ +- do { +- ret = regmap_write(tas_dev->regmap, +- SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, +- TAS2783_SDCA_CTL_REQ_POW_STATE, 0), +- TAS2783_SDCA_POW_STATE_ON); +- if (!ret) +- break; +- usleep_range(2000, 2200); +- } while (retry--); +- mutex_unlock(&tas_dev->pde_lock); ++ scoped_guard(mutex, &tas_dev->pde_lock) { ++ /* ++ * Sometimes, there is error returned during power on. ++ * So added retry logic to ensure power on so that ++ * port prepare succeeds ++ */ ++ do { ++ ret = regmap_write(tas_dev->regmap, ++ SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, ++ TAS2783_SDCA_CTL_REQ_POW_STATE, 0), ++ TAS2783_SDCA_POW_STATE_ON); ++ if (!ret) ++ break; ++ usleep_range(2000, 2200); ++ } while (retry--); ++ } ++ + if (ret) + return ret; + +@@ -965,7 +1018,6 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, + static s32 tas_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { +- s32 ret; + struct snd_soc_component *component = dai->component; + struct tas2783_prv *tas_dev = + snd_soc_component_get_drvdata(component); +@@ -974,14 +1026,11 @@ static s32 tas_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + + sdw_stream_remove_slave(tas_dev->sdw_peripheral, sdw_stream); + +- mutex_lock(&tas_dev->pde_lock); +- ret = regmap_write(tas_dev->regmap, +- SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, +- TAS2783_SDCA_CTL_REQ_POW_STATE, 0), +- TAS2783_SDCA_POW_STATE_OFF); +- mutex_unlock(&tas_dev->pde_lock); +- +- return ret; ++ guard(mutex)(&tas_dev->pde_lock); ++ return regmap_write(tas_dev->regmap, ++ SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, ++ TAS2783_SDCA_CTL_REQ_POW_STATE, 0), ++ TAS2783_SDCA_POW_STATE_OFF); + } + + static const struct snd_soc_dai_ops tas_dai_ops = { +@@ -1098,7 +1147,13 @@ static s32 tas2783_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(tas_dev->regmap, false); +- regcache_sync(tas_dev->regmap); ++ ret = regcache_sync(tas_dev->regmap); ++ if (ret) { ++ regcache_cache_only(tas_dev->regmap, true); ++ regcache_mark_dirty(tas_dev->regmap); ++ return ret; ++ } ++ + return 0; + } + +@@ -1224,9 +1279,23 @@ static s32 tas_update_status(struct sdw_slave *slave, + if (tas_dev->hw_init || tas_dev->status != SDW_SLAVE_ATTACHED) + return 0; + +- /* updated the cache data to device */ + regcache_cache_only(tas_dev->regmap, false); +- regcache_sync(tas_dev->regmap); ++ ++ /* ++ * The device is attaching uninitialized: either this is the first ++ * attach, or it lost power (and with it all register and DSP state) ++ * while the controller was power-gated during system suspend. The ++ * cache still holds the pre-suspend values, and tas_io_init() below ++ * resets the device via TAS2783_SW_RESET anyway, so syncing it back ++ * is both useless and harmful: later read-modify-write updates would ++ * compare against stale data and skip the hardware write. ++ * ++ * Drop the cache instead, so that subsequent accesses see the real ++ * hardware state. Syncing after the reset is not an option either: ++ * the cache accepts registers for which tas2783_sdca_mbq_size() ++ * returns 0, and writing those back fails with -EINVAL. ++ */ ++ regcache_drop_region(tas_dev->regmap, 0, UINT_MAX); + + /* perform I/O transfers required for Slave initialization */ + return tas_io_init(&slave->dev, slave); +@@ -1329,7 +1398,7 @@ static s32 tas_sdw_probe(struct sdw_slave *peripheral, + function_data->desc = &peripheral->sdca_data.function[i]; + + /* Parse the function */ +- ret = sdca_parse_function(dev, peripheral, function_data); ++ ret = sdca_parse_function(dev, function_data); + if (!ret) + tas_dev->sa_func_data = function_data; + else +diff --git a/sound/soc/codecs/tas5805m.c b/sound/soc/codecs/tas5805m.c +--- a/sound/soc/codecs/tas5805m.c ++++ b/sound/soc/codecs/tas5805m.c +@@ -12,6 +12,7 @@ + // + // It has been simplified a little and reworked for the 5.x ALSA SoC API. + ++#include + #include + #include + #include +@@ -230,10 +231,9 @@ static int tas5805m_vol_get(struct snd_kcontrol *kcontrol, + struct tas5805m_priv *tas5805m = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + ucontrol->value.integer.value[0] = tas5805m->vol[0]; + ucontrol->value.integer.value[1] = tas5805m->vol[1]; +- mutex_unlock(&tas5805m->lock); + + return 0; + } +@@ -249,13 +249,12 @@ static int tas5805m_vol_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct tas5805m_priv *tas5805m = + snd_soc_component_get_drvdata(component); +- int ret = 0; + + if (!(volume_is_valid(ucontrol->value.integer.value[0]) && + volume_is_valid(ucontrol->value.integer.value[1]))) + return -EINVAL; + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + if (tas5805m->vol[0] != ucontrol->value.integer.value[0] || + tas5805m->vol[1] != ucontrol->value.integer.value[1]) { + tas5805m->vol[0] = ucontrol->value.integer.value[0]; +@@ -265,11 +264,10 @@ static int tas5805m_vol_put(struct snd_kcontrol *kcontrol, + tas5805m->is_powered); + if (tas5805m->is_powered) + tas5805m_refresh(tas5805m); +- ret = 1; ++ return 1; + } +- mutex_unlock(&tas5805m->lock); + +- return ret; ++ return 0; + } + + static const struct snd_kcontrol_new tas5805m_snd_controls[] = { +@@ -332,7 +330,7 @@ static void do_work(struct work_struct *work) + + dev_dbg(&tas5805m->i2c->dev, "DSP startup\n"); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + /* We mustn't issue any I2C transactions until the I2S + * clock is stable. Furthermore, we must allow a 5ms + * delay after the first set of register writes to +@@ -345,7 +343,6 @@ static void do_work(struct work_struct *work) + + tas5805m->is_powered = true; + tas5805m_refresh(tas5805m); +- mutex_unlock(&tas5805m->lock); + } + + static int tas5805m_dac_event(struct snd_soc_dapm_widget *w, +@@ -362,7 +359,7 @@ static int tas5805m_dac_event(struct snd_soc_dapm_widget *w, + dev_dbg(component->dev, "DSP shutdown\n"); + cancel_work_sync(&tas5805m->work); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + if (tas5805m->is_powered) { + tas5805m->is_powered = false; + +@@ -379,7 +376,6 @@ static int tas5805m_dac_event(struct snd_soc_dapm_widget *w, + + regmap_write(rm, REG_DEVICE_CTRL_2, DCTRL2_MODE_HIZ); + } +- mutex_unlock(&tas5805m->lock); + } + + return 0; +@@ -414,14 +410,13 @@ static int tas5805m_mute(struct snd_soc_dai *dai, int mute, int direction) + struct tas5805m_priv *tas5805m = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + dev_dbg(component->dev, "set mute=%d (is_powered=%d)\n", + mute, tas5805m->is_powered); + + tas5805m->is_muted = mute; + if (tas5805m->is_powered) + tas5805m_refresh(tas5805m); +- mutex_unlock(&tas5805m->lock); + + return 0; + } +@@ -462,7 +457,6 @@ static int tas5805m_i2c_probe(struct i2c_client *i2c) + struct tas5805m_priv *tas5805m; + char filename[128]; + const char *config_name; +- const struct firmware *fw; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &tas5805m_regmap); +@@ -507,24 +501,20 @@ static int tas5805m_i2c_probe(struct i2c_client *i2c) + + snprintf(filename, sizeof(filename), "tas5805m_dsp_%s.bin", + config_name); ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, filename, dev); + if (ret) + return ret; + + if ((fw->size < 2) || (fw->size & 1)) { + dev_err(dev, "firmware is invalid\n"); +- release_firmware(fw); + return -EINVAL; + } + + tas5805m->dsp_cfg_len = fw->size; + tas5805m->dsp_cfg_data = devm_kmemdup(dev, fw->data, fw->size, GFP_KERNEL); +- if (!tas5805m->dsp_cfg_data) { +- release_firmware(fw); ++ if (!tas5805m->dsp_cfg_data) + return -ENOMEM; +- } +- +- release_firmware(fw); + + /* Do the first part of the power-on here, while we can expect + * the I2S interface to be quiet. We must raise PDN# and then +diff --git a/sound/soc/codecs/tas675x.c b/sound/soc/codecs/tas675x.c +--- a/sound/soc/codecs/tas675x.c ++++ b/sound/soc/codecs/tas675x.c +@@ -1924,15 +1924,6 @@ static const struct reg_default tas675x_reg_defaults[] = { + { TAS675X_PWM_PHASE_M_CTRL_CH2_REG, 0x00 }, + { TAS675X_PWM_PHASE_M_CTRL_CH3_REG, 0x00 }, + { TAS675X_PWM_PHASE_M_CTRL_CH4_REG, 0x00 }, +- { TAS675X_DC_LDG_CTRL_REG, 0x00 }, +- { TAS675X_DC_LDG_LO_CTRL_REG, 0x00 }, +- { TAS675X_DC_LDG_TIME_CTRL_REG, 0x00 }, +- { TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 0x11 }, +- { TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 0x11 }, +- { TAS675X_AC_LDG_CTRL_REG, 0x10 }, +- { TAS675X_TWEETER_DETECT_CTRL_REG, 0x08 }, +- { TAS675X_TWEETER_DETECT_THRESH_REG, 0x00 }, +- { TAS675X_AC_LDG_FREQ_CTRL_REG, 0xC8 }, + { TAS675X_REPORT_ROUTING_1_REG, 0x00 }, + { TAS675X_OTSD_RECOVERY_EN_REG, 0x00 }, + { TAS675X_REPORT_ROUTING_2_REG, 0xA2 }, +@@ -1943,6 +1934,15 @@ static const struct reg_default tas675x_reg_defaults[] = { + { TAS675X_GPIO1_OUTPUT_SEL_REG, 0x00 }, + { TAS675X_GPIO2_OUTPUT_SEL_REG, 0x00 }, + { TAS675X_GPIO_CTRL_REG, TAS675X_GPIO_CTRL_RSTVAL }, ++ { TAS675X_DC_LDG_CTRL_REG, 0x00 }, ++ { TAS675X_DC_LDG_LO_CTRL_REG, 0x00 }, ++ { TAS675X_DC_LDG_TIME_CTRL_REG, 0x00 }, ++ { TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 0x11 }, ++ { TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 0x11 }, ++ { TAS675X_AC_LDG_CTRL_REG, 0x10 }, ++ { TAS675X_TWEETER_DETECT_CTRL_REG, 0x08 }, ++ { TAS675X_TWEETER_DETECT_THRESH_REG, 0x00 }, ++ { TAS675X_AC_LDG_FREQ_CTRL_REG, 0xC8 }, + { TAS675X_OTW_CTRL_CH1_CH2_REG, 0x11 }, + { TAS675X_OTW_CTRL_CH3_CH4_REG, 0x11 }, + }; +diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c +--- a/sound/soc/codecs/tlv320aic26.c ++++ b/sound/soc/codecs/tlv320aic26.c +@@ -320,8 +320,14 @@ static int aic26_probe(struct snd_soc_component *component) + return 0; + } + ++static void aic26_remove(struct snd_soc_component *component) ++{ ++ device_remove_file(component->dev, &dev_attr_keyclick); ++} ++ + static const struct snd_soc_component_driver aic26_soc_component_dev = { + .probe = aic26_probe, ++ .remove = aic26_remove, + .controls = aic26_snd_controls, + .num_controls = ARRAY_SIZE(aic26_snd_controls), + .dapm_widgets = tlv320aic26_dapm_widgets, +diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c +--- a/sound/soc/codecs/tlv320aic31xx.c ++++ b/sound/soc/codecs/tlv320aic31xx.c +@@ -1720,18 +1720,14 @@ static int tlv320dac3100_fw_load(struct aic31xx_priv *aic31xx, + static int tlv320dac3100_load_coeffs(struct aic31xx_priv *aic31xx, + const char *fw_name) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw, fw_name, aic31xx->dev); + if (ret) + return ret; + +- ret = tlv320dac3100_fw_load(aic31xx, fw->data, fw->size); +- +- release_firmware(fw); +- +- return ret; ++ return tlv320dac3100_fw_load(aic31xx, fw->data, fw->size); + } + + static int aic31xx_i2c_probe(struct i2c_client *i2c) +diff --git a/sound/soc/codecs/tlv320aic32x4-clk.c b/sound/soc/codecs/tlv320aic32x4-clk.c +--- a/sound/soc/codecs/tlv320aic32x4-clk.c ++++ b/sound/soc/codecs/tlv320aic32x4-clk.c +@@ -1,5 +1,5 @@ +-/* SPDX-License-Identifier: GPL-2.0 +- * ++// SPDX-License-Identifier: GPL-2.0 ++/* + * Clock Tree for the Texas Instruments TLV320AIC32x4 + * + * Copyright 2019 Annaliese McDermond +@@ -9,6 +9,7 @@ + + #include + #include ++#include + #include + #include + +@@ -57,7 +58,7 @@ static void clk_aic32x4_pll_unprepare(struct clk_hw *hw) + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + + regmap_update_bits(pll->regmap, AIC32X4_PLLPR, +- AIC32X4_PLLEN, 0); ++ AIC32X4_PLLEN, 0); + } + + static int clk_aic32x4_pll_is_prepared(struct clk_hw *hw) +@@ -75,7 +76,7 @@ static int clk_aic32x4_pll_is_prepared(struct clk_hw *hw) + } + + static int clk_aic32x4_pll_get_muldiv(struct clk_aic32x4 *pll, +- struct clk_aic32x4_pll_muldiv *settings) ++ struct clk_aic32x4_pll_muldiv *settings) + { + /* Change to use regmap_bulk_read? */ + unsigned int val; +@@ -106,19 +107,19 @@ static int clk_aic32x4_pll_get_muldiv(struct clk_aic32x4 *pll, + } + + static int clk_aic32x4_pll_set_muldiv(struct clk_aic32x4 *pll, +- struct clk_aic32x4_pll_muldiv *settings) ++ struct clk_aic32x4_pll_muldiv *settings) + { + int ret; + /* Change to use regmap_bulk_write for some if not all? */ + + ret = regmap_update_bits(pll->regmap, AIC32X4_PLLPR, +- AIC32X4_PLL_R_MASK, settings->r); ++ AIC32X4_PLL_R_MASK, settings->r); + if (ret < 0) + return ret; + + ret = regmap_update_bits(pll->regmap, AIC32X4_PLLPR, +- AIC32X4_PLL_P_MASK, +- settings->p << AIC32X4_PLL_P_SHIFT); ++ AIC32X4_PLL_P_MASK, ++ settings->p << AIC32X4_PLL_P_SHIFT); + if (ret < 0) + return ret; + +@@ -136,23 +137,21 @@ static int clk_aic32x4_pll_set_muldiv(struct clk_aic32x4 *pll, + return 0; + } + +-static unsigned long clk_aic32x4_pll_calc_rate( +- struct clk_aic32x4_pll_muldiv *settings, +- unsigned long parent_rate) ++static unsigned long clk_aic32x4_pll_calc_rate(struct clk_aic32x4_pll_muldiv *settings, ++ unsigned long parent_rate) + { + u64 rate; + /* + * We scale j by 10000 to account for the decimal part of P and divide + * it back out later. + */ +- rate = (u64) parent_rate * settings->r * +- ((settings->j * 10000) + settings->d); ++ rate = (u64)parent_rate * settings->r * ((settings->j * 10000) + settings->d); + +- return (unsigned long) DIV_ROUND_UP_ULL(rate, settings->p * 10000); ++ return (unsigned long)DIV_ROUND_UP_ULL(rate, settings->p * 10000); + } + + static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, +- unsigned long rate, unsigned long parent_rate) ++ unsigned long rate, unsigned long parent_rate) + { + u64 multiplier; + +@@ -165,14 +164,14 @@ static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, + * of the multiplier. This is because we can't do floating point + * math in the kernel. + */ +- multiplier = (u64) rate * settings->p * 10000; ++ multiplier = (u64)rate * settings->p * 10000; + do_div(multiplier, parent_rate); + + /* + * J can't be over 64, so R can scale this. + * R can't be greater than 4. + */ +- settings->r = ((u32) multiplier / 640000) + 1; ++ settings->r = ((u32)multiplier / 640000) + 1; + if (settings->r > 4) + return -1; + do_div(multiplier, settings->r); +@@ -184,14 +183,14 @@ static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, + return -1; + + /* Figure out the integer part, J, and the fractional part, D. */ +- settings->j = (u32) multiplier / 10000; +- settings->d = (u32) multiplier % 10000; ++ settings->j = (u32)multiplier / 10000; ++ settings->d = (u32)multiplier % 10000; + + return 0; + } + + static unsigned long clk_aic32x4_pll_recalc_rate(struct clk_hw *hw, +- unsigned long parent_rate) ++ unsigned long parent_rate) + { + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + struct clk_aic32x4_pll_muldiv settings; +@@ -220,8 +219,8 @@ static int clk_aic32x4_pll_determine_rate(struct clk_hw *hw, + } + + static int clk_aic32x4_pll_set_rate(struct clk_hw *hw, +- unsigned long rate, +- unsigned long parent_rate) ++ unsigned long rate, ++ unsigned long parent_rate) + { + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + struct clk_aic32x4_pll_muldiv settings; +@@ -236,7 +235,7 @@ static int clk_aic32x4_pll_set_rate(struct clk_hw *hw, + return ret; + + /* 10ms is the delay to wait before the clocks are stable */ +- msleep(10); ++ usleep_range(10000, 20000); + + return 0; + } +@@ -261,7 +260,6 @@ static u8 clk_aic32x4_pll_get_parent(struct clk_hw *hw) + return (val & AIC32X4_PLL_CLKIN_MASK) >> AIC32X4_PLL_CLKIN_SHIFT; + } + +- + static const struct clk_ops aic32x4_pll_ops = { + .prepare = clk_aic32x4_pll_prepare, + .unprepare = clk_aic32x4_pll_unprepare, +@@ -311,11 +309,11 @@ static void clk_aic32x4_div_unprepare(struct clk_hw *hw) + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + + regmap_update_bits(div->regmap, div->reg, +- AIC32X4_DIVEN, 0); ++ AIC32X4_DIVEN, 0); + } + + static int clk_aic32x4_div_set_rate(struct clk_hw *hw, unsigned long rate, +- unsigned long parent_rate) ++ unsigned long parent_rate) + { + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + u8 divisor; +@@ -342,7 +340,7 @@ static int clk_aic32x4_div_determine_rate(struct clk_hw *hw, + } + + static unsigned long clk_aic32x4_div_recalc_rate(struct clk_hw *hw, +- unsigned long parent_rate) ++ unsigned long parent_rate) + { + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + unsigned int val; +@@ -399,7 +397,7 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + { + .name = "pll", + .parent_names = +- (const char* []) { "mclk", "bclk", "gpio", "din" }, ++ (const char *[]) { "mclk", "bclk", "gpio", "din" }, + .num_parents = 4, + .ops = &aic32x4_pll_ops, + .reg = 0, +@@ -414,28 +412,28 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + }, + { + .name = "ndac", +- .parent_names = (const char * []) { "codec_clkin" }, ++ .parent_names = (const char *[]) { "codec_clkin" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_NDAC, + }, + { + .name = "mdac", +- .parent_names = (const char * []) { "ndac" }, ++ .parent_names = (const char *[]) { "ndac" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_MDAC, + }, + { + .name = "nadc", +- .parent_names = (const char * []) { "codec_clkin" }, ++ .parent_names = (const char *[]) { "codec_clkin" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_NADC, + }, + { + .name = "madc", +- .parent_names = (const char * []) { "nadc" }, ++ .parent_names = (const char *[]) { "nadc" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_MADC, +@@ -451,7 +449,7 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + }; + + static struct clk *aic32x4_register_clk(struct device *dev, +- struct aic32x4_clkdesc *desc) ++ struct aic32x4_clkdesc *desc) + { + struct clk_init_data init; + struct clk_aic32x4 *priv; +@@ -464,8 +462,8 @@ static struct clk *aic32x4_register_clk(struct device *dev, + init.flags = 0; + + priv = devm_kzalloc(dev, sizeof(struct clk_aic32x4), GFP_KERNEL); +- if (priv == NULL) +- return (struct clk *) -ENOMEM; ++ if (!priv) ++ return ERR_PTR(-ENOMEM); + + priv->dev = dev; + priv->hw.init = &init; +diff --git a/sound/soc/codecs/tlv320aic32x4-i2c.c b/sound/soc/codecs/tlv320aic32x4-i2c.c +--- a/sound/soc/codecs/tlv320aic32x4-i2c.c ++++ b/sound/soc/codecs/tlv320aic32x4-i2c.c +@@ -1,11 +1,10 @@ +-/* SPDX-License-Identifier: GPL-2.0 +- * ++// SPDX-License-Identifier: GPL-2.0 ++/* + * Copyright 2011-2019 NW Digital Radio + * + * Author: Annaliese McDermond + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. +- * + */ + + #include +@@ -16,17 +15,23 @@ + + #include "tlv320aic32x4.h" + ++static const struct regmap_config aic32x4_i2c_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = AIC32X4_REFPOWERUP, ++ .ranges = aic32x4_regmap_pages, ++ .num_ranges = 1, ++}; ++ + static int aic32x4_i2c_probe(struct i2c_client *i2c) + { + struct regmap *regmap; +- struct regmap_config config; + enum aic32x4_type type; + +- config = aic32x4_regmap_config; +- config.reg_bits = 8; +- config.val_bits = 8; ++ regmap = devm_regmap_init_i2c(i2c, &aic32x4_i2c_regmap_config); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); + +- regmap = devm_regmap_init_i2c(i2c, &config); + type = (uintptr_t)i2c_get_match_data(i2c); + + return aic32x4_probe(&i2c->dev, regmap, type); +diff --git a/sound/soc/codecs/tlv320aic32x4-spi.c b/sound/soc/codecs/tlv320aic32x4-spi.c +--- a/sound/soc/codecs/tlv320aic32x4-spi.c ++++ b/sound/soc/codecs/tlv320aic32x4-spi.c +@@ -1,11 +1,10 @@ +-/* SPDX-License-Identifier: GPL-2.0 +- * ++// SPDX-License-Identifier: GPL-2.0 ++/* + * Copyright 2011-2019 NW Digital Radio + * + * Author: Annaliese McDermond + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. +- * + */ + + #include +@@ -16,19 +15,25 @@ + + #include "tlv320aic32x4.h" + ++static const struct regmap_config aic32x4_spi_regmap_config = { ++ .reg_bits = 7, ++ .pad_bits = 1, ++ .val_bits = 8, ++ .read_flag_mask = 0x01, ++ .max_register = AIC32X4_REFPOWERUP, ++ .ranges = aic32x4_regmap_pages, ++ .num_ranges = 1, ++}; ++ + static int aic32x4_spi_probe(struct spi_device *spi) + { + struct regmap *regmap; +- struct regmap_config config; + enum aic32x4_type type; + +- config = aic32x4_regmap_config; +- config.reg_bits = 7; +- config.pad_bits = 1; +- config.val_bits = 8; +- config.read_flag_mask = 0x01; ++ regmap = devm_regmap_init_spi(spi, &aic32x4_spi_regmap_config); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); + +- regmap = devm_regmap_init_spi(spi, &config); + type = (uintptr_t)spi_get_device_match_data(spi); + + return aic32x4_probe(&spi->dev, regmap, type); +diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c +--- a/sound/soc/codecs/tlv320aic32x4.c ++++ b/sound/soc/codecs/tlv320aic32x4.c +@@ -1,7 +1,5 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + /* +- * linux/sound/soc/codecs/tlv320aic32x4.c +- * + * Copyright 2011 Vista Silicon S.L. + * + * Author: Javier Martin +@@ -28,7 +26,6 @@ + #include + #include + #include +-#include + + #include "tlv320aic32x4.h" + +@@ -45,7 +42,8 @@ struct aic32x4_priv { + struct regulator *supply_dv; + struct regulator *supply_av; + +- struct aic32x4_setup_data *setup; ++ unsigned int gpio_func[5]; ++ + struct device *dev; + enum aic32x4_type type; + +@@ -75,7 +73,7 @@ static int aic32x4_reset_adc(struct snd_soc_dapm_widget *w, + }; + + static int mic_bias_event(struct snd_soc_dapm_widget *w, +- struct snd_kcontrol *kcontrol, int event) ++ struct snd_kcontrol *kcontrol, int event) + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + +@@ -83,25 +81,23 @@ static int mic_bias_event(struct snd_soc_dapm_widget *w, + case SND_SOC_DAPM_POST_PMU: + /* Change Mic Bias Registor */ + snd_soc_component_update_bits(component, AIC32X4_MICBIAS, +- AIC32x4_MICBIAS_MASK, +- AIC32X4_MICBIAS_LDOIN | +- AIC32X4_MICBIAS_2075V); +- printk(KERN_DEBUG "%s: Mic Bias will be turned ON\n", __func__); ++ AIC32x4_MICBIAS_MASK, ++ AIC32X4_MICBIAS_LDOIN | ++ AIC32X4_MICBIAS_2075V); ++ dev_dbg(component->dev, "Mic Bias will be turned ON\n"); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, AIC32X4_MICBIAS, +- AIC32x4_MICBIAS_MASK, 0); +- printk(KERN_DEBUG "%s: Mic Bias will be turned OFF\n", +- __func__); ++ AIC32x4_MICBIAS_MASK, 0); ++ dev_dbg(component->dev, "Mic Bias will be turned OFF\n"); + break; + } + + return 0; + } + +- + static int aic32x4_get_mfp1_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -114,7 +110,7 @@ static int aic32x4_get_mfp1_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -123,8 +119,7 @@ static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, + val = snd_soc_component_read(component, AIC32X4_DOUTCTL); + gpio_check = (val & AIC32X4_MFP_GPIO_ENABLED); + if (gpio_check != AIC32X4_MFP_GPIO_ENABLED) { +- printk(KERN_ERR "%s: MFP2 is not configure as a GPIO output\n", +- __func__); ++ dev_err(component->dev, "MFP2 is not configure as a GPIO output\n"); + return -EINVAL; + } + +@@ -142,7 +137,7 @@ static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_get_mfp3_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -155,7 +150,7 @@ static int aic32x4_get_mfp3_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -164,8 +159,7 @@ static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, + val = snd_soc_component_read(component, AIC32X4_MISOCTL); + gpio_check = (val & AIC32X4_MFP_GPIO_ENABLED); + if (gpio_check != AIC32X4_MFP_GPIO_ENABLED) { +- printk(KERN_ERR "%s: MFP4 is not configure as a GPIO output\n", +- __func__); ++ dev_err(component->dev, "MFP4 is not configure as a GPIO output\n"); + return -EINVAL; + } + +@@ -183,7 +177,7 @@ static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_get_mfp5_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -195,7 +189,7 @@ static int aic32x4_get_mfp5_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -204,8 +198,7 @@ static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, + val = snd_soc_component_read(component, AIC32X4_GPIOCTL); + gpio_check = (val & AIC32X4_MFP5_GPIO_OUTPUT); + if (gpio_check != AIC32X4_MFP5_GPIO_OUTPUT) { +- printk(KERN_ERR "%s: MFP5 is not configure as a GPIO output\n", +- __func__); ++ dev_err(component->dev, "MFP5 is not configure as a GPIO output\n"); + return -EINVAL; + } + +@@ -222,25 +215,31 @@ static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, + return 0; + }; + +-static const struct snd_kcontrol_new aic32x4_mfp1[] = { +- SOC_SINGLE_BOOL_EXT("MFP1 GPIO", 0, aic32x4_get_mfp1_gpio, NULL), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp2[] = { +- SOC_SINGLE_BOOL_EXT("MFP2 GPIO", 0, NULL, aic32x4_set_mfp2_gpio), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp3[] = { +- SOC_SINGLE_BOOL_EXT("MFP3 GPIO", 0, aic32x4_get_mfp3_gpio, NULL), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp4[] = { +- SOC_SINGLE_BOOL_EXT("MFP4 GPIO", 0, NULL, aic32x4_set_mfp4_gpio), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp5[] = { +- SOC_SINGLE_BOOL_EXT("MFP5 GPIO", 0, aic32x4_get_mfp5_gpio, +- aic32x4_set_mfp5_gpio), ++static const struct { ++ unsigned int reg; ++ struct snd_kcontrol_new ctrl; ++} aic32x4_mfp_cfg[] = { ++ { ++ .reg = AIC32X4_DINCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP1 GPIO", 0, aic32x4_get_mfp1_gpio, NULL), ++ }, ++ { ++ .reg = AIC32X4_DOUTCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP2 GPIO", 0, NULL, aic32x4_set_mfp2_gpio), ++ }, ++ { ++ .reg = AIC32X4_SCLKCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP3 GPIO", 0, aic32x4_get_mfp3_gpio, NULL), ++ }, ++ { ++ .reg = AIC32X4_MISOCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP4 GPIO", 0, NULL, aic32x4_set_mfp4_gpio), ++ }, ++ { ++ .reg = AIC32X4_GPIOCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP5 GPIO", 0, aic32x4_get_mfp5_gpio, ++ aic32x4_set_mfp5_gpio), ++ }, + }; + + /* 0dB min, 0.5dB steps */ +@@ -270,53 +269,42 @@ static SOC_ENUM_SINGLE_DECL(r_ptm_enum, AIC32X4_RPLAYBACK, 2, ptm_text); + + static const struct snd_kcontrol_new aic32x4_snd_controls[] = { + SOC_DOUBLE_R_S_TLV("PCM Playback Volume", AIC32X4_LDACVOL, +- AIC32X4_RDACVOL, 0, -0x7f, 0x30, 7, 0, tlv_pcm), ++ AIC32X4_RDACVOL, 0, -0x7f, 0x30, 7, 0, tlv_pcm), + SOC_ENUM("DAC Left Playback PowerTune Switch", l_ptm_enum), + SOC_ENUM("DAC Right Playback PowerTune Switch", r_ptm_enum), + SOC_DOUBLE_R_S_TLV("HP Driver Gain Volume", AIC32X4_HPLGAIN, +- AIC32X4_HPRGAIN, 0, -0x6, 0x1d, 5, 0, +- tlv_driver_gain), ++ AIC32X4_HPRGAIN, 0, -0x6, 0x1d, 5, 0, tlv_driver_gain), + SOC_DOUBLE_R_S_TLV("LO Driver Gain Volume", AIC32X4_LOLGAIN, +- AIC32X4_LORGAIN, 0, -0x6, 0x1d, 5, 0, +- tlv_driver_gain), ++ AIC32X4_LORGAIN, 0, -0x6, 0x1d, 5, 0, tlv_driver_gain), + SOC_DOUBLE_R("HP DAC Playback Switch", AIC32X4_HPLGAIN, +- AIC32X4_HPRGAIN, 6, 0x01, 1), ++ AIC32X4_HPRGAIN, 6, 0x01, 1), + SOC_DOUBLE_R("LO DAC Playback Switch", AIC32X4_LOLGAIN, +- AIC32X4_LORGAIN, 6, 0x01, 1), ++ AIC32X4_LORGAIN, 6, 0x01, 1), + SOC_ENUM("LO Playback Common Mode Switch", lo_cm_enum), + SOC_DOUBLE_R("Mic PGA Switch", AIC32X4_LMICPGAVOL, +- AIC32X4_RMICPGAVOL, 7, 0x01, 1), ++ AIC32X4_RMICPGAVOL, 7, 0x01, 1), + + SOC_SINGLE("ADCFGA Left Mute Switch", AIC32X4_ADCFGA, 7, 1, 0), + SOC_SINGLE("ADCFGA Right Mute Switch", AIC32X4_ADCFGA, 3, 1, 0), + + SOC_DOUBLE_R_S_TLV("ADC Level Volume", AIC32X4_LADCVOL, +- AIC32X4_RADCVOL, 0, -0x18, 0x28, 6, 0, tlv_adc_vol), ++ AIC32X4_RADCVOL, 0, -0x18, 0x28, 6, 0, tlv_adc_vol), + SOC_DOUBLE_R_TLV("PGA Level Volume", AIC32X4_LMICPGAVOL, +- AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5), ++ AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5), + + SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0), + + SOC_SINGLE("AGC Left Switch", AIC32X4_LAGC1, 7, 1, 0), + SOC_SINGLE("AGC Right Switch", AIC32X4_RAGC1, 7, 1, 0), +- SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1, +- 4, 0x07, 0), +- SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1, +- 0, 0x03, 0), +- SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2, +- 6, 0x03, 0), +- SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2, +- 1, 0x1F, 0), +- SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3, +- 0, 0x7F, 0), +- SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4, +- 3, 0x1F, 0), +- SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5, +- 3, 0x1F, 0), +- SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6, +- 0, 0x1F, 0), +- SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7, +- 0, 0x0F, 0), ++ SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1, 4, 0x07, 0), ++ SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1, 0, 0x03, 0), ++ SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2, 6, 0x03, 0), ++ SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2, 1, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3, 0, 0x7F, 0), ++ SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4, 3, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5, 3, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6, 0, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7, 0, 0x0F, 0), + }; + + static const struct snd_kcontrol_new hpl_output_mixer_controls[] = { +@@ -354,21 +342,27 @@ static SOC_ENUM_SINGLE_DECL(in3r_lpga_n_enum, AIC32X4_LMICPGANIN, 2, resistor_te + static const struct snd_kcontrol_new in1l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L L+ Switch", in1l_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in2l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L L+ Switch", in2l_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in3l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L L+ Switch", in3l_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in1r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R L+ Switch", in1r_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new cml_to_lmixer_controls[] = { + SOC_DAPM_ENUM("CM_L L- Switch", cml_lpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in2r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R L- Switch", in2r_lpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in3r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R L- Switch", in3r_lpga_n_enum), + }; +@@ -385,21 +379,27 @@ static SOC_ENUM_SINGLE_DECL(in3l_rpga_n_enum, AIC32X4_RMICPGANIN, 2, resistor_te + static const struct snd_kcontrol_new in1r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R R+ Switch", in1r_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in2r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R R+ Switch", in2r_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in3r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R R+ Switch", in3r_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in2l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L R+ Switch", in2l_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new cmr_to_rmixer_controls[] = { + SOC_DAPM_ENUM("CM_R R- Switch", cmr_rpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in1l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L R- Switch", in1l_rpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in3l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L R- Switch", in3l_rpga_n_enum), + }; +@@ -428,38 +428,38 @@ static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { + + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", AIC32X4_ADCSETUP, 6, 0), + SND_SOC_DAPM_MUX("IN1_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in1r_to_rmixer_controls), ++ in1r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in2r_to_rmixer_controls), ++ in2r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in3r_to_rmixer_controls), ++ in3r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in2l_to_rmixer_controls), ++ in2l_to_rmixer_controls), + SND_SOC_DAPM_MUX("CM_R to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- cmr_to_rmixer_controls), ++ cmr_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN1_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in1l_to_rmixer_controls), ++ in1l_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in3l_to_rmixer_controls), ++ in3l_to_rmixer_controls), + + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0), + SND_SOC_DAPM_MUX("IN1_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in1l_to_lmixer_controls), ++ in1l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in2l_to_lmixer_controls), ++ in2l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in3l_to_lmixer_controls), ++ in3l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN1_R to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in1r_to_lmixer_controls), ++ in1r_to_lmixer_controls), + SND_SOC_DAPM_MUX("CM_L to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- cml_to_lmixer_controls), ++ cml_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in2r_to_lmixer_controls), ++ in2r_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in3r_to_lmixer_controls), ++ in3r_to_lmixer_controls), + + SND_SOC_DAPM_SUPPLY("Mic Bias", AIC32X4_MICBIAS, 6, 0, mic_bias_event, +- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_POST("ADC Reset", aic32x4_reset_adc), + +@@ -575,7 +575,7 @@ static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { + {"IN3_R to Left Mixer Negative Resistor", "40 kOhm", "IN3_R"}, + }; + +-static const struct regmap_range_cfg aic32x4_regmap_pages[] = { ++const struct regmap_range_cfg aic32x4_regmap_pages[] = { + { + .selector_reg = 0, + .selector_mask = 0xff, +@@ -585,13 +585,7 @@ static const struct regmap_range_cfg aic32x4_regmap_pages[] = { + .range_max = AIC32X4_REFPOWERUP, + }, + }; +- +-const struct regmap_config aic32x4_regmap_config = { +- .max_register = AIC32X4_REFPOWERUP, +- .ranges = aic32x4_regmap_pages, +- .num_ranges = ARRAY_SIZE(aic32x4_regmap_pages), +-}; +-EXPORT_SYMBOL(aic32x4_regmap_config); ++EXPORT_SYMBOL_GPL(aic32x4_regmap_pages); + + static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +@@ -624,7 +618,7 @@ static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) + case SND_SOC_DAIFMT_CBC_CFC: + break; + default: +- printk(KERN_ERR "aic32x4: invalid clock provider\n"); ++ dev_err(component->dev, "invalid clock provider\n"); + return -EINVAL; + } + +@@ -651,19 +645,20 @@ static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) + AIC32X4_IFACE1_DATATYPE_SHIFT); + break; + default: +- printk(KERN_ERR "aic32x4: invalid DAI interface format\n"); ++ dev_err(component->dev, "invalid DAI interface format\n"); + return -EINVAL; + } + + aic32x4->fmt = fmt; + + snd_soc_component_update_bits(component, AIC32X4_IFACE1, +- AIC32X4_IFACE1_DATATYPE_MASK | +- AIC32X4_IFACE1_MASTER_MASK, iface_reg_1); ++ AIC32X4_IFACE1_DATATYPE_MASK | ++ AIC32X4_IFACE1_MASTER_MASK, ++ iface_reg_1); + snd_soc_component_update_bits(component, AIC32X4_IFACE2, +- AIC32X4_DATA_OFFSET_MASK, iface_reg_2); ++ AIC32X4_DATA_OFFSET_MASK, iface_reg_2); + snd_soc_component_update_bits(component, AIC32X4_IFACE3, +- AIC32X4_BCLKINV_MASK, iface_reg_3); ++ AIC32X4_BCLKINV_MASK, iface_reg_3); + + return 0; + } +@@ -676,14 +671,13 @@ static int aic32x4_set_aosr(struct snd_soc_component *component, u8 aosr) + static int aic32x4_set_dosr(struct snd_soc_component *component, u16 dosr) + { + snd_soc_component_write(component, AIC32X4_DOSRMSB, dosr >> 8); +- snd_soc_component_write(component, AIC32X4_DOSRLSB, +- (dosr & 0xff)); ++ snd_soc_component_write(component, AIC32X4_DOSRLSB, dosr & 0xff); + + return 0; + } + + static int aic32x4_set_processing_blocks(struct snd_soc_component *component, +- u8 r_block, u8 p_block) ++ u8 r_block, u8 p_block) + { + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + +@@ -703,6 +697,45 @@ static int aic32x4_set_processing_blocks(struct snd_soc_component *component, + return 0; + } + ++static int aic32x4_configure_rate(struct snd_soc_component *component, ++ unsigned int rate, u8 *aosr, u8 *adc_rc, ++ u8 *dac_rc, u8 *dosr_inc) ++{ ++ struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); ++ u8 prb_rx, prb_tx; ++ ++ if (rate <= 48000) { ++ *aosr = 128; ++ *adc_rc = 6; ++ *dac_rc = 8; ++ *dosr_inc = 8; ++ prb_rx = 1; ++ prb_tx = 1; ++ } else if (rate <= 96000) { ++ *aosr = 64; ++ *adc_rc = 6; ++ *dac_rc = 8; ++ *dosr_inc = 4; ++ prb_rx = 1; ++ prb_tx = (aic32x4->type == AIC32X4_TYPE_TAS2505) ? 1 : 9; ++ } else if (rate == 192000) { ++ *aosr = 32; ++ *adc_rc = 3; ++ *dac_rc = 4; ++ *dosr_inc = 2; ++ prb_rx = 13; ++ prb_tx = (aic32x4->type == AIC32X4_TYPE_TAS2505) ? 1 : 19; ++ } else { ++ dev_err(component->dev, "Sampling rate %u not supported\n", rate); ++ return -EINVAL; ++ } ++ ++ if (aic32x4->type == AIC32X4_TYPE_TAS2505) ++ prb_rx = 0; ++ ++ return aic32x4_set_processing_blocks(component, prb_rx, prb_tx); ++} ++ + static int aic32x4_setup_clocks(struct snd_soc_component *component, + unsigned int sample_rate, unsigned int channels, + unsigned int bit_depth) +@@ -717,7 +750,7 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, + unsigned long adc_clock_rate, dac_clock_rate; + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "pll" }, + { .id = "nadc" }, + { .id = "madc" }, +@@ -729,37 +762,11 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, + if (ret) + return ret; + +- if (sample_rate <= 48000) { +- aosr = 128; +- adc_resource_class = 6; +- dac_resource_class = 8; +- dosr_increment = 8; +- if (aic32x4->type == AIC32X4_TYPE_TAS2505) +- aic32x4_set_processing_blocks(component, 0, 1); +- else +- aic32x4_set_processing_blocks(component, 1, 1); +- } else if (sample_rate <= 96000) { +- aosr = 64; +- adc_resource_class = 6; +- dac_resource_class = 8; +- dosr_increment = 4; +- if (aic32x4->type == AIC32X4_TYPE_TAS2505) +- aic32x4_set_processing_blocks(component, 0, 1); +- else +- aic32x4_set_processing_blocks(component, 1, 9); +- } else if (sample_rate == 192000) { +- aosr = 32; +- adc_resource_class = 3; +- dac_resource_class = 4; +- dosr_increment = 2; +- if (aic32x4->type == AIC32X4_TYPE_TAS2505) +- aic32x4_set_processing_blocks(component, 0, 1); +- else +- aic32x4_set_processing_blocks(component, 13, 19); +- } else { +- dev_err(component->dev, "Sampling rate not supported\n"); +- return -EINVAL; +- } ++ ret = aic32x4_configure_rate(component, sample_rate, &aosr, ++ &adc_resource_class, &dac_resource_class, ++ &dosr_increment); ++ if (ret) ++ return ret; + + /* PCM over I2S is always 2-channel */ + if ((aic32x4->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S) +@@ -781,50 +788,39 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, + (min_mdac * dosr * sample_rate); + for (mdac = min_mdac; mdac <= 128; ++mdac) { + for (ndac = max_ndac; ndac > 0; --ndac) { +- dac_clock_rate = ndac * mdac * dosr * +- sample_rate; +- if (dac_clock_rate == adc_clock_rate) { +- if (clk_round_rate(clocks[0].clk, dac_clock_rate) == 0) +- continue; ++ dac_clock_rate = ndac * mdac * dosr * sample_rate; ++ if (dac_clock_rate != adc_clock_rate) ++ continue; + +- clk_set_rate(clocks[0].clk, +- dac_clock_rate); ++ if (clk_round_rate(clocks[0].clk, dac_clock_rate) == 0) ++ continue; + +- clk_set_rate(clocks[1].clk, +- sample_rate * aosr * +- madc); +- clk_set_rate(clocks[2].clk, +- sample_rate * aosr); +- aic32x4_set_aosr(component, +- aosr); ++ clk_set_rate(clocks[0].clk, dac_clock_rate); + +- clk_set_rate(clocks[3].clk, +- sample_rate * dosr * +- mdac); +- clk_set_rate(clocks[4].clk, +- sample_rate * dosr); +- aic32x4_set_dosr(component, +- dosr); ++ clk_set_rate(clocks[1].clk, sample_rate * aosr * madc); ++ clk_set_rate(clocks[2].clk, sample_rate * aosr); ++ aic32x4_set_aosr(component, aosr); + +- clk_set_rate(clocks[5].clk, +- sample_rate * channels * +- bit_depth); ++ clk_set_rate(clocks[3].clk, sample_rate * dosr * mdac); ++ clk_set_rate(clocks[4].clk, sample_rate * dosr); ++ aic32x4_set_dosr(component, dosr); + +- return 0; +- } ++ clk_set_rate(clocks[5].clk, ++ sample_rate * channels * bit_depth); ++ ++ return 0; + } + } + } + } + +- dev_err(component->dev, +- "Could not set clocks to support sample rate.\n"); ++ dev_err(component->dev, "Could not set clocks to support sample rate.\n"); + return -EINVAL; + } + + static int aic32x4_hw_params(struct snd_pcm_substream *substream, +- struct snd_pcm_hw_params *params, +- struct snd_soc_dai *dai) ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) + { + struct snd_soc_component *component = dai->component; + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); +@@ -837,24 +833,20 @@ static int aic32x4_hw_params(struct snd_pcm_substream *substream, + + switch (params_physical_width(params)) { + case 16: +- iface1_reg |= (AIC32X4_WORD_LEN_16BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_16BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 20: +- iface1_reg |= (AIC32X4_WORD_LEN_20BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_20BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 24: +- iface1_reg |= (AIC32X4_WORD_LEN_24BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_24BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 32: +- iface1_reg |= (AIC32X4_WORD_LEN_32BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_32BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + } + snd_soc_component_update_bits(component, AIC32X4_IFACE1, +- AIC32X4_IFACE1_DATALEN_MASK, iface1_reg); ++ AIC32X4_IFACE1_DATALEN_MASK, iface1_reg); + + if (params_channels(params) == 1) { + dacsetup_reg = AIC32X4_RDAC2LCHN | AIC32X4_LDAC2LCHN; +@@ -865,7 +857,7 @@ static int aic32x4_hw_params(struct snd_pcm_substream *substream, + dacsetup_reg = AIC32X4_LDAC2LCHN | AIC32X4_RDAC2RCHN; + } + snd_soc_component_update_bits(component, AIC32X4_DACSETUP, +- AIC32X4_DAC_CHAN_MASK, dacsetup_reg); ++ AIC32X4_DAC_CHAN_MASK, dacsetup_reg); + + return 0; + } +@@ -875,7 +867,7 @@ static int aic32x4_mute(struct snd_soc_dai *dai, int mute, int direction) + struct snd_soc_component *component = dai->component; + + snd_soc_component_update_bits(component, AIC32X4_DACMUTE, +- AIC32X4_MUTEON, mute ? AIC32X4_MUTEON : 0); ++ AIC32X4_MUTEON, mute ? AIC32X4_MUTEON : 0); + + return 0; + } +@@ -886,7 +878,7 @@ static int aic32x4_set_bias_level(struct snd_soc_component *component, + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "madc" }, + { .id = "mdac" }, + { .id = "bdiv" }, +@@ -955,44 +947,14 @@ static void aic32x4_setup_gpios(struct snd_soc_component *component) + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + + /* setup GPIO functions */ +- /* MFP1 */ +- if (aic32x4->setup->gpio_func[0] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_DINCTL, +- aic32x4->setup->gpio_func[0]); +- snd_soc_add_component_controls(component, aic32x4_mfp1, +- ARRAY_SIZE(aic32x4_mfp1)); +- } ++ BUILD_BUG_ON(ARRAY_SIZE(aic32x4->gpio_func) != ARRAY_SIZE(aic32x4_mfp_cfg)); ++ for (int i = 0; i < ARRAY_SIZE(aic32x4->gpio_func); i++) { ++ if (aic32x4->gpio_func[i] == AIC32X4_MFPX_DEFAULT_VALUE) ++ continue; + +- /* MFP2 */ +- if (aic32x4->setup->gpio_func[1] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_DOUTCTL, +- aic32x4->setup->gpio_func[1]); +- snd_soc_add_component_controls(component, aic32x4_mfp2, +- ARRAY_SIZE(aic32x4_mfp2)); +- } +- +- /* MFP3 */ +- if (aic32x4->setup->gpio_func[2] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_SCLKCTL, +- aic32x4->setup->gpio_func[2]); +- snd_soc_add_component_controls(component, aic32x4_mfp3, +- ARRAY_SIZE(aic32x4_mfp3)); +- } +- +- /* MFP4 */ +- if (aic32x4->setup->gpio_func[3] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_MISOCTL, +- aic32x4->setup->gpio_func[3]); +- snd_soc_add_component_controls(component, aic32x4_mfp4, +- ARRAY_SIZE(aic32x4_mfp4)); +- } +- +- /* MFP5 */ +- if (aic32x4->setup->gpio_func[4] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_GPIOCTL, +- aic32x4->setup->gpio_func[4]); +- snd_soc_add_component_controls(component, aic32x4_mfp5, +- ARRAY_SIZE(aic32x4_mfp5)); ++ snd_soc_component_write(component, aic32x4_mfp_cfg[i].reg, ++ aic32x4->gpio_func[i]); ++ snd_soc_add_component_controls(component, &aic32x4_mfp_cfg[i].ctrl, 1); + } + } + +@@ -1002,7 +964,7 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + u32 tmp_reg; + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "codec_clkin" }, + { .id = "pll" }, + { .id = "bdiv" }, +@@ -1013,8 +975,7 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + if (ret) + return ret; + +- if (aic32x4->setup) +- aic32x4_setup_gpios(component); ++ aic32x4_setup_gpios(component); + + clk_set_parent(clocks[0].clk, clocks[1].clk); + clk_set_parent(clocks[2].clk, clocks[3].clk); +@@ -1022,7 +983,7 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + /* Power platform configuration */ + if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) { + snd_soc_component_write(component, AIC32X4_MICBIAS, +- AIC32X4_MICBIAS_LDOIN | AIC32X4_MICBIAS_2075V); ++ AIC32X4_MICBIAS_LDOIN | AIC32X4_MICBIAS_2075V); + } + if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) + snd_soc_component_write(component, AIC32X4_PWRCFG, AIC32X4_AVDDWEAKDISABLE); +@@ -1041,16 +1002,16 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + /* Mic PGA routing */ + if (aic32x4->micpga_routing & AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K) + snd_soc_component_write(component, AIC32X4_LMICPGANIN, +- AIC32X4_LMICPGANIN_IN2R_10K); ++ AIC32X4_LMICPGANIN_IN2R_10K); + else + snd_soc_component_write(component, AIC32X4_LMICPGANIN, +- AIC32X4_LMICPGANIN_CM1L_10K); ++ AIC32X4_LMICPGANIN_CM1L_10K); + if (aic32x4->micpga_routing & AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K) + snd_soc_component_write(component, AIC32X4_RMICPGANIN, +- AIC32X4_RMICPGANIN_IN1L_10K); ++ AIC32X4_RMICPGANIN_IN1L_10K); + else + snd_soc_component_write(component, AIC32X4_RMICPGANIN, +- AIC32X4_RMICPGANIN_CM1R_10K); ++ AIC32X4_RMICPGANIN_CM1R_10K); + + /* + * Workaround: for an unknown reason, the ADC needs to be powered up +@@ -1058,8 +1019,8 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + * a HW BUG or some kind of behavior not documented in the datasheet. + */ + tmp_reg = snd_soc_component_read(component, AIC32X4_ADCSETUP); +- snd_soc_component_write(component, AIC32X4_ADCSETUP, tmp_reg | +- AIC32X4_LADC_EN | AIC32X4_RADC_EN); ++ snd_soc_component_write(component, AIC32X4_ADCSETUP, ++ tmp_reg | AIC32X4_LADC_EN | AIC32X4_RADC_EN); + snd_soc_component_write(component, AIC32X4_ADCSETUP, tmp_reg); + + /* +@@ -1102,13 +1063,13 @@ static const struct snd_kcontrol_new aic32x4_tas2505_snd_controls[] = { + SOC_ENUM("DAC Playback PowerTune Switch", l_ptm_enum), + + SOC_SINGLE_TLV("HP Driver Gain Volume", +- AIC32X4_HPLGAIN, 0, 0x74, 1, tlv_tas_driver_gain), ++ AIC32X4_HPLGAIN, 0, 0x74, 1, tlv_tas_driver_gain), + SOC_SINGLE("HP DAC Playback Switch", AIC32X4_HPLGAIN, 6, 1, 1), + + SOC_SINGLE_TLV("Speaker Driver Playback Volume", +- TAS2505_SPKVOL1, 0, 0x74, 1, tlv_tas_driver_gain), ++ TAS2505_SPKVOL1, 0, 0x74, 1, tlv_tas_driver_gain), + SOC_SINGLE_TLV("Speaker Amplifier Playback Volume", +- TAS2505_SPKVOL2, 4, 5, 0, tlv_amp_vol), ++ TAS2505_SPKVOL2, 4, 5, 0, tlv_amp_vol), + + SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0), + }; +@@ -1144,11 +1105,12 @@ static const struct snd_soc_dapm_route aic32x4_tas2505_dapm_routes[] = { + static struct snd_soc_dai_driver aic32x4_tas2505_dai = { + .name = "tas2505-hifi", + .playback = { +- .stream_name = "Playback", +- .channels_min = 1, +- .channels_max = 2, +- .rates = SNDRV_PCM_RATE_8000_96000, +- .formats = AIC32X4_FORMATS,}, ++ .stream_name = "Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = AIC32X4_FORMATS, ++ }, + .ops = &aic32x4_ops, + .symmetric_rate = 1, + }; +@@ -1159,7 +1121,7 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) + u32 tmp_reg; + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "codec_clkin" }, + { .id = "pll" }, + { .id = "bdiv" }, +@@ -1170,8 +1132,7 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) + if (ret) + return ret; + +- if (aic32x4->setup) +- aic32x4_setup_gpios(component); ++ aic32x4_setup_gpios(component); + + clk_set_parent(clocks[0].clk, clocks[1].clk); + clk_set_parent(clocks[2].clk, clocks[3].clk); +@@ -1192,7 +1153,7 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) + snd_soc_component_write(component, AIC32X4_CMMODE, tmp_reg); + + /* +- * Enable the fast charging feature and ensure the needed 40ms ellapsed ++ * Enable the fast charging feature and ensure the needed 40ms elapsed + * before using the analog circuits. + */ + snd_soc_component_write(component, TAS2505_REFPOWERUP, +@@ -1218,17 +1179,10 @@ static const struct snd_soc_component_driver soc_component_dev_aic32x4_tas2505 = + .endianness = 1, + }; + +-static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, +- struct device_node *np) ++static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, struct device_node *np) + { +- struct aic32x4_setup_data *aic32x4_setup; + int ret; + +- aic32x4_setup = devm_kzalloc(aic32x4->dev, sizeof(*aic32x4_setup), +- GFP_KERNEL); +- if (!aic32x4_setup) +- return -ENOMEM; +- + ret = of_property_match_string(np, "clock-names", "mclk"); + if (ret < 0) + return -EINVAL; +@@ -1245,9 +1199,11 @@ static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, + gpiod_set_consumer_name(aic32x4->rstn_gpio, "tlv320aic32x4_rstn"); + } + +- if (of_property_read_u32_array(np, "aic32x4-gpio-func", +- aic32x4_setup->gpio_func, 5) >= 0) +- aic32x4->setup = aic32x4_setup; ++ for (int i = 0; i < ARRAY_SIZE(aic32x4->gpio_func); i++) ++ aic32x4->gpio_func[i] = AIC32X4_MFPX_DEFAULT_VALUE; ++ of_property_read_u32_array(np, "aic32x4-gpio-func", ++ aic32x4->gpio_func, ARRAY_SIZE(aic32x4->gpio_func)); ++ + return 0; + } + +@@ -1266,7 +1222,7 @@ static void aic32x4_disable_regulators(struct aic32x4_priv *aic32x4) + } + + static int aic32x4_setup_regulators(struct device *dev, +- struct aic32x4_priv *aic32x4) ++ struct aic32x4_priv *aic32x4) + { + int ret = 0; + +@@ -1356,12 +1312,8 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap, + struct device_node *np = dev->of_node; + int ret; + +- if (IS_ERR(regmap)) +- return PTR_ERR(regmap); +- +- aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv), +- GFP_KERNEL); +- if (aic32x4 == NULL) ++ aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv), GFP_KERNEL); ++ if (!aic32x4) + return -ENOMEM; + + aic32x4->dev = dev; +@@ -1405,11 +1357,13 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap, + switch (aic32x4->type) { + case AIC32X4_TYPE_TAS2505: + ret = devm_snd_soc_register_component(dev, +- &soc_component_dev_aic32x4_tas2505, &aic32x4_tas2505_dai, 1); ++ &soc_component_dev_aic32x4_tas2505, ++ &aic32x4_tas2505_dai, 1); + break; + default: + ret = devm_snd_soc_register_component(dev, +- &soc_component_dev_aic32x4, &aic32x4_dai, 1); ++ &soc_component_dev_aic32x4, ++ &aic32x4_dai, 1); + } + + if (ret) { +diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h +--- a/sound/soc/codecs/tlv320aic32x4.h ++++ b/sound/soc/codecs/tlv320aic32x4.h +@@ -3,7 +3,6 @@ + * tlv320aic32x4.h + */ + +- + #ifndef _TLV320AIC32X4_H + #define _TLV320AIC32X4_H + +@@ -16,7 +15,7 @@ enum aic32x4_type { + AIC32X4_TYPE_TAS2505, + }; + +-extern const struct regmap_config aic32x4_regmap_config; ++extern const struct regmap_range_cfg aic32x4_regmap_pages[]; + int aic32x4_probe(struct device *dev, struct regmap *regmap, + enum aic32x4_type type); + void aic32x4_remove(struct device *dev); +@@ -24,7 +23,7 @@ int aic32x4_register_clocks(struct device *dev, const char *mclk_name); + + /* tlv320aic32x4 register space (in decimal to match datasheet) */ + +-#define AIC32X4_REG(page, reg) ((page * 128) + reg) ++#define AIC32X4_REG(page, reg) (((page) * 128) + (reg)) + + #define AIC32X4_PSEL AIC32X4_REG(0, 0) + +@@ -234,4 +233,31 @@ int aic32x4_register_clocks(struct device *dev, const char *mclk_name); + #define AIC32X4_MAX_CODEC_CLKIN_FREQ 110000000 + #define AIC32X4_MAX_PLL_CLKIN 20000000 + ++#define AIC32X4_PWR_MICBIAS_2075_LDOIN 0x00000001 ++#define AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE 0x00000002 ++#define AIC32X4_PWR_AIC32X4_LDO_ENABLE 0x00000004 ++#define AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36 0x00000008 ++#define AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED 0x00000010 ++ ++#define AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K 0x00000001 ++#define AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K 0x00000002 ++ ++/* GPIO API */ ++#define AIC32X4_MFPX_DEFAULT_VALUE 0xff ++ ++#define AIC32X4_MFP1_DIN_DISABLED 0 ++#define AIC32X4_MFP1_DIN_ENABLED 0x2 ++#define AIC32X4_MFP1_GPIO_IN 0x4 ++ ++#define AIC32X4_MFP2_GPIO_OUT_LOW 0x0 ++#define AIC32X4_MFP2_GPIO_OUT_HIGH 0x1 ++ ++#define AIC32X4_MFP_GPIO_ENABLED 0x4 ++ ++#define AIC32X4_MFP5_GPIO_DISABLED 0x0 ++#define AIC32X4_MFP5_GPIO_INPUT 0x8 ++#define AIC32X4_MFP5_GPIO_OUTPUT 0xc ++#define AIC32X4_MFP5_GPIO_OUT_LOW 0x0 ++#define AIC32X4_MFP5_GPIO_OUT_HIGH 0x1 ++ + #endif /* _TLV320AIC32X4_H */ +diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c +--- a/sound/soc/codecs/tlv320dac33.c ++++ b/sound/soc/codecs/tlv320dac33.c +@@ -7,6 +7,7 @@ + * Copyright: (C) 2009 Nokia Corporation + */ + ++#include + #include + #include + #include +@@ -236,13 +237,10 @@ static int dac33_write_locked(struct snd_soc_component *component, unsigned int + unsigned int value) + { + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&dac33->mutex); +- ret = dac33_write(component, reg, value); +- mutex_unlock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + +- return ret; ++ return dac33_write(component, reg, value); + } + + #define DAC33_I2C_ADDR_AUTOINC 0x80 +@@ -365,13 +363,13 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + + /* Safety check */ + if (unlikely(power == dac33->chip_power)) { + dev_dbg(component->dev, "Trying to set the same power state: %s\n", + power ? "ON" : "OFF"); +- goto exit; ++ return ret; + } + + if (power) { +@@ -380,7 +378,7 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret != 0) { + dev_err(component->dev, + "Failed to enable supplies: %d\n", ret); +- goto exit; ++ return ret; + } + + if (dac33->reset_gpiod) { +@@ -388,7 +386,7 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret < 0) { + dev_err(&dac33->i2c->dev, + "Failed to set reset GPIO: %d\n", ret); +- goto exit; ++ return ret; + } + } + +@@ -400,7 +398,7 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret < 0) { + dev_err(&dac33->i2c->dev, + "Failed to set reset GPIO: %d\n", ret); +- goto exit; ++ return ret; + } + } + +@@ -409,14 +407,12 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret != 0) { + dev_err(component->dev, + "Failed to disable supplies: %d\n", ret); +- goto exit; ++ return ret; + } + + dac33->chip_power = 0; + } + +-exit: +- mutex_unlock(&dac33->mutex); + return ret; + } + +@@ -659,7 +655,6 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + { + struct snd_soc_component *component = dac33->component; + unsigned int delay; +- unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: +@@ -667,10 +662,10 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + DAC33_THRREG(dac33->nsample)); + + /* Take the timestamps */ +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp2 = ktime_to_us(ktime_get()); +- dac33->t_stamp1 = dac33->t_stamp2; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ dac33->t_stamp2 = ktime_to_us(ktime_get()); ++ dac33->t_stamp1 = dac33->t_stamp2; ++ } + + dac33_write16(component, DAC33_PREFILL_MSB, + DAC33_THRREG(dac33->alarm_threshold)); +@@ -682,11 +677,11 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + break; + case DAC33_FIFO_MODE7: + /* Take the timestamp */ +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp1 = ktime_to_us(ktime_get()); +- /* Move back the timestamp with drain time */ +- dac33->t_stamp1 -= dac33->mode7_us_to_lthr; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ dac33->t_stamp1 = ktime_to_us(ktime_get()); ++ /* Move back the timestamp with drain time */ ++ dac33->t_stamp1 -= dac33->mode7_us_to_lthr; ++ } + + dac33_write16(component, DAC33_PREFILL_MSB, + DAC33_THRREG(DAC33_MODE7_MARGIN)); +@@ -704,14 +699,12 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + static inline void dac33_playback_handler(struct tlv320dac33_priv *dac33) + { + struct snd_soc_component *component = dac33->component; +- unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: + /* Take the timestamp */ +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp2 = ktime_to_us(ktime_get()); +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) ++ dac33->t_stamp2 = ktime_to_us(ktime_get()); + + dac33_write16(component, DAC33_NSAMPLE_MSB, + DAC33_THRREG(dac33->nsample)); +@@ -735,7 +728,7 @@ static void dac33_work(struct work_struct *work) + dac33 = container_of(work, struct tlv320dac33_priv, work); + component = dac33->component; + +- mutex_lock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + switch (dac33->state) { + case DAC33_PREFILL: + dac33->state = DAC33_PLAYBACK; +@@ -757,18 +750,15 @@ static void dac33_work(struct work_struct *work) + dac33_write(component, DAC33_FIFO_CTRL_A, reg); + break; + } +- mutex_unlock(&dac33->mutex); + } + + static irqreturn_t dac33_interrupt_handler(int irq, void *dev) + { + struct snd_soc_component *component = dev; + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); +- unsigned long flags; + +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp1 = ktime_to_us(ktime_get()); +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) ++ dac33->t_stamp1 = ktime_to_us(ktime_get()); + + /* Do not schedule the workqueue in Mode7 */ + if (dac33->fifo_mode != DAC33_FIFO_MODE7) +@@ -902,14 +892,13 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream, + return -EINVAL; + } + +- mutex_lock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + + if (!dac33->chip_power) { + /* + * Chip is not powered yet. + * Do the init in the dac33_set_bias_level later. + */ +- mutex_unlock(&dac33->mutex); + return 0; + } + +@@ -1053,8 +1042,6 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream, + break; + } + +- mutex_unlock(&dac33->mutex); +- + return 0; + } + +@@ -1156,21 +1143,20 @@ static snd_pcm_sframes_t dac33_dai_delay( + unsigned int time_delta, uthr; + int samples_out, samples_in, samples; + snd_pcm_sframes_t delay = 0; +- unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_BYPASS: + break; + case DAC33_FIFO_MODE1: +- spin_lock_irqsave(&dac33->lock, flags); +- t0 = dac33->t_stamp1; +- t1 = dac33->t_stamp2; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ t0 = dac33->t_stamp1; ++ t1 = dac33->t_stamp2; ++ } + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t1) +- goto out; ++ return 0; + + if (t0 > t1) { + /* +@@ -1230,23 +1216,22 @@ static snd_pcm_sframes_t dac33_dai_delay( + } + break; + case DAC33_FIFO_MODE7: +- spin_lock_irqsave(&dac33->lock, flags); +- t0 = dac33->t_stamp1; +- uthr = dac33->uthr; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ t0 = dac33->t_stamp1; ++ uthr = dac33->uthr; ++ } + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t0) +- goto out; ++ return 0; + + if (t_now <= t0) { + /* + * Either the timestamps are messed or equal. Report + * maximum delay + */ +- delay = uthr; +- goto out; ++ return uthr; + } + + time_delta = t_now - t0; +@@ -1287,7 +1272,7 @@ static snd_pcm_sframes_t dac33_dai_delay( + dac33->fifo_mode); + break; + } +-out: ++ + return delay; + } + +diff --git a/sound/soc/codecs/tscs42xx.c b/sound/soc/codecs/tscs42xx.c +--- a/sound/soc/codecs/tscs42xx.c ++++ b/sound/soc/codecs/tscs42xx.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -210,25 +211,21 @@ static int power_up_audio_plls(struct snd_soc_component *component) + return ret; + } + +- mutex_lock(&tscs42xx->pll_lock); ++ guard(mutex)(&tscs42xx->pll_lock); + + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, mask, val); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL on (%d)\n", ret); +- goto exit; ++ return ret; + } + + if (!plls_locked(component)) { + dev_err(component->dev, "Failed to lock plls\n"); + ret = -ENOMSG; +- goto exit; ++ return ret; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->pll_lock); +- +- return ret; ++ return 0; + } + + static int power_down_audio_plls(struct snd_soc_component *component) +@@ -236,28 +233,24 @@ static int power_down_audio_plls(struct snd_soc_component *component) + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + int ret; + +- mutex_lock(&tscs42xx->pll_lock); ++ guard(mutex)(&tscs42xx->pll_lock); + + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, + RM_PLLCTL1C_PDB_PLL1, + RV_PLLCTL1C_PDB_PLL1_DISABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL off (%d)\n", ret); +- goto exit; ++ return ret; + } + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, + RM_PLLCTL1C_PDB_PLL2, + RV_PLLCTL1C_PDB_PLL2_DISABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL off (%d)\n", ret); +- goto exit; ++ return ret; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->pll_lock); +- +- return ret; ++ return 0; + } + + static int coeff_ram_get(struct snd_kcontrol *kcontrol, +@@ -269,13 +262,11 @@ static int coeff_ram_get(struct snd_kcontrol *kcontrol, + (struct coeff_ram_ctl *)kcontrol->private_value; + struct soc_bytes_ext *params = &ctl->bytes_ext; + +- mutex_lock(&tscs42xx->coeff_ram_lock); ++ guard(mutex)(&tscs42xx->coeff_ram_lock); + + memcpy(ucontrol->value.bytes.data, + &tscs42xx->coeff_ram[ctl->addr * COEFF_SIZE], params->max); + +- mutex_unlock(&tscs42xx->coeff_ram_lock); +- + return 0; + } + +@@ -290,14 +281,14 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + unsigned int coeff_cnt = params->max / COEFF_SIZE; + int ret; + +- mutex_lock(&tscs42xx->coeff_ram_lock); ++ guard(mutex)(&tscs42xx->coeff_ram_lock); + + tscs42xx->coeff_ram_synced = false; + + memcpy(&tscs42xx->coeff_ram[ctl->addr * COEFF_SIZE], + ucontrol->value.bytes.data, params->max); + +- mutex_lock(&tscs42xx->pll_lock); ++ guard(mutex)(&tscs42xx->pll_lock); + + if (plls_locked(component)) { + ret = write_coeff_ram(component, tscs42xx->coeff_ram, +@@ -305,18 +296,12 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + if (ret < 0) { + dev_err(component->dev, + "Failed to flush coeff ram cache (%d)\n", ret); +- goto exit; ++ return ret; + } + tscs42xx->coeff_ram_synced = true; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->pll_lock); +- +- mutex_unlock(&tscs42xx->coeff_ram_lock); +- +- return ret; ++ return 0; + } + + /* Input L Capture Route */ +@@ -385,21 +370,17 @@ static int dac_event(struct snd_soc_dapm_widget *w, + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + int ret; + +- mutex_lock(&tscs42xx->coeff_ram_lock); ++ guard(mutex)(&tscs42xx->coeff_ram_lock); + + if (!tscs42xx->coeff_ram_synced) { + ret = write_coeff_ram(component, tscs42xx->coeff_ram, 0x00, + COEFF_RAM_COEFF_COUNT); + if (ret < 0) +- goto exit; ++ return ret; + tscs42xx->coeff_ram_synced = true; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->coeff_ram_lock); +- +- return ret; ++ return 0; + } + + static const struct snd_soc_dapm_widget tscs42xx_dapm_widgets[] = { +@@ -926,12 +907,10 @@ static int setup_sample_rate(struct snd_soc_component *component, + return ret; + } + +- mutex_lock(&tscs42xx->audio_params_lock); ++ guard(mutex)(&tscs42xx->audio_params_lock); + + tscs42xx->samplerate = rate; + +- mutex_unlock(&tscs42xx->audio_params_lock); +- + return 0; + } + +@@ -1253,12 +1232,10 @@ static int tscs42xx_set_dai_bclk_ratio(struct snd_soc_dai *codec_dai, + return ret; + } + +- mutex_lock(&tscs42xx->audio_params_lock); ++ guard(mutex)(&tscs42xx->audio_params_lock); + + tscs42xx->bclk_ratio = ratio; + +- mutex_unlock(&tscs42xx->audio_params_lock); +- + return 0; + } + +diff --git a/sound/soc/codecs/tscs454.c b/sound/soc/codecs/tscs454.c +--- a/sound/soc/codecs/tscs454.c ++++ b/sound/soc/codecs/tscs454.c +@@ -4,6 +4,7 @@ + // Author: Steven Eckhoff + + #include ++#include + #include + #include + #include +@@ -329,12 +330,10 @@ static int coeff_ram_get(struct snd_kcontrol *kcontrol, + return -EINVAL; + } + +- mutex_lock(coeff_ram_lock); +- +- memcpy(ucontrol->value.bytes.data, +- &coeff_ram[ctl->addr * COEFF_SIZE], params->max); +- +- mutex_unlock(coeff_ram_lock); ++ scoped_guard(mutex, coeff_ram_lock) { ++ memcpy(ucontrol->value.bytes.data, ++ &coeff_ram[ctl->addr * COEFF_SIZE], params->max); ++ } + + return 0; + } +@@ -428,15 +427,15 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + return -EINVAL; + } + +- mutex_lock(coeff_ram_lock); ++ guard(mutex)(coeff_ram_lock); + + *coeff_ram_synced = false; + + memcpy(&coeff_ram[ctl->addr * COEFF_SIZE], + ucontrol->value.bytes.data, params->max); + +- mutex_lock(&tscs454->pll1.lock); +- mutex_lock(&tscs454->pll2.lock); ++ guard(mutex)(&tscs454->pll1.lock); ++ guard(mutex)(&tscs454->pll2.lock); + + val = snd_soc_component_read(component, R_PLLSTAT); + if (val) { /* PLLs locked */ +@@ -446,18 +445,12 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + if (ret < 0) { + dev_err(component->dev, + "Failed to flush coeff ram cache (%d)\n", ret); +- goto exit; ++ return ret; + } + *coeff_ram_synced = true; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs454->pll2.lock); +- mutex_unlock(&tscs454->pll1.lock); +- mutex_unlock(coeff_ram_lock); +- +- return ret; ++ return 0; + } + + static inline int coeff_ram_sync(struct snd_soc_component *component, +@@ -465,41 +458,35 @@ static inline int coeff_ram_sync(struct snd_soc_component *component, + { + int ret; + +- mutex_lock(&tscs454->dac_ram.lock); +- if (!tscs454->dac_ram.synced) { +- ret = write_coeff_ram(component, tscs454->dac_ram.cache, +- R_DACCRS, R_DACCRADD, R_DACCRWDL, +- 0x00, COEFF_RAM_COEFF_COUNT); +- if (ret < 0) { +- mutex_unlock(&tscs454->dac_ram.lock); +- return ret; ++ scoped_guard(mutex, &tscs454->dac_ram.lock) { ++ if (!tscs454->dac_ram.synced) { ++ ret = write_coeff_ram(component, tscs454->dac_ram.cache, ++ R_DACCRS, R_DACCRADD, R_DACCRWDL, ++ 0x00, COEFF_RAM_COEFF_COUNT); ++ if (ret < 0) ++ return ret; + } + } +- mutex_unlock(&tscs454->dac_ram.lock); + +- mutex_lock(&tscs454->spk_ram.lock); +- if (!tscs454->spk_ram.synced) { +- ret = write_coeff_ram(component, tscs454->spk_ram.cache, +- R_SPKCRS, R_SPKCRADD, R_SPKCRWDL, +- 0x00, COEFF_RAM_COEFF_COUNT); +- if (ret < 0) { +- mutex_unlock(&tscs454->spk_ram.lock); +- return ret; ++ scoped_guard(mutex, &tscs454->spk_ram.lock) { ++ if (!tscs454->spk_ram.synced) { ++ ret = write_coeff_ram(component, tscs454->spk_ram.cache, ++ R_SPKCRS, R_SPKCRADD, R_SPKCRWDL, ++ 0x00, COEFF_RAM_COEFF_COUNT); ++ if (ret < 0) ++ return ret; + } + } +- mutex_unlock(&tscs454->spk_ram.lock); + +- mutex_lock(&tscs454->sub_ram.lock); +- if (!tscs454->sub_ram.synced) { +- ret = write_coeff_ram(component, tscs454->sub_ram.cache, +- R_SUBCRS, R_SUBCRADD, R_SUBCRWDL, +- 0x00, COEFF_RAM_COEFF_COUNT); +- if (ret < 0) { +- mutex_unlock(&tscs454->sub_ram.lock); +- return ret; ++ scoped_guard(mutex, &tscs454->sub_ram.lock) { ++ if (!tscs454->sub_ram.synced) { ++ ret = write_coeff_ram(component, tscs454->sub_ram.cache, ++ R_SUBCRS, R_SUBCRADD, R_SUBCRWDL, ++ 0x00, COEFF_RAM_COEFF_COUNT); ++ if (ret < 0) ++ return ret; + } + } +- mutex_unlock(&tscs454->sub_ram.lock); + + return 0; + } +@@ -658,16 +645,14 @@ static int set_sysclk(struct snd_soc_component *component) + + static inline void reserve_pll(struct pll *pll) + { +- mutex_lock(&pll->lock); ++ guard(mutex)(&pll->lock); + pll->users++; +- mutex_unlock(&pll->lock); + } + + static inline void free_pll(struct pll *pll) + { +- mutex_lock(&pll->lock); ++ guard(mutex)(&pll->lock); + pll->users--; +- mutex_unlock(&pll->lock); + } + + static int pll_connected(struct snd_soc_dapm_widget *source, +@@ -679,15 +664,13 @@ static int pll_connected(struct snd_soc_dapm_widget *source, + int users; + + if (strstr(source->name, "PLL 1")) { +- mutex_lock(&tscs454->pll1.lock); +- users = tscs454->pll1.users; +- mutex_unlock(&tscs454->pll1.lock); ++ scoped_guard(mutex, &tscs454->pll1.lock) ++ users = tscs454->pll1.users; + dev_dbg(component->dev, "%s(): PLL 1 users = %d\n", __func__, + users); + } else { +- mutex_lock(&tscs454->pll2.lock); +- users = tscs454->pll2.users; +- mutex_unlock(&tscs454->pll2.lock); ++ scoped_guard(mutex, &tscs454->pll2.lock) ++ users = tscs454->pll2.users; + dev_dbg(component->dev, "%s(): PLL 2 users = %d\n", __func__, + users); + } +@@ -806,7 +789,7 @@ static inline int aif_free(struct snd_soc_component *component, + { + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&tscs454->aifs_status_lock); ++ guard(mutex)(&tscs454->aifs_status_lock); + + dev_dbg(component->dev, "%s(): aif %d\n", __func__, aif->id); + +@@ -829,8 +812,6 @@ static inline int aif_free(struct snd_soc_component *component, + free_pll(tscs454->internal_rate.pll); + } + +- mutex_unlock(&tscs454->aifs_status_lock); +- + return 0; + } + +@@ -3174,7 +3155,7 @@ static int tscs454_hw_params(struct snd_pcm_substream *substream, + unsigned int val; + int ret; + +- mutex_lock(&tscs454->aifs_status_lock); ++ guard(mutex)(&tscs454->aifs_status_lock); + + dev_dbg(component->dev, "%s(): aif %d fs = %u\n", __func__, + aif->id, fs); +@@ -3207,14 +3188,14 @@ static int tscs454_hw_params(struct snd_pcm_substream *substream, + ret = set_aif_fs(component, aif->id, fs); + if (ret < 0) { + dev_err(component->dev, "Failed to set aif fs (%d)\n", ret); +- goto exit; ++ return ret; + } + + ret = set_aif_sample_format(component, params_format(params), aif->id); + if (ret < 0) { + dev_err(component->dev, + "Failed to set aif sample format (%d)\n", ret); +- goto exit; ++ return ret; + } + + set_aif_status_active(&tscs454->aifs_status, aif->id, +@@ -3223,11 +3204,7 @@ static int tscs454_hw_params(struct snd_pcm_substream *substream, + dev_dbg(component->dev, "Set aif %d active. Streams status is 0x%x\n", + aif->id, tscs454->aifs_status.streams); + +- ret = 0; +-exit: +- mutex_unlock(&tscs454->aifs_status_lock); +- +- return ret; ++ return 0; + } + + static int tscs454_hw_free(struct snd_pcm_substream *substream, +diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c +--- a/sound/soc/codecs/twl6040.c ++++ b/sound/soc/codecs/twl6040.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -273,7 +274,7 @@ static void twl6040_hs_jack_report(struct snd_soc_component *component, + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + int status; + +- mutex_lock(&priv->mutex); ++ guard(mutex)(&priv->mutex); + + /* Sync status */ + status = twl6040_read(component, TWL6040_REG_STATUS); +@@ -281,8 +282,6 @@ static void twl6040_hs_jack_report(struct snd_soc_component *component, + snd_soc_jack_report(jack, report, report); + else + snd_soc_jack_report(jack, 0, report); +- +- mutex_unlock(&priv->mutex); + } + + void twl6040_hs_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/uda1342.c b/sound/soc/codecs/uda1342.c +--- a/sound/soc/codecs/uda1342.c ++++ b/sound/soc/codecs/uda1342.c +@@ -308,9 +308,16 @@ static int uda1342_suspend(struct device *dev) + static int uda1342_resume(struct device *dev) + { + struct uda1342_priv *uda1342 = dev_get_drvdata(dev); ++ int ret; + ++ regcache_cache_only(uda1342->regmap, false); + regcache_mark_dirty(uda1342->regmap); +- regcache_sync(uda1342->regmap); ++ ret = regcache_sync(uda1342->regmap); ++ if (ret) { ++ regcache_cache_only(uda1342->regmap, true); ++ regcache_mark_dirty(uda1342->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c +--- a/sound/soc/codecs/wcd9335.c ++++ b/sound/soc/codecs/wcd9335.c +@@ -5010,9 +5010,9 @@ static int wcd9335_power_on_reset(struct wcd9335_codec *wcd) + */ + usleep_range(600, 650); + +- gpiod_set_value(wcd->reset_gpio, 1); ++ gpiod_set_value_cansleep(wcd->reset_gpio, 1); + msleep(20); +- gpiod_set_value(wcd->reset_gpio, 0); ++ gpiod_set_value_cansleep(wcd->reset_gpio, 0); + msleep(20); + + return 0; +diff --git a/sound/soc/codecs/wcd934x.c b/sound/soc/codecs/wcd934x.c +--- a/sound/soc/codecs/wcd934x.c ++++ b/sound/soc/codecs/wcd934x.c +@@ -1265,13 +1265,10 @@ static int wcd934x_set_sido_input_src(struct wcd934x_codec *wcd, int sido_src) + + static int wcd934x_enable_ana_bias_and_sysclk(struct wcd934x_codec *wcd) + { +- mutex_lock(&wcd->sysclk_mutex); +- +- if (++wcd->sysclk_users != 1) { +- mutex_unlock(&wcd->sysclk_mutex); +- return 0; ++ scoped_guard(mutex, &wcd->sysclk_mutex) { ++ if (++wcd->sysclk_users != 1) ++ return 0; + } +- mutex_unlock(&wcd->sysclk_mutex); + + regmap_update_bits(wcd->regmap, WCD934X_ANA_BIAS, + WCD934X_ANA_BIAS_EN_MASK, +@@ -1328,12 +1325,10 @@ static int wcd934x_enable_ana_bias_and_sysclk(struct wcd934x_codec *wcd) + + static int wcd934x_disable_ana_bias_and_syclk(struct wcd934x_codec *wcd) + { +- mutex_lock(&wcd->sysclk_mutex); +- if (--wcd->sysclk_users != 0) { +- mutex_unlock(&wcd->sysclk_mutex); +- return 0; ++ scoped_guard(mutex, &wcd->sysclk_mutex) { ++ if (--wcd->sysclk_users != 0) ++ return 0; + } +- mutex_unlock(&wcd->sysclk_mutex); + + regmap_update_bits(wcd->regmap, WCD934X_CLK_SYS_MCLK_PRG, + WCD934X_EXT_CLK_BUF_EN_MASK | +@@ -2384,7 +2379,7 @@ static int wcd934x_micbias_control(struct snd_soc_component *component, + __func__, micb_num); + return -EINVAL; + } +- mutex_lock(&wcd934x->micb_lock); ++ guard(mutex)(&wcd934x->micb_lock); + + switch (req) { + case MICB_PULLUP_ENABLE: +@@ -2446,8 +2441,6 @@ static int wcd934x_micbias_control(struct snd_soc_component *component, + break; + } + +- mutex_unlock(&wcd934x->micb_lock); +- + return 0; + } + +@@ -2488,7 +2481,7 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) + { + struct wcd934x_codec *wcd934x = snd_soc_component_get_drvdata(component); +- int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; ++ int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -2506,7 +2499,7 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd934x->micb_lock); ++ guard(mutex)(&wcd934x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as +@@ -2521,15 +2514,11 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD934X_MICB_VAL_MASK); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = -EINVAL; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return -EINVAL; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + if (micb_en == WCD934X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, +@@ -2550,9 +2539,8 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + */ + usleep_range(2000, 2100); + } +-exit: +- mutex_unlock(&wcd934x->micb_lock); +- return ret; ++ ++ return 0; + } + + static int wcd934x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +@@ -5899,10 +5887,8 @@ static int wcd934x_codec_probe(struct platform_device *pdev) + } + + static const struct platform_device_id wcd934x_driver_id[] = { +- { +- .name = "wcd934x-codec", +- }, +- {}, ++ { .name = "wcd934x-codec" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, wcd934x_driver_id); + +diff --git a/sound/soc/codecs/wcd937x-sdw.c b/sound/soc/codecs/wcd937x-sdw.c +--- a/sound/soc/codecs/wcd937x-sdw.c ++++ b/sound/soc/codecs/wcd937x-sdw.c +@@ -1084,10 +1084,16 @@ static int wcd937x_sdw_runtime_suspend(struct device *dev) + static int wcd937x_sdw_runtime_resume(struct device *dev) + { + struct wcd937x_sdw_priv *wcd = dev_get_drvdata(dev); ++ int ret; + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); +- regcache_sync(wcd->regmap); ++ ret = regcache_sync(wcd->regmap); ++ if (ret) { ++ regcache_cache_only(wcd->regmap, true); ++ regcache_mark_dirty(wcd->regmap); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/wcd937x.c b/sound/soc/codecs/wcd937x.c +--- a/sound/soc/codecs/wcd937x.c ++++ b/sound/soc/codecs/wcd937x.c +@@ -2,6 +2,7 @@ + // Copyright (c) 2023-2024 Qualcomm Innovation Center, Inc. All rights reserved. + + #include ++#include + #include + #include + #include +@@ -1056,7 +1057,7 @@ static int wcd937x_micbias_control(struct snd_soc_component *component, + return -EINVAL; + } + +- mutex_lock(&wcd937x->micb_lock); ++ guard(mutex)(&wcd937x->micb_lock); + switch (req) { + case MICB_PULLUP_ENABLE: + wcd937x->pullup_ref[micb_index]++; +@@ -1136,7 +1137,6 @@ static int wcd937x_micbias_control(struct snd_soc_component *component, + } + break; + } +- mutex_unlock(&wcd937x->micb_lock); + + return 0; + } +@@ -1460,7 +1460,7 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) + { + struct wcd937x_priv *wcd937x = snd_soc_component_get_drvdata(component); +- int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; ++ int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -1475,7 +1475,7 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd937x->micb_lock); ++ guard(mutex)(&wcd937x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as +@@ -1490,15 +1490,11 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD937X_MICB_VOUT_MASK); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = -EINVAL; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return -EINVAL; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + if (micb_en == WCD937X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, +@@ -1519,9 +1515,8 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + */ + usleep_range(2000, 2100); + } +-exit: +- mutex_unlock(&wcd937x->micb_lock); +- return ret; ++ ++ return 0; + } + + static int wcd937x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/wcd938x-sdw.c b/sound/soc/codecs/wcd938x-sdw.c +--- a/sound/soc/codecs/wcd938x-sdw.c ++++ b/sound/soc/codecs/wcd938x-sdw.c +@@ -1245,10 +1245,16 @@ static int wcd938x_sdw_runtime_suspend(struct device *dev) + static int wcd938x_sdw_runtime_resume(struct device *dev) + { + struct wcd938x_sdw_priv *wcd = dev_get_drvdata(dev); ++ int ret; + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); +- regcache_sync(wcd->regmap); ++ ret = regcache_sync(wcd->regmap); ++ if (ret) { ++ regcache_cache_only(wcd->regmap, true); ++ regcache_mark_dirty(wcd->regmap); ++ return ret; ++ } + } + + pm_runtime_mark_last_busy(dev); +diff --git a/sound/soc/codecs/wcd938x.c b/sound/soc/codecs/wcd938x.c +--- a/sound/soc/codecs/wcd938x.c ++++ b/sound/soc/codecs/wcd938x.c +@@ -1,6 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-only + // Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + ++#include + #include + #include + #include +@@ -1976,7 +1977,7 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) + { + struct wcd938x_priv *wcd938x = snd_soc_component_get_drvdata(component); +- int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; ++ int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -1994,7 +1995,7 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd938x->micb_lock); ++ guard(mutex)(&wcd938x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as +@@ -2009,15 +2010,11 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD938X_MICB_VOUT_MASK); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = -EINVAL; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return -EINVAL; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + if (micb_en == WCD938X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, +@@ -2038,9 +2035,8 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + */ + usleep_range(2000, 2100); + } +-exit: +- mutex_unlock(&wcd938x->micb_lock); +- return ret; ++ ++ return 0; + } + + static int wcd938x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/wcd939x-sdw.c b/sound/soc/codecs/wcd939x-sdw.c +--- a/sound/soc/codecs/wcd939x-sdw.c ++++ b/sound/soc/codecs/wcd939x-sdw.c +@@ -1431,10 +1431,16 @@ static int wcd939x_sdw_runtime_suspend(struct device *dev) + static int wcd939x_sdw_runtime_resume(struct device *dev) + { + struct wcd939x_sdw_priv *wcd = dev_get_drvdata(dev); ++ int ret; + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); +- regcache_sync(wcd->regmap); ++ ret = regcache_sync(wcd->regmap); ++ if (ret) { ++ regcache_cache_only(wcd->regmap, true); ++ regcache_mark_dirty(wcd->regmap); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/wcd939x.c b/sound/soc/codecs/wcd939x.c +--- a/sound/soc/codecs/wcd939x.c ++++ b/sound/soc/codecs/wcd939x.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1923,7 +1924,6 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + struct wcd939x_priv *wcd939x = snd_soc_component_get_drvdata(component); + unsigned int micb_reg, cur_vout_ctl, micb_en; + int req_vout_ctl; +- int ret = 0; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -1941,7 +1941,7 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd939x->micb_lock); ++ guard(mutex)(&wcd939x->micb_lock); + + /* + * If requested micbias voltage is same as current micbias +@@ -1957,15 +1957,11 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD939X_MICB_VOUT_CTL); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = req_vout_ctl; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return req_vout_ctl; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + dev_dbg(component->dev, "%s: micb_num: %d, cur_mv: %d, req_mv: %d, micb_en: %d\n", + __func__, micb_num, WCD_VOUT_CTL_TO_MICB(cur_vout_ctl), +@@ -1990,9 +1986,7 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + usleep_range(2000, 2100); + } + +-exit: +- mutex_unlock(&wcd939x->micb_lock); +- return ret; ++ return 0; + } + + static int wcd939x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/wm0010.c b/sound/soc/codecs/wm0010.c +--- a/sound/soc/codecs/wm0010.c ++++ b/sound/soc/codecs/wm0010.c +@@ -9,6 +9,7 @@ + * Scott Ling + */ + ++#include + #include + #include + #include +@@ -148,13 +149,11 @@ static const char *wm0010_state_to_str(enum wm0010_state state) + static void wm0010_halt(struct snd_soc_component *component) + { + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); +- unsigned long flags; + enum wm0010_state state; + + /* Fetch the wm0010 state */ +- spin_lock_irqsave(&wm0010->irq_lock, flags); +- state = wm0010->state; +- spin_unlock_irqrestore(&wm0010->irq_lock, flags); ++ scoped_guard(spinlock_irqsave, &wm0010->irq_lock) ++ state = wm0010->state; + + switch (state) { + case WM0010_POWER_OFF: +@@ -173,9 +172,8 @@ static void wm0010_halt(struct snd_soc_component *component) + break; + } + +- spin_lock_irqsave(&wm0010->irq_lock, flags); +- wm0010->state = WM0010_POWER_OFF; +- spin_unlock_irqrestore(&wm0010->irq_lock, flags); ++ scoped_guard(spinlock_irqsave, &wm0010->irq_lock) ++ wm0010->state = WM0010_POWER_OFF; + } + + struct wm0010_boot_xfer { +@@ -190,11 +188,9 @@ struct wm0010_boot_xfer { + static void wm0010_mark_boot_failure(struct wm0010_priv *wm0010) + { + enum wm0010_state state; +- unsigned long flags; + +- spin_lock_irqsave(&wm0010->irq_lock, flags); +- state = wm0010->state; +- spin_unlock_irqrestore(&wm0010->irq_lock, flags); ++ scoped_guard(spinlock_irqsave, &wm0010->irq_lock) ++ state = wm0010->state; + + dev_err(wm0010->dev, "Failed to transition from `%s' state to `%s' state\n", + wm0010_state_to_str(state), wm0010_state_to_str(state + 1)); +@@ -337,7 +333,6 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + struct wm0010_boot_xfer *xfer; + int ret; + DECLARE_COMPLETION_ONSTACK(done); +- const struct firmware *fw; + const struct dfw_binrec *rec; + const struct dfw_inforec *inforec; + u64 *img; +@@ -346,6 +341,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + + INIT_LIST_HEAD(&xfer_list); + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, name, component->dev); + if (ret != 0) { + dev_err(component->dev, "Failed to request application(%s): %d\n", +@@ -364,16 +360,14 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + /* First record should be INFO */ + if (rec->command != DFW_CMD_INFO) { + dev_err(component->dev, "First record not INFO\r\n"); +- ret = -EINVAL; +- goto abort; ++ return -EINVAL; + } + + if (inforec->info_version != INFO_VERSION) { + dev_err(component->dev, + "Unsupported version (%02d) of INFO record\r\n", + inforec->info_version); +- ret = -EINVAL; +- goto abort; ++ return -EINVAL; + } + + dev_dbg(component->dev, "Version v%02d INFO record found\r\n", +@@ -382,8 +376,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + /* Check it's a DSP file */ + if (dsp != DEVICE_ID_WM0010) { + dev_err(component->dev, "Not a WM0010 firmware file.\r\n"); +- ret = -EINVAL; +- goto abort; ++ return -EINVAL; + } + + /* Skip the info record as we don't need to send it */ +@@ -408,14 +401,14 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + out = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!out) { + ret = -ENOMEM; +- goto abort1; ++ goto abort; + } + xfer->t.rx_buf = out; + + img = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!img) { + ret = -ENOMEM; +- goto abort1; ++ goto abort; + } + xfer->t.tx_buf = img; + +@@ -453,13 +446,13 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + ret = spi_async(spi, &xfer->m); + if (ret != 0) { + dev_err(component->dev, "Write failed: %d\n", ret); +- goto abort1; ++ goto abort; + } + + if (wm0010->boot_failed) { + dev_dbg(component->dev, "Boot fail!\n"); + ret = -EINVAL; +- goto abort1; ++ goto abort; + } + } + +@@ -467,7 +460,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + + ret = 0; + +-abort1: ++abort: + while (!list_empty(&xfer_list)) { + xfer = list_first_entry(&xfer_list, struct wm0010_boot_xfer, + list); +@@ -477,8 +470,6 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + kfree(xfer); + } + +-abort: +- release_firmware(fw); + return ret; + } + +@@ -486,14 +477,12 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + { + struct spi_device *spi = to_spi_device(component->dev); + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); +- const struct firmware *fw; + struct spi_message m; + struct spi_transfer t; +- u32 *img; +- u8 *out; + int i; + int ret = 0; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, "wm0010_stage2.bin", component->dev); + if (ret != 0) { + dev_err(component->dev, "Failed to request stage2 loader: %d\n", +@@ -504,17 +493,15 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + dev_dbg(component->dev, "Downloading %zu byte stage 2 loader\n", fw->size); + + /* Copy to local buffer first as vmalloc causes problems for dma */ +- img = kmemdup(&fw->data[0], fw->size, GFP_KERNEL | GFP_DMA); +- if (!img) { +- ret = -ENOMEM; +- goto abort2; +- } ++ u32 *img __free(kfree) = ++ kmemdup(&fw->data[0], fw->size, GFP_KERNEL | GFP_DMA); ++ if (!img) ++ return -ENOMEM; + +- out = kzalloc(fw->size, GFP_KERNEL | GFP_DMA); +- if (!out) { +- ret = -ENOMEM; +- goto abort1; +- } ++ u8 *out __free(kfree) = ++ kzalloc(fw->size, GFP_KERNEL | GFP_DMA); ++ if (!out) ++ return -ENOMEM; + + spi_message_init(&m); + memset(&t, 0, sizeof(t)); +@@ -531,7 +518,7 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + ret = spi_sync(spi, &m); + if (ret != 0) { + dev_err(component->dev, "Initial download failed: %d\n", ret); +- goto abort; ++ return ret; + } + + /* Look for errors from the boot ROM */ +@@ -540,18 +527,11 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + dev_err(component->dev, "Boot ROM error: %x in %d\n", + out[i], i); + wm0010_mark_boot_failure(wm0010); +- ret = -EBUSY; +- goto abort; ++ return -EBUSY; + } + } +-abort: +- kfree(out); +-abort1: +- kfree(img); +-abort2: +- release_firmware(fw); + +- return ret; ++ return 0; + } + + static int wm0010_boot(struct snd_soc_component *component) +@@ -734,9 +714,8 @@ static int wm0010_set_bias_level(struct snd_soc_component *component, + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_dapm_get_bias_level(dapm) == SND_SOC_BIAS_PREPARE) { +- mutex_lock(&wm0010->lock); +- wm0010_halt(component); +- mutex_unlock(&wm0010->lock); ++ scoped_guard(mutex, &wm0010->lock) ++ wm0010_halt(component); + } + break; + case SND_SOC_BIAS_OFF: +@@ -832,9 +811,8 @@ static irqreturn_t wm0010_irq(int irq, void *data) + case WM0010_OUT_OF_RESET: + case WM0010_BOOTROM: + case WM0010_STAGE2: +- spin_lock(&wm0010->irq_lock); +- complete(&wm0010->boot_completion); +- spin_unlock(&wm0010->irq_lock); ++ scoped_guard(spinlock, &wm0010->irq_lock) ++ complete(&wm0010->boot_completion); + return IRQ_HANDLED; + default: + return IRQ_NONE; +diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c +--- a/sound/soc/codecs/wm2000.c ++++ b/sound/soc/codecs/wm2000.c +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -612,20 +613,15 @@ static int wm2000_anc_mode_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + unsigned int anc_active = ucontrol->value.integer.value[0]; +- int ret; + + if (anc_active > 1) + return -EINVAL; + +- mutex_lock(&wm2000->lock); ++ guard(mutex)(&wm2000->lock); + + wm2000->anc_active = anc_active; + +- ret = wm2000_anc_set_mode(wm2000); +- +- mutex_unlock(&wm2000->lock); +- +- return ret; ++ return wm2000_anc_set_mode(wm2000); + } + + static int wm2000_speaker_get(struct snd_kcontrol *kcontrol, +@@ -645,20 +641,15 @@ static int wm2000_speaker_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + unsigned int val = ucontrol->value.integer.value[0]; +- int ret; + + if (val > 1) + return -EINVAL; + +- mutex_lock(&wm2000->lock); ++ guard(mutex)(&wm2000->lock); + + wm2000->spk_ena = val; + +- ret = wm2000_anc_set_mode(wm2000); +- +- mutex_unlock(&wm2000->lock); +- +- return ret; ++ return wm2000_anc_set_mode(wm2000); + } + + static const struct snd_kcontrol_new wm2000_controls[] = { +@@ -676,9 +667,8 @@ static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w, + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); +- int ret; + +- mutex_lock(&wm2000->lock); ++ guard(mutex)(&wm2000->lock); + + if (SND_SOC_DAPM_EVENT_ON(event)) + wm2000->anc_eng_ena = 1; +@@ -686,11 +676,7 @@ static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w, + if (SND_SOC_DAPM_EVENT_OFF(event)) + wm2000->anc_eng_ena = 0; + +- ret = wm2000_anc_set_mode(wm2000); +- +- mutex_unlock(&wm2000->lock); +- +- return ret; ++ return wm2000_anc_set_mode(wm2000); + } + + static const struct snd_soc_dapm_widget wm2000_dapm_widgets[] = { +@@ -810,7 +796,7 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) + struct wm2000_priv *wm2000; + struct wm2000_platform_data *pdata; + const char *filename; +- const struct firmware *fw = NULL; ++ const struct firmware *fw __free(firmware) = NULL; + int ret, i; + unsigned int reg; + u16 id; +@@ -828,7 +814,7 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) + ret = PTR_ERR(wm2000->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); +- goto out; ++ return ret; + } + + for (i = 0; i < WM2000_NUM_SUPPLIES; i++) +@@ -922,9 +908,6 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) + + err_supplies: + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); +- +-out: +- release_firmware(fw); + return ret; + } + +diff --git a/sound/soc/codecs/wm2200.c b/sound/soc/codecs/wm2200.c +--- a/sound/soc/codecs/wm2200.c ++++ b/sound/soc/codecs/wm2200.c +@@ -2461,7 +2461,15 @@ static int wm2200_runtime_resume(struct device *dev) + } + + regcache_cache_only(wm2200->regmap, false); +- regcache_sync(wm2200->regmap); ++ ret = regcache_sync(wm2200->regmap); ++ if (ret) { ++ regcache_cache_only(wm2200->regmap, true); ++ regcache_mark_dirty(wm2200->regmap); ++ gpiod_set_value_cansleep(wm2200->ldo_ena, 0); ++ regulator_bulk_disable(ARRAY_SIZE(wm2200->core_supplies), ++ wm2200->core_supplies); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c +--- a/sound/soc/codecs/wm5100.c ++++ b/sound/soc/codecs/wm5100.c +@@ -2659,7 +2659,15 @@ static int wm5100_runtime_resume(struct device *dev) + } + + regcache_cache_only(wm5100->regmap, false); +- regcache_sync(wm5100->regmap); ++ ret = regcache_sync(wm5100->regmap); ++ if (ret) { ++ regcache_cache_only(wm5100->regmap, true); ++ regcache_mark_dirty(wm5100->regmap); ++ gpiod_set_value_cansleep(wm5100->ldo_ena, 0); ++ regulator_bulk_disable(ARRAY_SIZE(wm5100->core_supplies), ++ wm5100->core_supplies); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c +--- a/sound/soc/codecs/wm5102.c ++++ b/sound/soc/codecs/wm5102.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -667,10 +668,9 @@ static int wm5102_out_comp_coeff_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + put_unaligned_be16(arizona->dac_comp_coeff, + ucontrol->value.bytes.data); +- mutex_unlock(&arizona->dac_comp_lock); + + return 0; + } +@@ -681,16 +681,14 @@ static int wm5102_out_comp_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + uint16_t dac_comp_coeff = get_unaligned_be16(ucontrol->value.bytes.data); +- int ret = 0; + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + if (arizona->dac_comp_coeff != dac_comp_coeff) { + arizona->dac_comp_coeff = dac_comp_coeff; +- ret = 1; ++ return 1; + } +- mutex_unlock(&arizona->dac_comp_lock); + +- return ret; ++ return 0; + } + + static int wm5102_out_comp_switch_get(struct snd_kcontrol *kcontrol, +@@ -699,9 +697,8 @@ static int wm5102_out_comp_switch_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + ucontrol->value.integer.value[0] = arizona->dac_comp_enabled; +- mutex_unlock(&arizona->dac_comp_lock); + + return 0; + } +@@ -712,19 +709,17 @@ static int wm5102_out_comp_switch_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; +- int ret = 0; + + if (ucontrol->value.integer.value[0] > mc->max) + return -EINVAL; + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + if (arizona->dac_comp_enabled != ucontrol->value.integer.value[0]) { + arizona->dac_comp_enabled = ucontrol->value.integer.value[0]; +- ret = 1; ++ return 1; + } +- mutex_unlock(&arizona->dac_comp_lock); + +- return ret; ++ return 0; + } + + static const char * const wm5102_osr_text[] = { +diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c +--- a/sound/soc/codecs/wm8731.c ++++ b/sound/soc/codecs/wm8731.c +@@ -10,6 +10,7 @@ + * Based on wm8753.c by Liam Girdwood + */ + ++#include + #include + #include + #include +@@ -110,22 +111,20 @@ static int wm8731_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8731_priv *wm8731 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; +- int ret = 0; + + if (deemph > 1) + return -EINVAL; + +- mutex_lock(&wm8731->lock); ++ guard(mutex)(&wm8731->lock); + if (wm8731->deemph != deemph) { + wm8731->deemph = deemph; + + wm8731_set_deemph(component); + +- ret = 1; ++ return 1; + } +- mutex_unlock(&wm8731->lock); + +- return ret; ++ return 0; + } + + static const DECLARE_TLV_DB_SCALE(in_tlv, -3450, 150, 0); +diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c +--- a/sound/soc/codecs/wm8903.c ++++ b/sound/soc/codecs/wm8903.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -458,22 +459,20 @@ static int wm8903_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8903_priv *wm8903 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; +- int ret = 0; + + if (deemph > 1) + return -EINVAL; + +- mutex_lock(&wm8903->lock); ++ guard(mutex)(&wm8903->lock); + if (wm8903->deemph != deemph) { + wm8903->deemph = deemph; + + wm8903_set_deemph(component); + +- ret = 1; ++ return 1; + } +- mutex_unlock(&wm8903->lock); + +- return ret; ++ return 0; + } + + /* ALSA can only do steps of .01dB */ +diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c +--- a/sound/soc/codecs/wm8940.c ++++ b/sound/soc/codecs/wm8940.c +@@ -333,7 +333,7 @@ static const struct snd_soc_dapm_route wm8940_dapm_routes[] = { + {"ADC", NULL, "Boost Mixer"}, + }; + +-#define wm8940_reset(c) snd_soc_component_write(c, WM8940_SOFTRESET, 0); ++#define wm8940_reset(c) snd_soc_component_write(c, WM8940_SOFTRESET, 0) + + static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c +--- a/sound/soc/codecs/wm8958-dsp2.c ++++ b/sound/soc/codecs/wm8958-dsp2.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -864,9 +865,8 @@ static void wm8958_enh_eq_loaded(const struct firmware *fw, void *context) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "ENH_EQ", fw, true) == 0)) { +- mutex_lock(&wm8994->fw_lock); ++ guard(mutex)(&wm8994->fw_lock); + wm8994->enh_eq = fw; +- mutex_unlock(&wm8994->fw_lock); + } + } + +@@ -876,9 +876,8 @@ static void wm8958_mbc_vss_loaded(const struct firmware *fw, void *context) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "MBC+VSS", fw, true) == 0)) { +- mutex_lock(&wm8994->fw_lock); ++ guard(mutex)(&wm8994->fw_lock); + wm8994->mbc_vss = fw; +- mutex_unlock(&wm8994->fw_lock); + } + } + +@@ -888,9 +887,8 @@ static void wm8958_mbc_loaded(const struct firmware *fw, void *context) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "MBC", fw, true) == 0)) { +- mutex_lock(&wm8994->fw_lock); ++ guard(mutex)(&wm8994->fw_lock); + wm8994->mbc = fw; +- mutex_unlock(&wm8994->fw_lock); + } + } + +diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c +--- a/sound/soc/codecs/wm8962.c ++++ b/sound/soc/codecs/wm8962.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1564,11 +1565,10 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + int old = wm8962->dsp2_ena; +- int ret = 0; + int dsp2_running = snd_soc_component_read(component, WM8962_DSP2_POWER_MANAGEMENT) & + WM8962_DSP2_ENA; + +- mutex_lock(&wm8962->dsp2_ena_lock); ++ guard(mutex)(&wm8962->dsp2_ena_lock); + + if (ucontrol->value.integer.value[0]) + wm8962->dsp2_ena |= 1 << shift; +@@ -1576,9 +1576,7 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + wm8962->dsp2_ena &= ~(1 << shift); + + if (wm8962->dsp2_ena == old) +- goto out; +- +- ret = 1; ++ return 0; + + if (dsp2_running) { + if (wm8962->dsp2_ena) +@@ -1587,10 +1585,7 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + wm8962_dsp2_stop(component); + } + +-out: +- mutex_unlock(&wm8962->dsp2_ena_lock); +- +- return ret; ++ return 1; + } + + /* The VU bits for the headphones are in a different register to the mute +@@ -3937,7 +3932,9 @@ static int wm8962_runtime_resume(struct device *dev) + WM8962_OSC_ENA | WM8962_PLL2_ENA | WM8962_PLL3_ENA, + 0); + +- regcache_sync(wm8962->regmap); ++ ret = regcache_sync(wm8962->regmap); ++ if (ret) ++ goto cache_sync_err; + + regmap_update_bits(wm8962->regmap, WM8962_ANTI_POP, + WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA, +@@ -3955,6 +3952,11 @@ static int wm8962_runtime_resume(struct device *dev) + + return 0; + ++cache_sync_err: ++ regcache_cache_only(wm8962->regmap, true); ++ regcache_mark_dirty(wm8962->regmap); ++ regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies), ++ wm8962->supplies); + disable_clock: + clk_disable_unprepare(wm8962->pdata.mclk); + return ret; +diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c +--- a/sound/soc/codecs/wm8994.c ++++ b/sound/soc/codecs/wm8994.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -766,7 +767,7 @@ static void active_reference(struct snd_soc_component *component) + { + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + wm8994->active_refcount++; + +@@ -775,8 +776,6 @@ static void active_reference(struct snd_soc_component *component) + + /* If we're using jack detection go into audio mode */ + wm1811_jackdet_set_mode(component, WM1811_JACKDET_MODE_AUDIO); +- +- mutex_unlock(&wm8994->accdet_lock); + } + + static void active_dereference(struct snd_soc_component *component) +@@ -784,7 +783,7 @@ static void active_dereference(struct snd_soc_component *component) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + u16 mode; + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + wm8994->active_refcount--; + +@@ -800,8 +799,6 @@ static void active_dereference(struct snd_soc_component *component) + + wm1811_jackdet_set_mode(component, mode); + } +- +- mutex_unlock(&wm8994->accdet_lock); + } + + static int clk_sys_event(struct snd_soc_dapm_widget *w, +@@ -3704,7 +3701,7 @@ static void wm8958_open_circuit_work(struct work_struct *work) + open_circuit_work.work); + struct device *dev = wm8994->wm8994->dev; + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + wm1811_micd_stop(wm8994->hubs.component); + +@@ -3718,8 +3715,6 @@ static void wm8958_open_circuit_work(struct work_struct *work) + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + wm8994->btn_mask | + SND_JACK_HEADSET); +- +- mutex_unlock(&wm8994->accdet_lock); + } + + static void wm8958_mic_id(void *data, u16 status) +@@ -3777,7 +3772,7 @@ static void wm1811_mic_work(struct work_struct *work) + struct snd_soc_component *component = wm8994->hubs.component; + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + +- pm_runtime_get_sync(component->dev); ++ guard(pm_runtime_active)(component->dev); + + /* If required for an external cap force MICBIAS on */ + if (control->pdata.jd_ext_cap) { +@@ -3785,7 +3780,7 @@ static void wm1811_mic_work(struct work_struct *work) + snd_soc_dapm_sync(dapm); + } + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + dev_dbg(component->dev, "Starting mic detection\n"); + +@@ -3803,10 +3798,6 @@ static void wm1811_mic_work(struct work_struct *work) + snd_soc_component_update_bits(component, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, WM8958_MICD_ENA); + } +- +- mutex_unlock(&wm8994->accdet_lock); +- +- pm_runtime_put(component->dev); + } + + static irqreturn_t wm1811_jackdet_irq(int irq, void *data) +@@ -4026,15 +4017,10 @@ static void wm8958_mic_work(struct work_struct *work) + mic_complete_work.work); + struct snd_soc_component *component = wm8994->hubs.component; + +- pm_runtime_get_sync(component->dev); +- +- mutex_lock(&wm8994->accdet_lock); ++ guard(pm_runtime_active)(component->dev); ++ guard(mutex)(&wm8994->accdet_lock); + + wm8994->mic_id_cb(wm8994->mic_id_cb_data, wm8994->mic_status); +- +- mutex_unlock(&wm8994->accdet_lock); +- +- pm_runtime_put(component->dev); + } + + static irqreturn_t wm8958_mic_irq(int irq, void *data) +diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c +--- a/sound/soc/codecs/wm9712.c ++++ b/sound/soc/codecs/wm9712.c +@@ -6,6 +6,7 @@ + * Author: Liam Girdwood + */ + ++#include + #include + #include + #include +@@ -229,7 +230,7 @@ static int wm9712_hp_mixer_put(struct snd_kcontrol *kcontrol, + shift = mc->shift & 0xff; + mask = 1 << shift; + +- mutex_lock(&wm9712->lock); ++ guard(mutex)(&wm9712->lock); + old = wm9712->hp_mixer[mixer]; + if (ucontrol->value.integer.value[0]) + wm9712->hp_mixer[mixer] |= mask; +@@ -251,8 +252,6 @@ static int wm9712_hp_mixer_put(struct snd_kcontrol *kcontrol, + &update); + } + +- mutex_unlock(&wm9712->lock); +- + return change; + } + +diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c +--- a/sound/soc/codecs/wm9713.c ++++ b/sound/soc/codecs/wm9713.c +@@ -11,6 +11,7 @@ + * o Support for DAPM + */ + ++#include + #include + #include + #include +@@ -238,7 +239,7 @@ static int wm9713_hp_mixer_put(struct snd_kcontrol *kcontrol, + shift = mc->shift & 0xff; + mask = (1 << shift); + +- mutex_lock(&wm9713->lock); ++ guard(mutex)(&wm9713->lock); + old = wm9713->hp_mixer[mixer]; + if (ucontrol->value.integer.value[0]) + wm9713->hp_mixer[mixer] |= mask; +@@ -260,8 +261,6 @@ static int wm9713_hp_mixer_put(struct snd_kcontrol *kcontrol, + &update); + } + +- mutex_unlock(&wm9713->lock); +- + return change; + } + +diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c +--- a/sound/soc/codecs/wm_adsp.c ++++ b/sound/soc/codecs/wm_adsp.c +@@ -348,7 +348,6 @@ int wm_adsp_fw_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct wm_adsp *dsp = snd_soc_component_get_drvdata(component); +- int ret = 1; + + if (ucontrol->value.enumerated.item[0] == dsp[e->shift_l].fw) + return 0; +@@ -356,16 +355,14 @@ int wm_adsp_fw_put(struct snd_kcontrol *kcontrol, + if (ucontrol->value.enumerated.item[0] >= WM_ADSP_NUM_FW) + return -EINVAL; + +- mutex_lock(&dsp[e->shift_l].cs_dsp.pwr_lock); ++ guard(mutex)(&dsp[e->shift_l].cs_dsp.pwr_lock); + + if (dsp[e->shift_l].cs_dsp.booted || !list_empty(&dsp[e->shift_l].compr_list)) +- ret = -EBUSY; ++ return -EBUSY; + else + dsp[e->shift_l].fw = ucontrol->value.enumerated.item[0]; + +- mutex_unlock(&dsp[e->shift_l].cs_dsp.pwr_lock); +- +- return ret; ++ return 1; + } + EXPORT_SYMBOL_GPL(wm_adsp_fw_put); + +@@ -450,15 +447,13 @@ static int wm_coeff_put_acked(struct snd_kcontrol *kctl, + if (val == 0) + return 0; /* 0 means no event */ + +- mutex_lock(&cs_ctl->dsp->pwr_lock); ++ guard(mutex)(&cs_ctl->dsp->pwr_lock); + + if (cs_ctl->enabled) + ret = cs_dsp_coeff_write_acked_control(cs_ctl, val); + else + ret = -EPERM; + +- mutex_unlock(&cs_ctl->dsp->pwr_lock); +- + if (ret < 0) + return ret; + +@@ -486,15 +481,13 @@ static int wm_coeff_tlv_get(struct snd_kcontrol *kctl, + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + int ret = 0; + +- mutex_lock(&cs_ctl->dsp->pwr_lock); ++ guard(mutex)(&cs_ctl->dsp->pwr_lock); + + ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, cs_ctl->cache, size); + + if (!ret && copy_to_user(bytes, cs_ctl->cache, size)) + ret = -EFAULT; + +- mutex_unlock(&cs_ctl->dsp->pwr_lock); +- + return ret; + } + +@@ -694,10 +687,9 @@ int wm_adsp_write_ctl(struct wm_adsp *dsp, const char *name, int type, + struct cs_dsp_coeff_ctl *cs_ctl; + int ret; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + cs_ctl = cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg); + ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, buf, len); +- mutex_unlock(&dsp->cs_dsp.pwr_lock); + + if (ret < 0) + return ret; +@@ -709,14 +701,10 @@ EXPORT_SYMBOL_GPL(wm_adsp_write_ctl); + int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type, + unsigned int alg, void *buf, size_t len) + { +- int ret; ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + +- mutex_lock(&dsp->cs_dsp.pwr_lock); +- ret = cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg), ++ return cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg), + 0, buf, len); +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- +- return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_read_ctl); + +@@ -1270,38 +1258,32 @@ int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream) + { + struct wm_adsp_compr *compr, *tmp; + struct snd_soc_pcm_runtime *rtd = stream->private_data; +- int ret = 0; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + if (wm_adsp_fw[dsp->fw].num_caps == 0) { + adsp_err(dsp, "%s: Firmware does not support compressed API\n", + snd_soc_rtd_to_codec(rtd, 0)->name); +- ret = -ENXIO; +- goto out; ++ return -ENXIO; + } + + if (wm_adsp_fw[dsp->fw].compr_direction != stream->direction) { + adsp_err(dsp, "%s: Firmware does not support stream direction\n", + snd_soc_rtd_to_codec(rtd, 0)->name); +- ret = -EINVAL; +- goto out; ++ return -EINVAL; + } + + list_for_each_entry(tmp, &dsp->compr_list, list) { + if (!strcmp(tmp->name, snd_soc_rtd_to_codec(rtd, 0)->name)) { + adsp_err(dsp, "%s: Only a single stream supported per dai\n", + snd_soc_rtd_to_codec(rtd, 0)->name); +- ret = -EBUSY; +- goto out; ++ return -EBUSY; + } + } + + compr = kzalloc_obj(*compr); +- if (!compr) { +- ret = -ENOMEM; +- goto out; +- } ++ if (!compr) ++ return -ENOMEM; + + compr->dsp = dsp; + compr->stream = stream; +@@ -1311,10 +1293,7 @@ int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream) + + stream->runtime->private_data = compr; + +-out: +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- +- return ret; ++ return 0; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_open); + +@@ -1324,7 +1303,7 @@ int wm_adsp_compr_free(struct snd_soc_component *component, + struct wm_adsp_compr *compr = stream->runtime->private_data; + struct wm_adsp *dsp = compr->dsp; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + wm_adsp_compr_detach(compr); + list_del(&compr->list); +@@ -1332,8 +1311,6 @@ int wm_adsp_compr_free(struct snd_soc_component *component, + kfree(compr->raw_buf); + kfree(compr); + +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return 0; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_free); +@@ -1741,7 +1718,7 @@ int wm_adsp_compr_trigger(struct snd_soc_component *component, + + compr_dbg(compr, "Trigger: %d\n", cmd); + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: +@@ -1777,8 +1754,6 @@ int wm_adsp_compr_trigger(struct snd_soc_component *component, + break; + } + +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_trigger); +@@ -1907,21 +1882,20 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, + + compr_dbg(compr, "Pointer request\n"); + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + buf = compr->buf; + + if (dsp->fatal_error || !buf || buf->error) { + snd_compr_stop_error(stream, SNDRV_PCM_STATE_XRUN); +- ret = -EIO; +- goto out; ++ return -EIO; + } + + if (buf->avail < wm_adsp_compr_frag_words(compr)) { + ret = wm_adsp_buffer_update_avail(buf); + if (ret < 0) { + compr_err(compr, "Error reading avail: %d\n", ret); +- goto out; ++ return ret; + } + + /* +@@ -1934,14 +1908,14 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, + if (buf->error) + snd_compr_stop_error(stream, + SNDRV_PCM_STATE_XRUN); +- goto out; ++ return ret; + } + + ret = wm_adsp_buffer_reenable_irq(buf); + if (ret < 0) { + compr_err(compr, "Failed to re-enable buffer IRQ: %d\n", + ret); +- goto out; ++ return ret; + } + } + } +@@ -1950,9 +1924,6 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, + tstamp->copied_total += buf->avail * CS_DSP_DATA_WORD_SIZE; + tstamp->sampling_rate = compr->sample_rate; + +-out: +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_pointer); +@@ -2063,15 +2034,13 @@ int wm_adsp_compr_copy(struct snd_soc_component *component, + struct wm_adsp *dsp = compr->dsp; + int ret; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + if (stream->direction == SND_COMPRESS_CAPTURE) + ret = wm_adsp_compr_read(compr, buf, count); + else + ret = -ENOTSUPP; + +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_copy); +diff --git a/sound/soc/codecs/wsa881x.c b/sound/soc/codecs/wsa881x.c +--- a/sound/soc/codecs/wsa881x.c ++++ b/sound/soc/codecs/wsa881x.c +@@ -1178,7 +1178,13 @@ static int wsa881x_runtime_resume(struct device *dev) + } + + regcache_cache_only(regmap, false); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) { ++ regcache_cache_only(regmap, true); ++ regcache_mark_dirty(regmap); ++ gpiod_direction_output(wsa881x->sd_n, 1); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wsa883x.c b/sound/soc/codecs/wsa883x.c +--- a/sound/soc/codecs/wsa883x.c ++++ b/sound/soc/codecs/wsa883x.c +@@ -4,6 +4,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -1237,9 +1238,8 @@ static int wsa883x_spkr_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +- mutex_lock(&wsa883x->sp_lock); +- wsa883x->pa_on = true; +- mutex_unlock(&wsa883x->sp_lock); ++ scoped_guard(mutex, &wsa883x->sp_lock) ++ wsa883x->pa_on = true; + + switch (wsa883x->dev_mode) { + case RECEIVER: +@@ -1290,9 +1290,8 @@ static int wsa883x_spkr_event(struct snd_soc_dapm_widget *w, + WSA883X_GLOBAL_PA_EN_MASK, 0); + snd_soc_component_write_field(component, WSA883X_PDM_WD_CTL, + WSA883X_PDM_EN_MASK, 0); +- mutex_lock(&wsa883x->sp_lock); +- wsa883x->pa_on = false; +- mutex_unlock(&wsa883x->sp_lock); ++ scoped_guard(mutex, &wsa883x->sp_lock) ++ wsa883x->pa_on = false; + break; + } + return 0; +@@ -1696,9 +1695,15 @@ static int wsa883x_runtime_suspend(struct device *dev) + static int wsa883x_runtime_resume(struct device *dev) + { + struct regmap *regmap = dev_get_regmap(dev, NULL); ++ int ret; + + regcache_cache_only(regmap, false); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) { ++ regcache_cache_only(regmap, true); ++ regcache_mark_dirty(regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wsa884x.c b/sound/soc/codecs/wsa884x.c +--- a/sound/soc/codecs/wsa884x.c ++++ b/sound/soc/codecs/wsa884x.c +@@ -1701,9 +1701,8 @@ static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +- mutex_lock(&wsa884x->sp_lock); +- wsa884x->pa_on = true; +- mutex_unlock(&wsa884x->sp_lock); ++ scoped_guard(mutex, &wsa884x->sp_lock) ++ wsa884x->pa_on = true; + + wsa884x_spkr_post_pmu(component, wsa884x); + +@@ -1717,9 +1716,8 @@ static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w, + WSA884X_PDM_WD_CTL_PDM_WD_EN_MASK, + 0x0); + +- mutex_lock(&wsa884x->sp_lock); +- wsa884x->pa_on = false; +- mutex_unlock(&wsa884x->sp_lock); ++ scoped_guard(mutex, &wsa884x->sp_lock) ++ wsa884x->pa_on = false; + break; + } + +@@ -2147,9 +2145,15 @@ static int wsa884x_runtime_suspend(struct device *dev) + static int wsa884x_runtime_resume(struct device *dev) + { + struct regmap *regmap = dev_get_regmap(dev, NULL); ++ int ret; + + regcache_cache_only(regmap, false); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) { ++ regcache_cache_only(regmap, true); ++ regcache_mark_dirty(regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wsa885x.c b/sound/soc/codecs/wsa885x.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/wsa885x.c +@@ -0,0 +1,1533 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. ++ */ ++ ++/* WSA885X codec driver */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Control Registers - Audio Processing */ ++#define WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S 0x0000 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK 0x0004 ++#define WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX 0x0140 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID 0x0208 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX 0x0240 ++#define WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER 0x0340 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0 0x4405 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1 0x4406 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB 0x4409 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB 0x6409 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB 0x440a ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB 0x640a ++#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS 0x0a04 ++#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS 0x0a40 ++#define WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE 0x0b10 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX 0x0e40 ++ ++/* Analog Top Registers - Power and Clock Control */ ++#define WSA885X_ANA_TOP_PON_CKSK_CTL_0 0x800d ++#define WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG 0x8024 ++#define WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG 0x8025 ++#define WSA885X_ANA_TOP_BG_TVP_OVRD_CTL 0x8034 ++ ++/* Analog PLL Registers */ ++#define WSA885X_ANA_PLL_DIV_CTL_0 0x8090 ++#define WSA885X_ANA_PLL_DIV_CTL_1 0x8091 ++#define WSA885X_ANA_TOP_PLL_VCO_CTL 0x8092 ++#define WSA885X_ANA_TOP_PLL_LOOPFILT_0 0x8093 ++#define WSA885X_ANA_TOP_PLL_OVRD_CTL 0x8098 ++#define WSA885X_ANA_TOP_PLL_STATUS_0 0x809a ++#define WSA885X_ANA_TOP_PLL_STATUS_1 0x809b ++ ++/* Analog Boost Control Registers */ ++#define WSA885X_ANA_TOP_BOOST_STB_CTRL2 0x805b ++#define WSA885X_ANA_TOP_BOOST_STB_CTRL3 0x805c ++#define WSA885X_ANA_TOP_BOOST_BYP_CTRL2 0x805e ++#define WSA885X_ANA_TOP_BOOST_BYP_CTRL3 0x805f ++#define WSA885X_ANA_TOP_BOOST_MISC 0x8063 ++#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2 0x8065 ++#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4 0x8067 ++ ++/* Analog IV Sense ADC Registers */ ++#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2 0x80ca ++#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3 0x80cb ++#define WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL 0x80cc ++#define WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2 0x80d0 ++ ++/* Analog Speaker Power Stage Registers */ ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3 0x8108 ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3 0x810b ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3 0x810e ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3 0x8111 ++#define WSA885X_ANA_TOP_SPK_TOP_SPARE3 0x813c ++#define WSA885X_SPK_TOP_LF_CH1_CTRL11 0x811c ++#define WSA885X_SPK_TOP_LF_CH1_TUNE1 0x811d ++#define WSA885X_SPK_TOP_LF_CH2_TUNE1 0x8129 ++#define WSA885X_SPK_TOP_LF_CH1_CTRL9 0x811a ++#define WSA885X_SPK_TOP_LF_CH2_CTRL9 0x8126 ++#define WSA885X_SPK_TOP_LF_CH2_CTRL11 0x8128 ++#define WSA885X_SPK_TOP_COMMON_CTRL2 0x8102 ++#define WSA885X_SPK_TOP_COMMON_TUNE1 0x8103 ++#define WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1 0x80ba ++#define WSA885X_DIG_CTRL0_TOP_CLK_CFG 0x8418 ++#define WSA885X_DIG_CTRL0_SDCA_COMMIT 0x8419 ++#define WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE 0x841a ++#define WSA885X_DIG_CTRL0_SYS_CLK_SEL 0x841b ++#define WSA885X_DIG_CTRL0_CDC_CLK_CTL 0x841c ++#define WSA885X_DIG_CTRL0_PA_FSM_CTL 0x8420 ++#define WSA885X_DIG_CTRL0_POWER_FSM_CTL0 0x8423 ++#define WSA885X_DIG_CTRL0_POWER_FSM_CTL1 0x8424 ++#define WSA885X_DIG_CTRL0_PA0_FSM_CTL1 0x842b ++#define WSA885X_DIG_CTRL0_PA1_FSM_CTL1 0x8435 ++#define WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL 0x8458 ++#define WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL 0x8470 ++#define WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1 0x84b4 ++#define WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1 0x84b7 ++#define WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1 0x84A0 ++#define WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1 0x84A4 ++ ++/* Digital Control 1 Registers - I2S/TDM Interface */ ++#define WSA885X_DIG_CTRL1_I2S_CTL0 0x85A0 ++#define WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX 0x85A2 ++#define WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX 0x85A3 ++#define WSA885X_DIG_CTRL1_I2S_TDM_CTL0 0x85A7 ++#define WSA885X_DIG_CTRL1_I2S_TDM_CTL1 0x85A9 ++#define WSA885X_DIG_CTRL1_I2S_TDM_CH_RX 0x85AA ++#define WSA885X_DIG_CTRL1_I2S_TDM_CH_TX 0x85AB ++#define WSA885X_DIG_CTRL1_I2S_RESET_CTL 0x85AE ++ ++/* CDC RX Path Registers - Audio Data Path */ ++#define WSA885X_CDC_RX0_RX_PATH_CFG0 0x8601 ++#define WSA885X_CDC_RX0_RX_PATH_CFG1 0x8602 ++#define WSA885X_CDC_RX0_RX_PATH_CTL 0x8606 ++#define WSA885X_RX0_RX_PATH_DSMDEM_CTL 0x8613 ++#define WSA885X_CDC_RX1_RX_PATH_CFG0 0x8621 ++#define WSA885X_CDC_RX1_RX_PATH_CFG1 0x8622 ++#define WSA885X_CDC_RX1_RX_PATH_CTL 0x8626 ++#define WSA885X_RX1_RX_PATH_DSMDEM_CTL 0x8633 ++ ++/* CDC Compander Registers - Dynamic Range Control */ ++#define WSA885X_CDC_COMPANDER0_CTL0 0x8640 ++#define WSA885X_CDC_COMPANDER0_CTL7 0x8647 ++#define WSA885X_CDC_COMPANDER1_CTL0 0x8660 ++#define WSA885X_CDC_COMPANDER1_CTL7 0x8667 ++ ++/* CDC Speaker Protection Registers - IV Sense */ ++#define WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL 0x86A1 ++#define WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL 0x86B1 ++#define WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL 0x86A9 ++#define WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL 0x86B9 ++ ++/* CDC Class-H Registers - Headroom Control */ ++#define WSA885X_CDC_CLSH_V1P8_BP_CTL1 0x86CD ++#define WSA885X_CDC_CLSH_V1P8_BP_CTL0 0x86CC ++#define WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0 0x86C7 ++#define WSA885X_CDC_CLSH_CLSH_V_HD_PA 0x86C3 ++#define WSA885X_CDC_CLSH_V1P8_BP_CTL2 0x86CE ++ ++/* Driver Constants */ ++#define WSA885X_CLK_RATE_FIXED 73728000 ++#define WSA885X_NUM_REGS 0x03 ++ ++/* Interrupt Registers */ ++#define WSA885X_INTR_STATUS0 0x8584 ++#define WSA885X_INTR_MASK0 0x8581 ++#define WSA885X_INTR_CLEAR0 0x8587 ++ ++/* Power and PA FSM Control Registers */ ++#define WSA885X_PA0_FSM_CTL0 0x842A ++#define WSA885X_PA1_FSM_CTL0 0x8434 ++ ++/* Digital Control GPIO and Interrupt Registers */ ++#define WSA885X_DIG_CTRL1_PIN_CT 0x8510 ++#define WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL 0x8518 ++#define WSA885X_DIG_CTRL1_INTR_MODE 0x8580 ++ ++#define WSA885X_I2S_CTL0_PCM_RATE_MASK GENMASK(4, 1) ++#define WSA885X_I2S_CTL0_ENABLE_MASK BIT(0) ++#define WSA885X_I2S_CTL0_PCM_RATE(v) \ ++ FIELD_PREP(WSA885X_I2S_CTL0_PCM_RATE_MASK, (v)) ++#define WSA885X_I2S_CTL0_PCM_RATE_8KHZ 0x0 ++#define WSA885X_I2S_CTL0_PCM_RATE_16KHZ 0x1 ++#define WSA885X_I2S_CTL0_PCM_RATE_32KHZ 0x2 ++#define WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ 0x3 ++#define WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ 0x4 ++#define WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ 0x5 ++#define WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ 0x6 ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK GENMASK(2, 0) ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK GENMASK(6, 4) ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT0(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, (v)) ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT1(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, (v)) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK GENMASK(2, 0) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK GENMASK(6, 4) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT2(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK, (v)) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT3(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK, (v)) ++#define WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK BIT(0) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK GENMASK(3, 2) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2 \ ++ FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 0) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4 \ ++ FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 1) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8 \ ++ FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 3) ++#define WSA885X_I2S_TDM_CH_TX_CH0_EN BIT(0) ++#define WSA885X_I2S_TDM_CH_TX_CH1_EN BIT(1) ++#define WSA885X_I2S_TDM_CH_TX_CH2_EN BIT(2) ++#define WSA885X_I2S_TDM_CH_TX_CH3_EN BIT(3) ++#define WSA885X_I2S_TDM_CH_RX_CH0_EN BIT(0) ++#define WSA885X_I2S_TDM_CH_RX_CH3_EN BIT(3) ++#define WSA885X_I2S_RESET_CTL_RESET_MASK BIT(0) ++#define WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK BIT(2) ++#define WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK BIT(3) ++#define WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK BIT(2) ++ ++#define WSA885X_I2S_TX_SLOT_ISENSE0 0x1 ++#define WSA885X_I2S_TX_SLOT_ISENSE1 0x2 ++#define WSA885X_I2S_TX_SLOT_CUR_SENSE0 0x5 ++#define WSA885X_I2S_TX_SLOT_CUR_SENSE1 0x6 ++ ++/* RX Sample Rate Index Values - Audio Playback Path */ ++#define WSA885X_RX_RATE_8000HZ 0x00 ++#define WSA885X_RX_RATE_16000HZ 0x01 ++#define WSA885X_RX_RATE_32000HZ 0x02 ++#define WSA885X_RX_RATE_44100HZ 0x03 ++#define WSA885X_RX_RATE_48000HZ 0x04 ++#define WSA885X_RX_RATE_96000HZ 0x05 ++#define WSA885X_RX_RATE_192000HZ 0x06 ++#define WSA885X_RX_RATE_384000HZ 0x07 ++ ++/* VI Sample Rate Index Values - Voltage/Current Sensing Path */ ++#define WSA885X_VI_RATE_8000HZ 0x00 ++#define WSA885X_VI_RATE_16000HZ 0x01 ++#define WSA885X_VI_RATE_44100HZ 0x02 ++#define WSA885X_VI_RATE_48000HZ 0x03 ++#define WSA885X_VI_RATE_96000HZ 0x04 ++#define WSA885X_VI_RATE_22050HZ 0x05 ++#define WSA885X_VI_RATE_24000HZ 0x06 ++#define WSA885X_VI_RATE_192000HZ 0x07 ++#define WSA885X_VI_RATE_384000HZ 0x08 ++ ++/* Channel Configuration Masks */ ++#define WSA885X_CHANNEL_STEREO 0x03 ++#define WSA885X_CHANNEL_MONO_LEFT 0x01 ++#define WSA885X_CHANNEL_MONO_RIGHT 0x02 ++ ++#define WSA885X_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ ++ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 | \ ++ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) ++ ++#define WSA885X_PLL_LOCK_BIT BIT(0) ++ ++#define WSA885X_FU21_VOL_STEPS 124 ++#define WSA885X_USAGE_MODE_MAX 8 ++static const DECLARE_TLV_DB_SCALE(wsa885x_fu21_digital_gain, -8400, 100, 0); ++ ++static bool wsa885x_is_valid_rx_slot_mask(u32 mask) ++{ ++ return mask == WSA885X_CHANNEL_MONO_LEFT || ++ mask == WSA885X_CHANNEL_MONO_RIGHT || ++ mask == WSA885X_CHANNEL_STEREO; ++} ++ ++static const char *const wsa885x_supply_name[] = { ++ "vdd-1p8", ++ "vdd-io", ++}; ++ ++enum { ++ WSA885X_BATT_1S = 1, ++ WSA885X_BATT_2S, ++}; ++ ++enum { ++ WSA885X_IRQ_INT_SAF2WAR = 0, ++ WSA885X_IRQ_INT_WAR2SAF, ++ WSA885X_IRQ_INT_DISABLE, ++ WSA885X_IRQ_INT_PA0_OCP, ++ WSA885X_IRQ_INT_PA1_OCP, ++ WSA885X_IRQ_INT_CLIP0, ++ WSA885X_IRQ_INT_CLIP1, ++ WSA885X_IRQ_INT_CLK_WD, ++ WSA885X_IRQ_INT_INTR_GPIO1_PIN, ++ WSA885X_IRQ_INT_INTR_GPIO2_PIN, ++ WSA885X_IRQ_INT_UVLO, ++ WSA885X_IRQ_INT_BOP, ++ WSA885X_IRQ_INT_PA0_FSM_ERR, ++ WSA885X_IRQ_INT_PA1_FSM_ERR, ++ WSA885X_IRQ_INT_MAIN_FSM_ERR, ++ WSA885X_IRQ_INT_PCM_DATA0_WD, ++ WSA885X_IRQ_INT_PCM_DATA1_WD, ++ WSA885X_IRQ_INT_PCM_DATA0_DC, ++ WSA885X_IRQ_INT_PCM_DATA1_DC, ++ WSA885X_IRQ_INT_PLL_UNLOCKED, ++ WSA885X_IRQ_INT_PROT_MODE_CHANGE, ++ WSA885X_IRQ_INT_PB_CLOCK_VALID, ++ WSA885X_IRQ_INT_SENSE_CLOCK_VALID, ++ WSA885X_IRQ_MAX, ++}; ++ ++struct wsa885x_priv { ++ struct i2c_client *client; ++ struct regmap *regmap; ++ struct device *dev; ++ struct snd_soc_component *component; ++ struct gpio_desc *sd_n; ++ struct reset_control *sd_reset; ++ u32 usage_mode; ++ u32 rx_slot_mask; ++ u32 batt_conf; ++ int stereo_vol_db; ++ struct mutex state_lock; /* protects mutable control state */ ++}; ++ ++struct wsa885x_reg_update { ++ unsigned int reg; ++ unsigned int mask; ++ unsigned int val; ++}; ++ ++static const struct regmap_range_cfg wsa885x_regmap_ranges[] = { ++ { ++ .range_min = 0, ++ .range_max = 0x88ff, ++ .selector_reg = 0x0, ++ .selector_mask = 0xFF, ++ .selector_shift = 0, ++ .window_start = 0, ++ .window_len = 0x100, ++ }, ++}; ++ ++static const struct reg_default wsa885x_codec_reg_defaults[] = { ++ {WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX, 0x04}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB, 0xac}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB, 0xac}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS, 0x03}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX, 0x03}, ++ {WSA885X_ANA_TOP_PON_CKSK_CTL_0, 0x00}, ++ {WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG, 0x19}, ++ {WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG, 0x22}, ++ {WSA885X_ANA_PLL_DIV_CTL_0, 0x0c}, ++ {WSA885X_ANA_PLL_DIV_CTL_1, 0x50}, ++ {WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00}, ++ {WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xb4}, ++ {WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00}, ++ {WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00}, ++ {WSA885X_ANA_TOP_BOOST_STB_CTRL2, 0x03}, ++ {WSA885X_ANA_TOP_BOOST_STB_CTRL3, 0x3c}, ++ {WSA885X_ANA_TOP_BOOST_BYP_CTRL2, 0xc5}, ++ {WSA885X_ANA_TOP_BOOST_BYP_CTRL3, 0x13}, ++ {WSA885X_ANA_TOP_BOOST_MISC, 0x79}, ++ {WSA885X_ANA_TOP_SPK_TOP_SPARE3, 0x00}, ++ {WSA885X_SPK_TOP_COMMON_CTRL2, 0x08}, ++ {WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x09}, ++ {WSA885X_SPK_TOP_LF_CH1_TUNE1, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH2_TUNE1, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH1_CTRL9, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH2_CTRL9, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x09}, ++ {WSA885X_SPK_TOP_COMMON_TUNE1, 0x03}, ++ {WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1, 0x00}, ++ {WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x0e}, ++ {WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2, 0x40}, ++ {WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4, 0xff}, ++ {WSA885X_ANA_TOP_PLL_STATUS_0, 0x00}, ++ {WSA885X_ANA_TOP_PLL_STATUS_1, 0x00}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2, 0x84}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x02}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL, 0x00}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2, 0xe0}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3, 0xa4}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3, 0xc9}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3, 0xa4}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3, 0xc9}, ++ {WSA885X_DIG_CTRL0_TOP_CLK_CFG, 0x00}, ++ {WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x00}, ++ {WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00}, ++ {WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00}, ++ {WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00}, ++ {WSA885X_DIG_CTRL0_POWER_FSM_CTL0, 0x05}, ++ {WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x00}, ++ {WSA885X_DIG_CTRL0_PA0_FSM_CTL1, 0x45}, ++ {WSA885X_DIG_CTRL0_PA1_FSM_CTL1, 0x45}, ++ {WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL, 0x7f}, ++ {WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x00}, ++ {WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1, 0x01}, ++ {WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1, 0x01}, ++ {WSA885X_DIG_CTRL1_I2S_CTL0, 0x06}, ++ {WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CTL0, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x05}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, WSA885X_I2S_TDM_CH_RX_CH3_EN}, ++ {WSA885X_CDC_RX0_RX_PATH_CFG0, 0x89}, ++ {WSA885X_CDC_RX0_RX_PATH_CFG1, 0x64}, ++ {WSA885X_CDC_RX0_RX_PATH_CTL, 0x24}, ++ {WSA885X_RX0_RX_PATH_DSMDEM_CTL, 0x01}, ++ {WSA885X_CDC_RX1_RX_PATH_CFG0, 0x89}, ++ {WSA885X_CDC_RX1_RX_PATH_CFG1, 0x64}, ++ {WSA885X_CDC_RX1_RX_PATH_CTL, 0x04}, ++ {WSA885X_RX1_RX_PATH_DSMDEM_CTL, 0x01}, ++ {WSA885X_CDC_COMPANDER0_CTL0, 0x01}, ++ {WSA885X_CDC_COMPANDER0_CTL7, 0x2a}, ++ {WSA885X_CDC_COMPANDER1_CTL0, 0x01}, ++ {WSA885X_CDC_COMPANDER1_CTL7, 0x2a}, ++ {WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x50}, ++ {WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0x6c}, ++ {WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0, 0x0d}, ++ {WSA885X_CDC_CLSH_CLSH_V_HD_PA, 0x03}, ++ {WSA885X_CDC_CLSH_V1P8_BP_CTL2, 0x05}, ++}; ++ ++static void wsa885x_multi_update_bits(struct regmap *regmap, ++ const struct wsa885x_reg_update *updates, ++ size_t num_updates) ++{ ++ size_t i; ++ ++ for (i = 0; i < num_updates; i++) ++ regmap_update_bits(regmap, updates[i].reg, ++ updates[i].mask, updates[i].val); ++} ++ ++static void wsa885x_toggle_irq_bit(struct wsa885x_priv *wsa885x, ++ unsigned int reg, unsigned int mask) ++{ ++ regmap_update_bits(wsa885x->regmap, reg, mask, 0); ++ regmap_update_bits(wsa885x->regmap, reg, mask, mask); ++} ++ ++static void wsa885x_pulse_irq_bit(struct wsa885x_priv *wsa885x, ++ unsigned int reg, unsigned int mask) ++{ ++ regmap_update_bits(wsa885x->regmap, reg, mask, 0); ++ regmap_update_bits(wsa885x->regmap, reg, mask, mask); ++ regmap_update_bits(wsa885x->regmap, reg, mask, 0); ++} ++ ++static int wsa885x_tdm_ctl0_slot_num_val(int slots, unsigned int *slot_num_val) ++{ ++ if (!slot_num_val) ++ return -EINVAL; ++ ++ switch (slots) { ++ case 2: ++ *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2; ++ return 0; ++ case 4: ++ *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4; ++ return 0; ++ case 8: ++ *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int wsa885x_reg_update_sequence(struct regmap *regmap, int slots) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x15 }, ++ { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x11 }, ++ }; ++ unsigned int slot_num_val; ++ int ret; ++ ++ if (!regmap) ++ return -EINVAL; ++ ++ ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val); ++ if (ret) ++ return ret; ++ ++ regmap_multi_reg_write(regmap, regs, ARRAY_SIZE(regs)); ++ ++ regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0, ++ WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, ++ slot_num_val); ++ regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0, ++ WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK, ++ WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK); ++ regmap_write(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH0_EN); ++ regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH1_EN, ++ WSA885X_I2S_TDM_CH_TX_CH1_EN); ++ ++ return 0; ++} ++ ++static int wsa885x_wait_for_pll_lock(struct wsa885x_priv *wsa885x) ++{ ++ unsigned int status = 0; ++ int cnt = 0, ret = 0; ++ ++ do { ++ usleep_range(1000, 1100); ++ ret = regmap_read(wsa885x->regmap, WSA885X_ANA_TOP_PLL_STATUS_0, &status); ++ if (ret) { ++ dev_err(wsa885x->dev, "PLL status read failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (status & WSA885X_PLL_LOCK_BIT) ++ return 0; ++ } while (++cnt < 20); ++ ++ dev_warn(wsa885x->dev, "PLL lock timeout after 20ms, status=0x%x\n", status); ++ return -ETIMEDOUT; ++} ++ ++static int wsa885x_2s_conf(struct wsa885x_priv *wsa885x) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_SPK_TOP_COMMON_TUNE1, 0x26 }, ++ { WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x0d }, ++ { WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x0d }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x71 }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0xAA }, ++ }; ++ ++ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++} ++ ++static const struct reg_sequence wsa885x_reg_init[] = { ++ { WSA885X_CDC_RX0_RX_PATH_CTL, 0x24 }, ++ { WSA885X_CDC_RX1_RX_PATH_CTL, 0x24 }, ++ { WSA885X_RX0_RX_PATH_DSMDEM_CTL, 0x01 }, ++ { WSA885X_RX1_RX_PATH_DSMDEM_CTL, 0x01 }, ++ { WSA885X_CDC_COMPANDER0_CTL0, 0x01 }, ++ { WSA885X_CDC_COMPANDER1_CTL0, 0x01 }, ++ { WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x0f }, ++ { WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x4f }, ++ { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x02 }, ++ { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x00 }, ++ { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK, 0x01 }, ++ { WSA885X_CDC_RX0_RX_PATH_CFG1, 0x60 }, ++ { WSA885X_CDC_RX1_RX_PATH_CFG1, 0x60 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3, 0xa5 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3, 0xa5 }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2, 0x85 }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x0c }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x0e }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL, 0x0c }, ++ { WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1, 0x01 }, ++ { WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1, 0x01 }, ++ { WSA885X_CDC_RX0_RX_PATH_CFG0, 0x88 }, ++ { WSA885X_CDC_RX0_RX_PATH_CFG0, 0x89 }, ++ { WSA885X_CDC_RX1_RX_PATH_CFG0, 0x88 }, ++ { WSA885X_CDC_RX1_RX_PATH_CFG0, 0x89 }, ++ { WSA885X_ANA_TOP_BOOST_STB_CTRL2, 0x82 }, ++ { WSA885X_ANA_TOP_BOOST_STB_CTRL3, 0x34 }, ++ { WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2, 0x41 }, ++ { WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4, 0x7f }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x50 }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0x6c }, ++ { WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0, 0x0d }, ++ { WSA885X_CDC_CLSH_CLSH_V_HD_PA, 0x03 }, ++ { WSA885X_DIG_CTRL0_POWER_FSM_CTL0, 0x05 }, ++ { WSA885X_ANA_TOP_PON_CKSK_CTL_0, 0x20 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3, 0x45 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3, 0x45 }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL2, 0x05 }, ++ { WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG, 0x35 }, ++ { WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG, 0x21 }, ++ { WSA885X_ANA_TOP_BOOST_BYP_CTRL2, 0xc7 }, ++ { WSA885X_ANA_TOP_BOOST_BYP_CTRL3, 0x11 }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2, 0x80 }, ++ { WSA885X_ANA_TOP_SPK_TOP_SPARE3, 0x08 }, ++ { WSA885X_DIG_CTRL0_PA0_FSM_CTL1, 0x47 }, ++ { WSA885X_DIG_CTRL0_PA1_FSM_CTL1, 0x47 }, ++ { WSA885X_CDC_COMPANDER0_CTL7, 0x34 }, ++ { WSA885X_CDC_COMPANDER1_CTL7, 0x34 }, ++ { WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL, 0x79 }, ++}; ++ ++static int wsa885x_hw_init(struct wsa885x_priv *wsa885x) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL, 0x2e }, ++ { WSA885X_DIG_CTRL1_INTR_MODE, 0x01 }, ++ { WSA885X_DIG_CTRL1_PIN_CT, 0x04 }, ++ }; ++ int ret; ++ ++ ret = regmap_multi_reg_write(wsa885x->regmap, wsa885x_reg_init, ++ ARRAY_SIZE(wsa885x_reg_init)); ++ if (ret) ++ return ret; ++ ++ if (wsa885x->batt_conf == WSA885X_BATT_2S) { ++ ret = wsa885x_2s_conf(wsa885x); ++ if (ret) ++ return ret; ++ } ++ ++ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++} ++ ++static int wsa885x_unmask_interrupts(struct wsa885x_priv *wsa885x) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_INTR_MASK0, 0x00 }, ++ { WSA885X_INTR_MASK0 + 1, 0x00 }, ++ { WSA885X_INTR_MASK0 + 2, 0xf8 }, ++ }; ++ ++ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++} ++ ++static int wsa885x_wait_for_pde_state(struct wsa885x_priv *wsa885x, int ps) ++{ ++ unsigned int act_ps = 0, clock_valid = 0; ++ int rc = 0, cnt = 0; ++ ++ if (ps < 0 || ps > 3) ++ return -EINVAL; ++ ++ do { ++ usleep_range(1000, 1500); ++ rc = regmap_read(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS, ++ &act_ps); ++ if (rc) { ++ dev_err(wsa885x->dev, "PDE state read failed: %d\n", rc); ++ return rc; ++ } ++ if (act_ps == ps) ++ return 0; ++ } while (++cnt < 5); ++ ++ if (regmap_read(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID, ++ &clock_valid)) ++ dev_err(wsa885x->dev, ++ "PDE power state %d request failed, actual_ps %d, clock_valid read failed\n", ++ ps, act_ps); ++ else ++ dev_err(wsa885x->dev, ++ "PDE power state %d request failed, actual_ps %d, clock_valid:%d\n", ++ ps, act_ps, clock_valid); ++ ++ return -ETIMEDOUT; ++} ++ ++static void wsa885x_program_stereo_volume(struct wsa885x_priv *wsa885x, ++ int stereo_vol_db, bool commit) ++{ ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB, ++ (u8)(s8)stereo_vol_db); ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00); ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB, ++ (u8)(s8)stereo_vol_db); ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00); ++ ++ if (commit) ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01); ++} ++ ++static int wsa885x_codec_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct wsa885x_priv *wsa885x; ++ u8 pcm_rate, cs21_sample_rate_idx, cs24_sample_rate_idx; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ switch (params_rate(params)) { ++ case 8000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_8KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_8000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_8000HZ; ++ break; ++ case 16000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_16KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_16000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_16000HZ; ++ break; ++ case 32000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_32KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_32000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ; ++ break; ++ case 44100: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_44100HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_44100HZ; ++ break; ++ case 48000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_48000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ; ++ break; ++ case 88200: ++ case 96000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_96000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_96000HZ; ++ break; ++ case 176400: ++ case 192000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_192000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_192000HZ; ++ break; ++ case 352800: ++ case 384000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_384000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_384000HZ; ++ break; ++ default: ++ dev_err(wsa885x->dev, "sampling rate %d is not supported\n", params_rate(params)); ++ return -EINVAL; ++ } ++ ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0, ++ WSA885X_I2S_CTL0_PCM_RATE_MASK | ++ WSA885X_I2S_CTL0_ENABLE_MASK, ++ WSA885X_I2S_CTL0_PCM_RATE(pcm_rate) | ++ WSA885X_I2S_CTL0_ENABLE_MASK); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00); ++ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX, ++ cs21_sample_rate_idx); ++ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX, ++ cs24_sample_rate_idx); ++ ++ mutex_lock(&wsa885x->state_lock); ++ wsa885x_program_stereo_volume(wsa885x, wsa885x->stereo_vol_db, false); ++ mutex_unlock(&wsa885x->state_lock); ++ ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01); ++ ++ return 0; ++} ++ ++static int wsa885x_codec_set_tdm_slot(struct snd_soc_dai *dai, ++ unsigned int tx_slot_mask, ++ unsigned int rx_slot_mask, int slots, ++ int slot_width) ++{ ++ static const struct wsa885x_reg_update stereo_updates[] = { ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_ISENSE1) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK, ++ WSA885X_I2S_CFG1_TDM_TX_SLOT2(WSA885X_I2S_TX_SLOT_CUR_SENSE0) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK, ++ WSA885X_I2S_CFG1_TDM_TX_SLOT3(WSA885X_I2S_TX_SLOT_CUR_SENSE1) }, ++ }; ++ static const struct wsa885x_reg_update mono_left_updates[] = { ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE0) }, ++ }; ++ static const struct wsa885x_reg_update mono_right_updates[] = { ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE1) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE1) }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ unsigned int slot_num_val; ++ u32 mask; ++ int ret; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val); ++ if (ret) { ++ dev_err(wsa885x->dev, "%s: unsupported slot count %d\n", ++ __func__, slots); ++ return ret; ++ } ++ ++ if (rx_slot_mask && !wsa885x_is_valid_rx_slot_mask(rx_slot_mask)) { ++ dev_err(wsa885x->dev, ++ "%s: unsupported rx_slot_mask 0x%x\n", ++ __func__, rx_slot_mask); ++ return -EINVAL; ++ } ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (rx_slot_mask) ++ wsa885x->rx_slot_mask = rx_slot_mask; ++ else if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask)) ++ wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; ++ mask = wsa885x->rx_slot_mask; ++ ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, ++ WSA885X_I2S_RESET_CTL_RESET_MASK, ++ WSA885X_I2S_RESET_CTL_RESET_MASK); ++ ++ if (mask == WSA885X_CHANNEL_STEREO) { ++ wsa885x_multi_update_bits(wsa885x->regmap, stereo_updates, ++ ARRAY_SIZE(stereo_updates)); ++ ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); ++ if (ret) ++ goto exit_unlock; ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH2_EN, ++ WSA885X_I2S_TDM_CH_TX_CH2_EN); ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH3_EN, ++ WSA885X_I2S_TDM_CH_TX_CH3_EN); ++ } else if (mask == WSA885X_CHANNEL_MONO_LEFT) { ++ wsa885x_multi_update_bits(wsa885x->regmap, mono_left_updates, ++ ARRAY_SIZE(mono_left_updates)); ++ ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); ++ if (ret) ++ goto exit_unlock; ++ } else if (mask == WSA885X_CHANNEL_MONO_RIGHT) { ++ wsa885x_multi_update_bits(wsa885x->regmap, mono_right_updates, ++ ARRAY_SIZE(mono_right_updates)); ++ ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); ++ if (ret) ++ goto exit_unlock; ++ } ++ ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0, ++ WSA885X_I2S_CTL0_ENABLE_MASK, ++ WSA885X_I2S_CTL0_ENABLE_MASK); ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, ++ WSA885X_I2S_RESET_CTL_RESET_MASK, 0); ++ ++ ret = 0; ++ ++exit_unlock: ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return ret; ++} ++ ++static int wsa885x_codec_set_sysclk(struct snd_soc_dai *dai, int clk_id, ++ unsigned int freq, int dir) ++{ ++ static const struct reg_sequence pll_prep[] = { ++ { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x03 }, ++ { WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x04 }, ++ { WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xB4 }, ++ { WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00 }, ++ { WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00 }, ++ }; ++ static const struct reg_sequence pll_cleanup[] = { ++ { WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00 }, ++ { WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00 }, ++ { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00 }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ u32 pll_div; ++ int ret = 0; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ if (!freq) ++ return -EINVAL; ++ if (WSA885X_CLK_RATE_FIXED % freq) ++ return -EINVAL; ++ pll_div = WSA885X_CLK_RATE_FIXED / freq; ++ if (pll_div > 0xff) ++ return -EINVAL; ++ ++ regmap_multi_reg_write(wsa885x->regmap, pll_prep, ARRAY_SIZE(pll_prep)); ++ regmap_write(wsa885x->regmap, WSA885X_ANA_PLL_DIV_CTL_0, pll_div); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x02); ++ ++ ret = wsa885x_wait_for_pll_lock(wsa885x); ++ if (ret) { ++ dev_err(wsa885x->dev, "PLL lock failed, aborting sysclk configuration\n"); ++ regmap_multi_reg_write(wsa885x->regmap, pll_cleanup, ++ ARRAY_SIZE(pll_cleanup)); ++ return ret; ++ } ++ ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x01); ++ regmap_write(wsa885x->regmap, WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00); ++ ++ return 0; ++} ++ ++static int wsa885x_codec_mute_stream(struct snd_soc_dai *dai, int mute, int stream) ++{ ++ static const struct reg_sequence mute_regs[] = { ++ { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03 }, ++ }; ++ static const struct reg_sequence mute_commit_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01 }, ++ { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, ++ }; ++ static const struct reg_sequence unmute_prep_head_regs[] = { ++ { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, ++ }; ++ static const struct reg_sequence unmute_prep_tail_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01 }, ++ }; ++ static const struct reg_sequence unmute_volume_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00 }, ++ }; ++ static const struct reg_sequence unmute_commit_regs[] = { ++ { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x00 }, ++ }; ++ static const struct reg_sequence unmute_finish_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x00 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x00 }, ++ { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ int ret = 0, ps0 = 0, ps3 = 3; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ if (stream != SNDRV_PCM_STREAM_PLAYBACK) ++ return 0; ++ ++ mutex_lock(&wsa885x->state_lock); ++ ++ if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX) { ++ ret = -EINVAL; ++ goto exit_unlock; ++ } ++ ++ if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask)) ++ wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; ++ ++ if (mute) { ++ regmap_multi_reg_write(wsa885x->regmap, mute_regs, ++ ARRAY_SIZE(mute_regs)); ++ ret = wsa885x_wait_for_pde_state(wsa885x, ps3); ++ if (ret) { ++ dev_err(wsa885x->dev, ++ "PS3 transition failed: %d\n", ret); ++ } else { ++ regmap_multi_reg_write(wsa885x->regmap, mute_commit_regs, ++ ARRAY_SIZE(mute_commit_regs)); ++ } ++ } else { ++ regmap_multi_reg_write(wsa885x->regmap, unmute_prep_head_regs, ++ ARRAY_SIZE(unmute_prep_head_regs)); ++ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE, ++ wsa885x->usage_mode); ++ regmap_multi_reg_write(wsa885x->regmap, unmute_prep_tail_regs, ++ ARRAY_SIZE(unmute_prep_tail_regs)); ++ wsa885x_program_stereo_volume(wsa885x, wsa885x->stereo_vol_db, false); ++ regmap_multi_reg_write(wsa885x->regmap, unmute_volume_regs, ++ ARRAY_SIZE(unmute_volume_regs)); ++ regmap_multi_reg_write(wsa885x->regmap, unmute_commit_regs, ++ ARRAY_SIZE(unmute_commit_regs)); ++ ret = wsa885x_wait_for_pde_state(wsa885x, ps0); ++ if (ret) ++ goto exit_unlock; ++ ++ if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_STEREO) { ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, ++ WSA885X_I2S_TDM_CH_RX_CH0_EN | ++ WSA885X_I2S_TDM_CH_RX_CH3_EN); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x03); ++ } else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_LEFT) { ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, ++ WSA885X_I2S_TDM_CH_RX_CH3_EN); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x01); ++ } else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_RIGHT) { ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, ++ WSA885X_I2S_TDM_CH_RX_CH0_EN); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x02); ++ } ++ ++ regmap_multi_reg_write(wsa885x->regmap, unmute_finish_regs, ++ ARRAY_SIZE(unmute_finish_regs)); ++ } ++ ++exit_unlock: ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return ret; ++} ++ ++static int wsa885x_codec_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) ++ return 0; ++ ++ mutex_lock(&wsa885x->state_lock); ++ regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 0; ++} ++ ++static const struct snd_soc_dai_ops wsa885x_dai_ops = { ++ .hw_params = wsa885x_codec_hw_params, ++ .set_tdm_slot = wsa885x_codec_set_tdm_slot, ++ .set_sysclk = wsa885x_codec_set_sysclk, ++ .mute_stream = wsa885x_codec_mute_stream, ++ .hw_free = wsa885x_codec_hw_free, ++}; ++ ++static struct snd_soc_dai_driver wsa885x_dai[] = { ++ { ++ .name = "wsa885x_dai_drv", ++ .playback = { ++ .stream_name = "WSA885X TDM Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = WSA885X_RATES, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | ++ SNDRV_PCM_FMTBIT_S32_LE, ++ }, ++ .ops = &wsa885x_dai_ops, ++ }, ++}; ++ ++static void wsa885x_reset_assert(void *data) ++{ ++ struct wsa885x_priv *wsa885x = data; ++ ++ if (wsa885x->sd_reset) ++ reset_control_assert(wsa885x->sd_reset); ++ else ++ gpiod_direction_output(wsa885x->sd_n, 1); ++} ++ ++static void wsa885x_reset_deassert(struct wsa885x_priv *wsa885x) ++{ ++ if (wsa885x->sd_reset) ++ reset_control_deassert(wsa885x->sd_reset); ++ else ++ gpiod_direction_output(wsa885x->sd_n, 0); ++} ++ ++static int wsa885x_get_reset(struct device *dev, struct wsa885x_priv *wsa885x) ++{ ++ wsa885x->sd_reset = devm_reset_control_get_optional_shared(dev, NULL); ++ if (IS_ERR(wsa885x->sd_reset)) ++ return dev_err_probe(dev, PTR_ERR(wsa885x->sd_reset), ++ "Failed to get reset\n"); ++ else if (wsa885x->sd_reset) ++ return 0; ++ ++ wsa885x->sd_n = devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH); ++ if (IS_ERR(wsa885x->sd_n)) ++ return dev_err_probe(dev, PTR_ERR(wsa885x->sd_n), ++ "Shutdown Control GPIO not found\n"); ++ ++ return 0; ++} ++ ++static bool wsa885x_volatile_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case WSA885X_ANA_TOP_PLL_STATUS_0: ++ case WSA885X_ANA_TOP_PLL_STATUS_1: ++ case WSA885X_DIG_CTRL0_SDCA_COMMIT: ++ case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS: ++ case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID: ++ case WSA885X_INTR_STATUS0: ++ case WSA885X_INTR_STATUS0 + 1: ++ case WSA885X_INTR_STATUS0 + 2: ++ case WSA885X_INTR_CLEAR0: ++ case WSA885X_INTR_CLEAR0 + 1: ++ case WSA885X_INTR_CLEAR0 + 2: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static bool wsa885x_readable_register(struct device *dev, unsigned int reg) ++{ ++ if (reg == WSA885X_INTR_CLEAR0 || ++ reg == WSA885X_INTR_CLEAR0 + 1 || ++ reg == WSA885X_INTR_CLEAR0 + 2) ++ return false; ++ return reg <= 0x88ff; ++} ++ ++static bool wsa885x_writeable_register(struct device *dev, unsigned int reg) ++{ ++ if (reg > 0x88ff) ++ return false; ++ ++ switch (reg) { ++ case WSA885X_ANA_TOP_PLL_STATUS_0: ++ case WSA885X_ANA_TOP_PLL_STATUS_1: ++ case WSA885X_INTR_STATUS0: ++ case WSA885X_INTR_STATUS0 + 1: ++ case WSA885X_INTR_STATUS0 + 2: ++ case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS: ++ case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID: ++ return false; ++ default: ++ return true; ++ } ++} ++ ++static const struct regmap_config wsa885x_regmap_cfg = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = 0x88FF, ++ .ranges = wsa885x_regmap_ranges, ++ .num_ranges = ARRAY_SIZE(wsa885x_regmap_ranges), ++ .reg_defaults = wsa885x_codec_reg_defaults, ++ .num_reg_defaults = ARRAY_SIZE(wsa885x_codec_reg_defaults), ++ .volatile_reg = wsa885x_volatile_register, ++ .writeable_reg = wsa885x_writeable_register, ++ .readable_reg = wsa885x_readable_register, ++ .cache_type = REGCACHE_MAPLE, ++ .use_single_read = true, ++ .use_single_write = true, ++}; ++ ++static int wsa885x_component_probe(struct snd_soc_component *component) ++{ ++ struct wsa885x_priv *wsa885x = ++ snd_soc_component_get_drvdata(component); ++ int ret; ++ ++ wsa885x->component = component; ++ snd_soc_component_init_regmap(component, wsa885x->regmap); ++ ++ ret = wsa885x_hw_init(wsa885x); ++ if (ret) ++ return ret; ++ ++ return wsa885x_unmask_interrupts(wsa885x); ++} ++ ++static int wsa885x_stereo_gain_offset_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ int val; ++ ++ mutex_lock(&wsa885x->state_lock); ++ val = wsa885x->stereo_vol_db + 84; ++ mutex_unlock(&wsa885x->state_lock); ++ if (val < 0 || val > WSA885X_FU21_VOL_STEPS) ++ return -ERANGE; ++ ++ ucontrol->value.integer.value[0] = val; ++ return 0; ++} ++ ++static int wsa885x_stereo_gain_offset_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ long val; ++ int stereo_vol_db; ++ ++ val = ucontrol->value.integer.value[0]; ++ ++ if (val < 0 || val > WSA885X_FU21_VOL_STEPS) { ++ dev_err(component->dev, "%s: Invalid range, Val: %ld\n", __func__, val); ++ return -EINVAL; ++ } ++ ++ stereo_vol_db = (int)val - 84; ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->stereo_vol_db == stereo_vol_db) { ++ mutex_unlock(&wsa885x->state_lock); ++ return 0; ++ } ++ ++ wsa885x_program_stereo_volume(wsa885x, stereo_vol_db, true); ++ wsa885x->stereo_vol_db = stereo_vol_db; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 1; ++} ++ ++static int wsa885x_usage_modes_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX) { ++ mutex_unlock(&wsa885x->state_lock); ++ return -ERANGE; ++ } ++ ++ ucontrol->value.integer.value[0] = wsa885x->usage_mode; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 0; ++} ++ ++static int wsa885x_usage_modes_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ u32 val = ucontrol->value.integer.value[0]; ++ ++ if (val > WSA885X_USAGE_MODE_MAX) ++ return -EINVAL; ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->usage_mode == val) { ++ mutex_unlock(&wsa885x->state_lock); ++ return 0; ++ } ++ ++ wsa885x->usage_mode = val; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 1; ++} ++ ++static int wsa885x_rx_slot_mask_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ u32 mask; ++ ++ mutex_lock(&wsa885x->state_lock); ++ mask = wsa885x->rx_slot_mask; ++ mutex_unlock(&wsa885x->state_lock); ++ if (!wsa885x_is_valid_rx_slot_mask(mask)) ++ return -ERANGE; ++ ++ ucontrol->value.integer.value[0] = mask; ++ ++ return 0; ++} ++ ++static int wsa885x_rx_slot_mask_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ u32 mask = ucontrol->value.integer.value[0]; ++ ++ if (!wsa885x_is_valid_rx_slot_mask(mask)) ++ return -EINVAL; ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->rx_slot_mask == mask) { ++ mutex_unlock(&wsa885x->state_lock); ++ return 0; ++ } ++ ++ wsa885x->rx_slot_mask = mask; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 1; ++} ++ ++static const struct snd_kcontrol_new wsa885x_snd_controls[] = { ++ SOC_SINGLE_EXT("Usage Mode", SND_SOC_NOPM, 0, WSA885X_USAGE_MODE_MAX, 0, ++ wsa885x_usage_modes_get, ++ wsa885x_usage_modes_put), ++ ++ SOC_SINGLE_EXT_TLV("Speaker Volume", SND_SOC_NOPM, ++ 0, WSA885X_FU21_VOL_STEPS, 0, ++ wsa885x_stereo_gain_offset_get, ++ wsa885x_stereo_gain_offset_put, ++ wsa885x_fu21_digital_gain), ++ ++ SOC_SINGLE_EXT("Rx Slot Mask", SND_SOC_NOPM, 0, 3, 0, ++ wsa885x_rx_slot_mask_get, ++ wsa885x_rx_slot_mask_put), ++}; ++ ++static const struct snd_soc_component_driver wsa885x_component = { ++ .name = "wsa885x", ++ .probe = wsa885x_component_probe, ++ .controls = wsa885x_snd_controls, ++ .num_controls = ARRAY_SIZE(wsa885x_snd_controls), ++}; ++ ++static irqreturn_t wsa885x_handle_irq(int irq_idx, void *data) ++{ ++ struct wsa885x_priv *wsa885x = data; ++ ++ if (irq_idx < 0 || irq_idx >= WSA885X_IRQ_MAX) ++ return IRQ_NONE; ++ ++ switch (irq_idx) { ++ case WSA885X_IRQ_INT_SAF2WAR: ++ case WSA885X_IRQ_INT_WAR2SAF: ++ case WSA885X_IRQ_INT_DISABLE: ++ case WSA885X_IRQ_INT_INTR_GPIO1_PIN: ++ case WSA885X_IRQ_INT_INTR_GPIO2_PIN: ++ case WSA885X_IRQ_INT_PA0_OCP: ++ case WSA885X_IRQ_INT_PA1_OCP: ++ case WSA885X_IRQ_INT_CLIP0: ++ case WSA885X_IRQ_INT_CLIP1: ++ case WSA885X_IRQ_INT_CLK_WD: ++ case WSA885X_IRQ_INT_BOP: ++ case WSA885X_IRQ_INT_UVLO: ++ case WSA885X_IRQ_INT_PCM_DATA0_DC: ++ case WSA885X_IRQ_INT_PCM_DATA1_DC: ++ case WSA885X_IRQ_INT_PLL_UNLOCKED: ++ case WSA885X_IRQ_INT_PROT_MODE_CHANGE: ++ case WSA885X_IRQ_INT_PB_CLOCK_VALID: ++ case WSA885X_IRQ_INT_SENSE_CLOCK_VALID: ++ break; ++ case WSA885X_IRQ_INT_PCM_DATA0_WD: ++ case WSA885X_IRQ_INT_PCM_DATA1_WD: ++ if (irq_idx == WSA885X_IRQ_INT_PCM_DATA0_WD) ++ wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1, ++ WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK); ++ else ++ wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1, ++ WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK); ++ break; ++ case WSA885X_IRQ_INT_PA0_FSM_ERR: ++ case WSA885X_IRQ_INT_PA1_FSM_ERR: ++ case WSA885X_IRQ_INT_MAIN_FSM_ERR: ++ if (irq_idx == WSA885X_IRQ_INT_MAIN_FSM_ERR) { ++ wsa885x_pulse_irq_bit(wsa885x, WSA885X_DIG_CTRL0_POWER_FSM_CTL0, ++ WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK); ++ } else if (irq_idx == WSA885X_IRQ_INT_PA0_FSM_ERR) { ++ wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA0_FSM_CTL0, ++ WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK); ++ } else if (irq_idx == WSA885X_IRQ_INT_PA1_FSM_ERR) { ++ wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA1_FSM_CTL0, ++ WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK); ++ } ++ break; ++ default: ++ break; ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t wsa885x_interrupt_handler(int irq, void *data) ++{ ++ static const unsigned int status_reg[WSA885X_NUM_REGS] = { ++ WSA885X_INTR_STATUS0, ++ WSA885X_INTR_STATUS0 + 1, ++ WSA885X_INTR_STATUS0 + 2, ++ }; ++ static const unsigned int clear_reg[WSA885X_NUM_REGS] = { ++ WSA885X_INTR_CLEAR0, ++ WSA885X_INTR_CLEAR0 + 1, ++ WSA885X_INTR_CLEAR0 + 2, ++ }; ++ unsigned int status[WSA885X_NUM_REGS] = { 0 }; ++ struct wsa885x_priv *wsa885x = data; ++ irqreturn_t handled = IRQ_NONE; ++ irqreturn_t irq_ret; ++ int i, bit, ret, irq_num; ++ ++ for (i = 0; i < WSA885X_NUM_REGS; i++) { ++ ret = regmap_read(wsa885x->regmap, status_reg[i], &status[i]); ++ if (ret) { ++ dev_err(wsa885x->dev, ++ "Failed to read status_reg[%d] (0x%x): %d\n", ++ i, status_reg[i], ret); ++ status[i] = 0; ++ continue; ++ } ++ } ++ ++ for (i = 0; i < WSA885X_NUM_REGS; i++) { ++ for (bit = 0; bit < 8; bit++) { ++ if (status[i] & BIT(bit)) { ++ irq_num = i * 8 + bit; ++ regmap_write(wsa885x->regmap, clear_reg[i], BIT(bit)); ++ regmap_write(wsa885x->regmap, clear_reg[i], 0); ++ if (irq_num >= WSA885X_IRQ_MAX) { ++ dev_warn_ratelimited(wsa885x->dev, ++ "Unexpected IRQ bit %d (reg %d)\n", ++ bit, i); ++ handled = IRQ_HANDLED; ++ continue; ++ } ++ irq_ret = wsa885x_handle_irq(irq_num, wsa885x); ++ if (irq_ret == IRQ_HANDLED) ++ handled = IRQ_HANDLED; ++ } ++ } ++ } ++ return handled; ++} ++ ++static int wsa885x_register_irq(struct wsa885x_priv *wsa885x) ++{ ++ if (!wsa885x->client->irq) ++ return dev_err_probe(wsa885x->dev, -EINVAL, ++ "IRQ is not configured\n"); ++ ++ return devm_request_threaded_irq(wsa885x->dev, wsa885x->client->irq, NULL, ++ wsa885x_interrupt_handler, ++ IRQF_ONESHOT, ++ dev_name(wsa885x->dev), wsa885x); ++} ++ ++static int wsa885x_probe(struct i2c_client *client) ++{ ++ struct wsa885x_priv *wsa885x; ++ const struct snd_soc_component_driver *component_driver = &wsa885x_component; ++ const char *battery_config; ++ unsigned int i; ++ int ret; ++ struct device *dev = &client->dev; ++ ++ wsa885x = devm_kzalloc(dev, sizeof(*wsa885x), GFP_KERNEL); ++ if (!wsa885x) ++ return -ENOMEM; ++ ++ wsa885x->client = client; ++ wsa885x->dev = dev; ++ wsa885x->stereo_vol_db = -84; ++ wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; ++ mutex_init(&wsa885x->state_lock); ++ wsa885x->regmap = devm_regmap_init_i2c(client, &wsa885x_regmap_cfg); ++ ++ if (IS_ERR(wsa885x->regmap)) ++ return PTR_ERR(wsa885x->regmap); ++ ++ ret = device_property_read_string(dev, "qcom,battery-config", ++ &battery_config); ++ if (ret) { ++ wsa885x->batt_conf = WSA885X_BATT_1S; ++ } else if (!strcmp(battery_config, "1s")) { ++ wsa885x->batt_conf = WSA885X_BATT_1S; ++ } else if (!strcmp(battery_config, "2s")) { ++ wsa885x->batt_conf = WSA885X_BATT_2S; ++ } else { ++ return dev_err_probe(dev, -EINVAL, ++ "Invalid battery config %s (expected 1s or 2s)\n", ++ battery_config); ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(wsa885x_supply_name); i++) { ++ ret = devm_regulator_get_enable(dev, wsa885x_supply_name[i]); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "Failed to enable regulator %s\n", ++ wsa885x_supply_name[i]); ++ } ++ ++ ret = wsa885x_get_reset(dev, wsa885x); ++ if (ret) ++ return ret; ++ ++ wsa885x_reset_deassert(wsa885x); ++ usleep_range(5000, 5500); ++ ++ ret = devm_add_action_or_reset(dev, wsa885x_reset_assert, wsa885x); ++ if (ret) ++ return dev_err_probe(dev, ret, "devm_add_action_or_reset failed\n"); ++ ++ i2c_set_clientdata(client, wsa885x); ++ ++ ret = wsa885x_register_irq(wsa885x); ++ if (ret) ++ return dev_err_probe(dev, ret, "wsa885x irq registration failed\n"); ++ ++ ret = devm_snd_soc_register_component(dev, component_driver, ++ wsa885x_dai, ++ ARRAY_SIZE(wsa885x_dai)); ++ if (ret) ++ return dev_err_probe(dev, ret, "Codec component registration failed\n"); ++ ++ return 0; ++} ++ ++static const struct of_device_id wsa885x_dt_match[] = { ++ { ++ .compatible = "qcom,wsa8855", ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, wsa885x_dt_match); ++ ++static const struct i2c_device_id wsa885x_id[] = { ++ { ++ .name = "wsa885x", ++ .driver_data = 0, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, wsa885x_id); ++ ++static struct i2c_driver wsa885x_driver = { ++ .driver = { ++ .name = "wsa885x", ++ .of_match_table = wsa885x_dt_match, ++ }, ++ .probe = wsa885x_probe, ++ .id_table = wsa885x_id, ++}; ++ ++module_i2c_driver(wsa885x_driver); ++ ++MODULE_DESCRIPTION("ASoC WSA885X Stereo Smart PA Codec Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/sound/soc/codecs/zl38060.c b/sound/soc/codecs/zl38060.c +--- a/sound/soc/codecs/zl38060.c ++++ b/sound/soc/codecs/zl38060.c +@@ -162,7 +162,7 @@ static int zl38_fw_send_xaddr(struct regmap *regmap, const void *data) + static int zl38_load_firmware(struct device *dev, struct regmap *regmap) + { + const struct ihex_binrec *rec; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + u32 addr; + u16 len; + int err; +@@ -180,7 +180,7 @@ static int zl38_load_firmware(struct device *dev, struct regmap *regmap) + return err; + err = zl38_fw_enter_boot_mode(regmap); + if (err) +- goto out; ++ return err; + rec = (const struct ihex_binrec *)fw->data; + while (rec) { + addr = be32_to_cpu(rec->addr); +@@ -195,15 +195,12 @@ static int zl38_load_firmware(struct device *dev, struct regmap *regmap) + err = -EINVAL; + } + if (err) +- goto out; ++ return err; + /* next ! */ + rec = ihex_next_binrec(rec); + } +- err = zl38_fw_go(regmap); + +-out: +- release_firmware(fw); +- return err; ++ return zl38_fw_go(regmap); + } + + +diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c +--- a/sound/soc/dwc/dwc-i2s.c ++++ b/sound/soc/dwc/dwc-i2s.c +@@ -955,7 +955,9 @@ static int dw_i2s_probe(struct platform_device *pdev) + } + + irq = platform_get_irq_optional(pdev, 0); +- if (irq >= 0) { ++ if (irq == -EPROBE_DEFER) ++ return irq; ++ if (irq > 0) { + ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0, + pdev->name, dev); + if (ret < 0) { +diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c +--- a/sound/soc/fsl/fsl-asoc-card.c ++++ b/sound/soc/fsl/fsl-asoc-card.c +@@ -69,7 +69,6 @@ static const struct snd_pcm_hw_constraint_list cs42888_channel_constraints = { + + /** + * struct codec_priv - CODEC private data +- * @mclk: Main clock of the CODEC + * @mclk_freq: Clock rate of MCLK + * @free_freq: Clock rate of MCLK for hw_free() + * @mclk_id: MCLK (or main clock) id for set_sysclk() +@@ -80,7 +79,6 @@ static const struct snd_pcm_hw_constraint_list cs42888_channel_constraints = { + * to stay within PLL frequency limits + */ + struct codec_priv { +- struct clk *mclk; + unsigned long mclk_freq; + unsigned long free_freq; + u32 mclk_id; +@@ -109,12 +107,58 @@ struct cpu_priv { + u32 slot_num; + }; + ++struct fsl_asoc_card_priv; ++ ++/* ++ * struct fsl_asoc_card_pdata - per-compatible static card description ++ * @sysclk_dir: initial CPU SYSCLK direction override (0 = leave default IN) ++ * @sysclk_ratio: SYSCLK ratio on sample rate (0 = not used) ++ * @slot_width: TDM slot width (0 = not TDM) ++ * @codec_dai_name: name of the codec DAI ++ * @codec_mclk_id: MCLK id passed to set_sysclk() for the codec ++ * @codec_fll_id: FLL id; only valid when has_pll is true ++ * @codec_pll_id: PLL id; only valid when has_pll is true ++ * @codec_pll_ratio_s24: PLL output ratio for S24_LE ++ * @has_pll: codec uses PLL/FLL; codec_fll_id and codec_pll_id are valid ++ * @dai_fmt: DAI format flags ++ * @playback_only: restrict card to playback direction ++ * @capture_only: restrict card to capture direction ++ * @dapm_routes: DAPM route table override ++ * @num_dapm_routes: number of entries in dapm_routes ++ * @exclude_format: PCM format bitmask excluded (for SAI + WM8960/WM8962) ++ * @codec_init: codec-specific init run after mclk_freq is populated ++ * @probe_init: optional DT-driven init run at end of probe() (e.g. SPDIF codec discovery) ++ */ ++struct fsl_asoc_card_pdata { ++ u32 sysclk_dir[2]; ++ u32 sysclk_ratio[2]; ++ u32 slot_width; ++ const char *codec_dai_name; ++ u32 codec_mclk_id; ++ int codec_fll_id; ++ int codec_pll_id; ++ int codec_pll_ratio_s24; ++ bool has_pll; ++ bool playback_only; ++ bool capture_only; ++ u32 dai_fmt; ++ const struct snd_soc_dapm_route *dapm_routes; ++ int num_dapm_routes; ++ u64 exclude_format; ++ int (*codec_init)(struct fsl_asoc_card_priv *priv); ++ int (*probe_init)(struct device_node *codec_np[], ++ struct device_node *cpu_np, ++ const char *codec_dai_name[], ++ struct fsl_asoc_card_priv *priv); ++}; ++ + /** + * struct fsl_asoc_card_priv - Freescale Generic ASOC card private data + * @dai_link: DAI link structure including normal one and DPCM link + * @hp_jack: Headphone Jack structure + * @mic_jack: Microphone Jack structure + * @pdev: platform device pointer ++ * @pdata: pointer to the per-compatible card platform data + * @codec_priv: CODEC private data + * @cpu_priv: CPU private data + * @card: ASoC card structure +@@ -135,6 +179,7 @@ struct fsl_asoc_card_priv { + struct simple_util_jack hp_jack; + struct simple_util_jack mic_jack; + struct platform_device *pdev; ++ const struct fsl_asoc_card_pdata *pdata; + struct codec_priv codec_priv[2]; + struct cpu_priv cpu_priv; + struct snd_soc_card card; +@@ -609,6 +654,192 @@ static int fsl_asoc_card_spdif_init(struct device_node *codec_np[], + return 0; + } + ++static int fsl_asoc_card_cs42888_codec_init(struct fsl_asoc_card_priv *priv) ++{ ++ unsigned long mclk_freq = priv->codec_priv[0].mclk_freq; ++ ++ /* ++ * Set CPU sysclk frequency from codec MCLK only if not already ++ * set by the CPU DAI init (e.g. ESAI extal clock takes precedence). ++ */ ++ if (!priv->cpu_priv.sysclk_freq[TX]) ++ priv->cpu_priv.sysclk_freq[TX] = mclk_freq; ++ if (!priv->cpu_priv.sysclk_freq[RX]) ++ priv->cpu_priv.sysclk_freq[RX] = mclk_freq; ++ ++ priv->constraint_channels = &cs42888_channel_constraints; ++ if (mclk_freq % 12288000 == 0) ++ priv->constraint_rates = &cs42888_rate_48k_constraints; ++ else if (mclk_freq % 11289600 == 0) ++ priv->constraint_rates = &cs42888_rate_44k_constraints; ++ else ++ dev_warn(&priv->pdev->dev, ++ "Unknown MCLK frequency %lu, no rate constraints\n", ++ mclk_freq); ++ ++ return 0; ++} ++ ++static int fsl_asoc_card_wm8958_codec_init(struct fsl_asoc_card_priv *priv) ++{ ++ priv->codec_priv[0].free_freq = priv->codec_priv[0].mclk_freq; ++ return 0; ++} ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_cs42888_pdata = { ++ .codec_dai_name = "cs42888", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ .sysclk_dir = { SND_SOC_CLOCK_OUT, SND_SOC_CLOCK_OUT }, ++ .slot_width = 32, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++ .codec_init = fsl_asoc_card_cs42888_codec_init, ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_cs427x_pdata = { ++ .codec_dai_name = "cs4271-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = CS427x_SYSCLK_MCLK, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_sgtl5000_pdata = { ++ .codec_dai_name = "sgtl5000", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = SGTL5000_SYSCLK, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_tlv320aic32x4_pdata = { ++ .codec_dai_name = "tlv320aic32x4-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_tlv320aic31xx_pdata = { ++ .codec_dai_name = "tlv320dac31xx-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ .sysclk_dir = { SND_SOC_CLOCK_OUT, SND_SOC_CLOCK_OUT }, ++ .playback_only = true, ++ .dapm_routes = audio_map_tx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_tx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8962_pdata = { ++ .codec_dai_name = "wm8962", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = WM8962_SYSCLK_MCLK, ++ .has_pll = true, ++ .codec_fll_id = WM8962_SYSCLK_FLL, ++ .codec_pll_id = WM8962_FLL, ++ /* ++ * WM8962 has same BCLK generation limitations as WM8960. ++ * See WM8960 section for detailed explanation. ++ */ ++ .exclude_format = SNDRV_PCM_FMTBIT_S20_3LE, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8960_pdata = { ++ .codec_dai_name = "wm8960-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .has_pll = true, ++ .codec_fll_id = WM8960_SYSCLK_AUTO, ++ .codec_pll_id = WM8960_SYSCLK_AUTO, ++ /* ++ * WM8960 in master mode cannot generate exact 1.92 MHz BCLK ++ * required for S20_3LE (48kHz x 2ch x 20bit). Closest available ++ * is 2.048 MHz (SYSCLK/6), which causes right channel corruption. ++ * ++ * In SAI master mode, SAI derive BCLK from MCLK using integer ++ * dividers only. S20_3LE requires non-integer divider ratios ++ * with standard MCLK frequencies. For example, 48kHz stereo ++ * needs 1.920 MHz BCLK, which requires a divider of 6.4 from ++ * 12.288 MHz MCLK (not an integer). ++ */ ++ .exclude_format = SNDRV_PCM_FMTBIT_S20_3LE, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_ac97_pdata = { ++ .codec_dai_name = "ac97-hifi", ++ .dai_fmt = SND_SOC_DAIFMT_AC97, ++ .dapm_routes = audio_map_ac97, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_ac97), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_mqs_pdata = { ++ .codec_dai_name = "fsl-mqs-dai", ++ .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBC_CFC | ++ SND_SOC_DAIFMT_NB_NF, ++ .playback_only = true, ++ .dapm_routes = audio_map_tx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_tx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8524_pdata = { ++ .codec_dai_name = "wm8524-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ /* RX=0, TX=1: set TX (index 1) to CLOCK_OUT, RX stays at default IN */ ++ .sysclk_dir = { 0, SND_SOC_CLOCK_OUT }, ++ .sysclk_ratio = { 0, 256 }, ++ .slot_width = 32, ++ .playback_only = true, ++ .dapm_routes = audio_map_tx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_tx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_si476x_pdata = { ++ .codec_dai_name = "si476x-codec", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ .dapm_routes = audio_map_rx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_rx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8958_pdata = { ++ .codec_dai_name = "wm8994-aif1", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = WM8994_FLL_SRC_MCLK1, ++ .has_pll = true, ++ .codec_fll_id = WM8994_SYSCLK_FLL1, ++ .codec_pll_id = WM8994_FLL1, ++ .codec_init = fsl_asoc_card_wm8958_codec_init, ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_nau8822_pdata = { ++ .codec_dai_name = "nau8822-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = NAU8822_CLK_MCLK, ++ .has_pll = true, ++ .codec_fll_id = NAU8822_CLK_PLL, ++ .codec_pll_id = NAU8822_CLK_PLL, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8904_pdata = { ++ .codec_dai_name = "wm8904-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = WM8904_FLL_MCLK, ++ .has_pll = true, ++ .codec_fll_id = WM8904_CLK_FLL, ++ .codec_pll_id = WM8904_FLL_MCLK, ++ .codec_pll_ratio_s24 = 192, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_spdif_pdata = { ++ .codec_dai_name = "spdif", ++ .dai_fmt = DAI_FMT_BASE, ++ .probe_init = fsl_asoc_card_spdif_init, ++}; ++ + static int hp_jack_event(struct notifier_block *nb, unsigned long event, + void *data) + { +@@ -643,17 +874,173 @@ static struct notifier_block mic_jack_nb = { + .notifier_call = mic_jack_event, + }; + +-static int fsl_asoc_card_late_probe(struct snd_soc_card *card) ++/* ++ * fsl_asoc_card_init_cpu - configure CPU DAI-specific settings. ++ * ++ * Called from late_probe() when the CPU DAI component is guaranteed bound. ++ */ ++static int fsl_asoc_card_init_cpu(struct snd_soc_card *card, ++ struct snd_soc_pcm_runtime *rtd) + { + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); +- struct snd_soc_pcm_runtime *rtd = list_first_entry( +- &card->rtd_list, struct snd_soc_pcm_runtime, list); ++ struct device_node *np = priv->pdev->dev.of_node; ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ const char *comp_drv_name = cpu_dai->component->driver->name; ++ struct device *dev = card->dev; ++ int ret; ++ ++ if (!strcmp(comp_drv_name, "fsl-ssi")) { ++ /* Only SSI needs to configure AUDMUX */ ++ ret = fsl_asoc_card_audmux_init(np, priv); ++ if (ret) { ++ dev_err(dev, "failed to init audmux\n"); ++ return ret; ++ } ++ } else if (!strcmp(comp_drv_name, "fsl-esai")) { ++ struct clk *esai_clk = clk_get(cpu_dai->dev, "extal"); ++ ++ if (!IS_ERR(esai_clk)) { ++ priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(esai_clk); ++ priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(esai_clk); ++ clk_put(esai_clk); ++ } else { ++ dev_warn(dev, "failed to get ESAI extal clock: %ld\n", PTR_ERR(esai_clk)); ++ } ++ ++ priv->cpu_priv.sysclk_id[TX] = ESAI_HCKT_EXTAL; ++ priv->cpu_priv.sysclk_id[RX] = ESAI_HCKR_EXTAL; ++ } else if (!strcmp(comp_drv_name, "fsl-sai")) { ++ priv->cpu_priv.sysclk_id[TX] = FSL_SAI_CLK_MAST1; ++ priv->cpu_priv.sysclk_id[RX] = FSL_SAI_CLK_MAST1; ++ ++ if (priv->pdata->exclude_format) ++ priv->exclude_format = priv->pdata->exclude_format; ++ } ++ ++ return 0; ++} ++ ++/* ++ * fsl_asoc_card_init_codecs - read codec MCLK rates and set codec sysclk. ++ * ++ * Called from late_probe() after all components are bound. ++ */ ++static int fsl_asoc_card_init_codecs(struct snd_soc_card *card, ++ struct snd_soc_pcm_runtime *rtd) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ const struct fsl_asoc_card_pdata *pdata = priv->pdata; + struct snd_soc_dai *codec_dai; + struct codec_priv *codec_priv; + struct device *dev = card->dev; + int codec_idx; + int ret; + ++ /* Read MCLK rate from each bound codec component */ ++ for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { ++ struct clk *codec_clk = clk_get(codec_dai->component->dev, NULL); ++ ++ codec_priv = &priv->codec_priv[codec_idx]; ++ if (!IS_ERR(codec_clk)) { ++ codec_priv->mclk_freq = clk_get_rate(codec_clk); ++ clk_put(codec_clk); ++ } ++ } ++ ++ if (pdata->codec_init) { ++ ret = pdata->codec_init(priv); ++ if (ret) ++ return ret; ++ } ++ ++ for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { ++ codec_priv = &priv->codec_priv[codec_idx]; ++ ++ ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, ++ codec_priv->mclk_freq, SND_SOC_CLOCK_IN); ++ if (ret && ret != -ENOTSUPP) { ++ dev_err(dev, "failed to set sysclk in %s\n", __func__); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++ ++static void fsl_asoc_card_free_jack(struct snd_soc_card *card) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ ++ if (priv->hp_jack.gpio.desc) { ++ snd_soc_jack_notifier_unregister(&priv->hp_jack.jack, &hp_jack_nb); ++ snd_soc_jack_free_gpios(&priv->hp_jack.jack, 1, &priv->hp_jack.gpio); ++ priv->hp_jack.gpio.desc = NULL; ++ } ++ ++ if (priv->mic_jack.gpio.desc) { ++ snd_soc_jack_notifier_unregister(&priv->mic_jack.jack, &mic_jack_nb); ++ snd_soc_jack_free_gpios(&priv->mic_jack.jack, 1, &priv->mic_jack.gpio); ++ priv->mic_jack.gpio.desc = NULL; ++ } ++} ++ ++/* ++ * fsl_asoc_card_init_jack - register optional headphone and mic jacks. ++ * ++ * Called from late_probe() once per card bind cycle. ++ */ ++static int fsl_asoc_card_init_jack(struct snd_soc_card *card) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ struct device_node *np = priv->pdev->dev.of_node; ++ int ret; ++ ++ /* ++ * Properties "hp-det-gpios" and "mic-det-gpios" are optional. ++ * simple_util_init_jack() checks for the GPIO property and ++ * does nothing if it is absent. ++ */ ++ if (of_property_present(np, "hp-det-gpios") || ++ of_property_present(np, "hp-det-gpio") /* deprecated */) { ++ ret = simple_util_init_jack(card, &priv->hp_jack, ++ 1, NULL, "Headphone Jack"); ++ if (ret) ++ return ret; ++ ++ snd_soc_jack_notifier_register(&priv->hp_jack.jack, &hp_jack_nb); ++ } ++ ++ if (of_property_present(np, "mic-det-gpios") || ++ of_property_present(np, "mic-det-gpio") /* deprecated */) { ++ ret = simple_util_init_jack(card, &priv->mic_jack, ++ 0, NULL, "Mic Jack"); ++ if (ret) ++ return ret; ++ ++ snd_soc_jack_notifier_register(&priv->mic_jack.jack, &mic_jack_nb); ++ } ++ ++ return 0; ++} ++ ++static int fsl_asoc_card_late_probe(struct snd_soc_card *card) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ struct snd_soc_pcm_runtime *rtd; ++ int ret; ++ ++ /* Use the first rtd which carries the CPU+codec DAIs */ ++ rtd = list_first_entry(&card->rtd_list, ++ struct snd_soc_pcm_runtime, list); ++ ++ ret = fsl_asoc_card_init_jack(card); ++ if (ret) ++ goto jack_fail; ++ ++ ret = fsl_asoc_card_init_cpu(card, rtd); ++ if (ret) ++ goto jack_fail; ++ + if (fsl_asoc_card_is_ac97(priv)) { + #if IS_ENABLED(CONFIG_SND_AC97_CODEC) + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; +@@ -671,19 +1058,20 @@ static int fsl_asoc_card_late_probe(struct snd_soc_card *card) + return 0; + } + +- for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { +- codec_priv = &priv->codec_priv[codec_idx]; ++ ret = fsl_asoc_card_init_codecs(card, rtd); ++ if (ret) ++ goto jack_fail; + +- ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, +- codec_priv->mclk_freq, SND_SOC_CLOCK_IN); +- if (ret && ret != -ENOTSUPP) { +- dev_err(dev, "failed to set sysclk in %s\n", __func__); +- return ret; +- } ++ return 0; + +- if (!IS_ERR_OR_NULL(codec_priv->mclk)) +- clk_prepare_enable(codec_priv->mclk); +- } ++jack_fail: ++ fsl_asoc_card_free_jack(card); ++ return ret; ++} ++ ++static int fsl_asoc_card_card_remove(struct snd_soc_card *card) ++{ ++ fsl_asoc_card_free_jack(card); + + return 0; + } +@@ -697,12 +1085,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + struct platform_device *asrc_pdev = NULL; + struct device_node *bitclkprovider = NULL; + struct device_node *frameprovider = NULL; +- struct platform_device *cpu_pdev; + struct fsl_asoc_card_priv *priv; +- struct device *codec_dev[2] = { NULL, NULL }; ++ const struct fsl_asoc_card_pdata *pdata; + struct snd_soc_dai_link_component *dlc; +- const char *codec_dai_name[2]; +- const char *codec_dev_name[2]; ++ const char *codec_dai_name[2] = { NULL, NULL }; + u32 asrc_fmt = 0; + int codec_idx; + u32 width; +@@ -714,6 +1100,13 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + + priv->pdev = pdev; + ++ pdata = of_device_get_match_data(&pdev->dev); ++ if (!pdata) { ++ dev_err(&pdev->dev, "unknown Device Tree compatible\n"); ++ return -EINVAL; ++ } ++ priv->pdata = pdata; ++ + cpu_np = of_parse_phandle(np, "audio-cpu", 0); + /* Give a chance to old DT bindings */ + if (!cpu_np) +@@ -726,52 +1119,13 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + goto fail; + } + +- cpu_pdev = of_find_device_by_node(cpu_np); +- if (!cpu_pdev) { +- dev_err(&pdev->dev, "failed to find CPU DAI device\n"); +- ret = -EINVAL; +- goto fail; +- } +- + codec_np[0] = of_parse_phandle(np, "audio-codec", 0); + codec_np[1] = of_parse_phandle(np, "audio-codec", 1); + +- for (codec_idx = 0; codec_idx < 2; codec_idx++) { +- if (codec_np[codec_idx]) { +- struct platform_device *codec_pdev; +- struct i2c_client *codec_i2c; +- +- codec_i2c = of_find_i2c_device_by_node(codec_np[codec_idx]); +- if (codec_i2c) { +- codec_dev[codec_idx] = &codec_i2c->dev; +- codec_dev_name[codec_idx] = codec_i2c->name; +- } +- if (!codec_dev[codec_idx]) { +- codec_pdev = of_find_device_by_node(codec_np[codec_idx]); +- if (codec_pdev) { +- codec_dev[codec_idx] = &codec_pdev->dev; +- codec_dev_name[codec_idx] = codec_pdev->name; +- } +- } +- } +- } +- + asrc_np = of_parse_phandle(np, "audio-asrc", 0); + if (asrc_np) + asrc_pdev = of_find_device_by_node(asrc_np); + +- /* Get the MCLK rate only, and leave it controlled by CODEC drivers */ +- for (codec_idx = 0; codec_idx < 2; codec_idx++) { +- if (codec_dev[codec_idx]) { +- struct clk *codec_clk = clk_get(codec_dev[codec_idx], NULL); +- +- if (!IS_ERR(codec_clk)) { +- priv->codec_priv[codec_idx].mclk_freq = clk_get_rate(codec_clk); +- clk_put(codec_clk); +- } +- } +- } +- + /* Default sample rate and format, will be updated in hw_params() */ + priv->sample_rate = 44100; + priv->sample_format = SNDRV_PCM_FORMAT_S16_LE; +@@ -823,133 +1177,40 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + } + + /* Diversify the card configurations */ +- if (of_device_is_compatible(np, "fsl,imx-audio-cs42888")) { +- codec_dai_name[0] = "cs42888"; +- priv->cpu_priv.sysclk_freq[TX] = priv->codec_priv[0].mclk_freq; +- priv->cpu_priv.sysclk_freq[RX] = priv->codec_priv[0].mclk_freq; +- priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.slot_width = 32; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; +- priv->constraint_channels = &cs42888_channel_constraints; +- if (priv->codec_priv[0].mclk_freq % 12288000 == 0) +- priv->constraint_rates = &cs42888_rate_48k_constraints; +- else if (priv->codec_priv[0].mclk_freq % 11289600 == 0) +- priv->constraint_rates = &cs42888_rate_44k_constraints; +- else +- dev_warn(&pdev->dev, "Unknown MCLK frequency %lu, no rate constraints\n", +- priv->codec_priv[0].mclk_freq); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-cs427x")) { +- codec_dai_name[0] = "cs4271-hifi"; +- priv->codec_priv[0].mclk_id = CS427x_SYSCLK_MCLK; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-sgtl5000")) { +- codec_dai_name[0] = "sgtl5000"; +- priv->codec_priv[0].mclk_id = SGTL5000_SYSCLK; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-tlv320aic32x4")) { +- codec_dai_name[0] = "tlv320aic32x4-hifi"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-tlv320aic31xx")) { +- codec_dai_name[0] = "tlv320dac31xx-hifi"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; ++ priv->cpu_priv.sysclk_dir[TX] = pdata->sysclk_dir[TX]; ++ priv->cpu_priv.sysclk_dir[RX] = pdata->sysclk_dir[RX]; ++ priv->cpu_priv.sysclk_ratio[TX] = pdata->sysclk_ratio[TX]; ++ priv->cpu_priv.sysclk_ratio[RX] = pdata->sysclk_ratio[RX]; ++ priv->cpu_priv.slot_width = pdata->slot_width; ++ ++ codec_dai_name[0] = pdata->codec_dai_name; ++ priv->codec_priv[0].mclk_id = pdata->codec_mclk_id; ++ if (pdata->has_pll) { ++ priv->codec_priv[0].fll_id = pdata->codec_fll_id; ++ priv->codec_priv[0].pll_id = pdata->codec_pll_id; ++ } ++ if (pdata->codec_pll_ratio_s24) ++ priv->codec_priv[0].pll_ratio_s24 = pdata->codec_pll_ratio_s24; ++ ++ if (pdata->playback_only) { + priv->dai_link[1].playback_only = 1; + priv->dai_link[2].playback_only = 1; +- priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; +- priv->card.dapm_routes = audio_map_tx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8962")) { +- codec_dai_name[0] = "wm8962"; +- priv->codec_priv[0].mclk_id = WM8962_SYSCLK_MCLK; +- priv->codec_priv[0].fll_id = WM8962_SYSCLK_FLL; +- priv->codec_priv[0].pll_id = WM8962_FLL; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- /* +- * WM8962 has same BCLK generation limitations as WM8960. +- * See WM8960 section for detailed explanation. +- */ +- if (of_node_name_eq(cpu_np, "sai")) +- priv->exclude_format = SNDRV_PCM_FMTBIT_S20_3LE; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8960")) { +- codec_dai_name[0] = "wm8960-hifi"; +- priv->codec_priv[0].fll_id = WM8960_SYSCLK_AUTO; +- priv->codec_priv[0].pll_id = WM8960_SYSCLK_AUTO; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- /* +- * WM8960 in master mode cannot generate exact 1.92 MHz BCLK +- * required for S20_3LE (48kHz × 2ch × 20bit). Closest available +- * is 2.048 MHz (SYSCLK/6), which causes right channel corruption. +- * +- * In SAI master mode, SAI derive BCLK from MCLK using integer +- * dividers only. S20_3LE requires non-integer divider ratios +- * with standard MCLK frequencies. For example, 48kHz stereo +- * needs 1.920 MHz BCLK, which requires a divider of 6.4 from +- * 12.288 MHz MCLK (not an integer). +- */ +- if (of_node_name_eq(cpu_np, "sai")) +- priv->exclude_format = SNDRV_PCM_FMTBIT_S20_3LE; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-ac97")) { +- codec_dai_name[0] = "ac97-hifi"; +- priv->dai_fmt = SND_SOC_DAIFMT_AC97; +- priv->card.dapm_routes = audio_map_ac97; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_ac97); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-mqs")) { +- codec_dai_name[0] = "fsl-mqs-dai"; +- priv->dai_fmt = SND_SOC_DAIFMT_LEFT_J | +- SND_SOC_DAIFMT_CBC_CFC | +- SND_SOC_DAIFMT_NB_NF; +- priv->dai_link[1].playback_only = 1; +- priv->dai_link[2].playback_only = 1; +- priv->card.dapm_routes = audio_map_tx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8524")) { +- codec_dai_name[0] = "wm8524-hifi"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; +- priv->dai_link[1].playback_only = 1; +- priv->dai_link[2].playback_only = 1; +- priv->cpu_priv.slot_width = 32; +- priv->card.dapm_routes = audio_map_tx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); +- priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.sysclk_ratio[TX] = 256; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-si476x")) { +- codec_dai_name[0] = "si476x-codec"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; +- priv->card.dapm_routes = audio_map_rx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_rx); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8958")) { +- codec_dai_name[0] = "wm8994-aif1"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- priv->codec_priv[0].mclk_id = WM8994_FLL_SRC_MCLK1; +- priv->codec_priv[0].fll_id = WM8994_SYSCLK_FLL1; +- priv->codec_priv[0].pll_id = WM8994_FLL1; +- priv->codec_priv[0].free_freq = priv->codec_priv[0].mclk_freq; +- priv->card.dapm_routes = NULL; +- priv->card.num_dapm_routes = 0; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-nau8822")) { +- codec_dai_name[0] = "nau8822-hifi"; +- priv->codec_priv[0].mclk_id = NAU8822_CLK_MCLK; +- priv->codec_priv[0].fll_id = NAU8822_CLK_PLL; +- priv->codec_priv[0].pll_id = NAU8822_CLK_PLL; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- if (codec_dev[0]) +- priv->codec_priv[0].mclk = devm_clk_get(codec_dev[0], NULL); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8904")) { +- codec_dai_name[0] = "wm8904-hifi"; +- priv->codec_priv[0].mclk_id = WM8904_FLL_MCLK; +- priv->codec_priv[0].fll_id = WM8904_CLK_FLL; +- priv->codec_priv[0].pll_id = WM8904_FLL_MCLK; +- priv->codec_priv[0].pll_ratio_s24 = 192; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-spdif")) { +- ret = fsl_asoc_card_spdif_init(codec_np, cpu_np, codec_dai_name, priv); ++ } ++ if (pdata->capture_only) { ++ priv->dai_link[1].capture_only = 1; ++ priv->dai_link[2].capture_only = 1; ++ } ++ ++ priv->dai_fmt = pdata->dai_fmt; ++ ++ priv->card.dapm_routes = pdata->dapm_routes; ++ priv->card.num_dapm_routes = pdata->num_dapm_routes; ++ ++ if (pdata->probe_init) { ++ ret = pdata->probe_init(codec_np, cpu_np, ++ codec_dai_name, priv); + if (ret) + goto asrc_fail; +- } else { +- dev_err(&pdev->dev, "unknown Device Tree compatible\n"); +- ret = -EINVAL; +- goto asrc_fail; + } + + /* +@@ -995,51 +1256,21 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + of_node_put(bitclkprovider); + of_node_put(frameprovider); + +- if (!fsl_asoc_card_is_ac97(priv) && !codec_dev[0] +- && codec_dai_name[0] != snd_soc_dummy_dlc.dai_name) { +- dev_dbg(&pdev->dev, "failed to find codec device\n"); +- ret = -EPROBE_DEFER; +- goto asrc_fail; +- } +- +- /* Common settings for corresponding Freescale CPU DAI driver */ +- if (of_node_name_eq(cpu_np, "ssi")) { +- /* Only SSI needs to configure AUDMUX */ +- ret = fsl_asoc_card_audmux_init(np, priv); +- if (ret) { +- dev_err(&pdev->dev, "failed to init audmux\n"); +- goto asrc_fail; +- } +- } else if (of_node_name_eq(cpu_np, "esai")) { +- struct clk *esai_clk = clk_get(&cpu_pdev->dev, "extal"); +- +- if (!IS_ERR(esai_clk)) { +- priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(esai_clk); +- priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(esai_clk); +- clk_put(esai_clk); +- } else if (PTR_ERR(esai_clk) == -EPROBE_DEFER) { +- ret = -EPROBE_DEFER; +- goto asrc_fail; +- } +- +- priv->cpu_priv.sysclk_id[1] = ESAI_HCKT_EXTAL; +- priv->cpu_priv.sysclk_id[0] = ESAI_HCKR_EXTAL; +- } else if (of_node_name_eq(cpu_np, "sai")) { +- priv->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1; +- priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1; +- } +- + /* Initialize sound card */ + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) { +- snprintf(priv->name, sizeof(priv->name), "%s-audio", +- fsl_asoc_card_is_ac97(priv) ? "ac97" : codec_dev_name[0]); +- priv->card.name = priv->name; ++ /* ++ * "model" is required by the DT binding. Enforce it here so ++ * the driver fails with a clear message. ++ */ ++ dev_err(&pdev->dev, "Error parsing card name: %d\n", ret); ++ goto asrc_fail; + } + priv->card.dai_link = priv->dai_link; + priv->card.late_probe = fsl_asoc_card_late_probe; ++ priv->card.remove = fsl_asoc_card_card_remove; + priv->card.dapm_widgets = fsl_asoc_card_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(fsl_asoc_card_dapm_widgets); + +@@ -1118,7 +1349,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-format", &asrc_fmt); +- priv->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ priv->asrc_format = asrc_fmt; + if (ret) { + /* Fallback to old binding; translate to asrc_format */ + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", +@@ -1146,39 +1377,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + goto asrc_fail; + } + +- /* +- * Properties "hp-det-gpios" and "mic-det-gpios" are optional, and +- * simple_util_init_jack() uses these properties for creating +- * Headphone Jack and Microphone Jack. +- * +- * The notifier is initialized in snd_soc_card_jack_new(), then +- * snd_soc_jack_notifier_register can be called. +- */ +- if (of_property_present(np, "hp-det-gpios") || +- of_property_present(np, "hp-det-gpio") /* deprecated */) { +- ret = simple_util_init_jack(&priv->card, &priv->hp_jack, +- 1, NULL, "Headphone Jack"); +- if (ret) +- goto asrc_fail; +- +- snd_soc_jack_notifier_register(&priv->hp_jack.jack, &hp_jack_nb); +- } +- +- if (of_property_present(np, "mic-det-gpios") || +- of_property_present(np, "mic-det-gpio") /* deprecated */) { +- ret = simple_util_init_jack(&priv->card, &priv->mic_jack, +- 0, NULL, "Mic Jack"); +- if (ret) +- goto asrc_fail; +- +- snd_soc_jack_notifier_register(&priv->mic_jack.jack, &mic_jack_nb); +- } +- + asrc_fail: + of_node_put(asrc_np); + of_node_put(codec_np[0]); + of_node_put(codec_np[1]); +- put_device(&cpu_pdev->dev); + fail: + of_node_put(cpu_np); + +@@ -1186,21 +1388,21 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + } + + static const struct of_device_id fsl_asoc_card_dt_ids[] = { +- { .compatible = "fsl,imx-audio-ac97", }, +- { .compatible = "fsl,imx-audio-cs42888", }, +- { .compatible = "fsl,imx-audio-cs427x", }, +- { .compatible = "fsl,imx-audio-tlv320aic32x4", }, +- { .compatible = "fsl,imx-audio-tlv320aic31xx", }, +- { .compatible = "fsl,imx-audio-sgtl5000", }, +- { .compatible = "fsl,imx-audio-wm8962", }, +- { .compatible = "fsl,imx-audio-wm8960", }, +- { .compatible = "fsl,imx-audio-mqs", }, +- { .compatible = "fsl,imx-audio-wm8524", }, +- { .compatible = "fsl,imx-audio-si476x", }, +- { .compatible = "fsl,imx-audio-wm8958", }, +- { .compatible = "fsl,imx-audio-nau8822", }, +- { .compatible = "fsl,imx-audio-wm8904", }, +- { .compatible = "fsl,imx-audio-spdif", }, ++ { .compatible = "fsl,imx-audio-ac97", .data = &fsl_asoc_ac97_pdata }, ++ { .compatible = "fsl,imx-audio-cs42888", .data = &fsl_asoc_cs42888_pdata }, ++ { .compatible = "fsl,imx-audio-cs427x", .data = &fsl_asoc_cs427x_pdata }, ++ { .compatible = "fsl,imx-audio-tlv320aic32x4", .data = &fsl_asoc_tlv320aic32x4_pdata }, ++ { .compatible = "fsl,imx-audio-tlv320aic31xx", .data = &fsl_asoc_tlv320aic31xx_pdata }, ++ { .compatible = "fsl,imx-audio-sgtl5000", .data = &fsl_asoc_sgtl5000_pdata }, ++ { .compatible = "fsl,imx-audio-wm8962", .data = &fsl_asoc_wm8962_pdata }, ++ { .compatible = "fsl,imx-audio-wm8960", .data = &fsl_asoc_wm8960_pdata }, ++ { .compatible = "fsl,imx-audio-mqs", .data = &fsl_asoc_mqs_pdata }, ++ { .compatible = "fsl,imx-audio-wm8524", .data = &fsl_asoc_wm8524_pdata }, ++ { .compatible = "fsl,imx-audio-si476x", .data = &fsl_asoc_si476x_pdata }, ++ { .compatible = "fsl,imx-audio-wm8958", .data = &fsl_asoc_wm8958_pdata }, ++ { .compatible = "fsl,imx-audio-nau8822", .data = &fsl_asoc_nau8822_pdata }, ++ { .compatible = "fsl,imx-audio-wm8904", .data = &fsl_asoc_wm8904_pdata }, ++ { .compatible = "fsl,imx-audio-spdif", .data = &fsl_asoc_spdif_pdata }, + {} + }; + MODULE_DEVICE_TABLE(of, fsl_asoc_card_dt_ids); +diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c +--- a/sound/soc/fsl/fsl_asrc.c ++++ b/sound/soc/fsl/fsl_asrc.c +@@ -222,10 +222,9 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) + enum asrc_pair_index index = ASRC_INVALID_PAIR; + struct fsl_asrc *asrc = pair->asrc; + struct device *dev = &asrc->pdev->dev; +- unsigned long lock_flags; + int i, ret = 0; + +- spin_lock_irqsave(&asrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&asrc->lock); + + for (i = ASRC_PAIR_A; i < ASRC_PAIR_MAX_NUM; i++) { + if (asrc->pair[i] != NULL) +@@ -250,8 +249,6 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) + pair->index = index; + } + +- spin_unlock_irqrestore(&asrc->lock, lock_flags); +- + return ret; + } + +@@ -265,19 +262,16 @@ static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair) + { + struct fsl_asrc *asrc = pair->asrc; + enum asrc_pair_index index = pair->index; +- unsigned long lock_flags; + + /* Make sure the pair is disabled */ + regmap_update_bits(asrc->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_MASK(index), 0); + +- spin_lock_irqsave(&asrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&asrc->lock); + + asrc->channel_avail += pair->channels; + asrc->pair[index] = NULL; + pair->error = 0; +- +- spin_unlock_irqrestore(&asrc->lock, lock_flags); + } + + /** +@@ -1358,7 +1352,7 @@ static int fsl_asrc_probe(struct platform_device *pdev) + } + + ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt); +- asrc->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ asrc->asrc_format = asrc_fmt; + if (ret) { + ret = of_property_read_u32(np, "fsl,asrc-width", &width); + if (ret) { +diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h +--- a/sound/soc/fsl/fsl_asrc.h ++++ b/sound/soc/fsl/fsl_asrc.h +@@ -444,7 +444,7 @@ struct dma_block { + }; + + /** +- * fsl_asrc_soc_data: soc specific data ++ * struct fsl_asrc_soc_data - soc specific data + * + * @use_edma: using edma as dma device or not + * @channel_bits: width of ASRCNCR register for each pair +@@ -457,7 +457,7 @@ struct fsl_asrc_soc_data { + }; + + /** +- * fsl_asrc_pair_priv: ASRC Pair private data ++ * struct fsl_asrc_pair_priv - ASRC Pair private data + * + * @config: configuration profile + */ +@@ -466,7 +466,7 @@ struct fsl_asrc_pair_priv { + }; + + /** +- * fsl_asrc_priv: ASRC private data ++ * struct fsl_asrc_priv - ASRC private data + * + * @asrck_clk: clock sources to driver ASRC internal logic + * @soc: soc specific data +diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c +--- a/sound/soc/fsl/fsl_asrc_dma.c ++++ b/sound/soc/fsl/fsl_asrc_dma.c +@@ -219,7 +219,9 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, + */ + component_be = snd_soc_lookup_component_nolocked(dev_be, SND_DMAENGINE_PCM_DRV_NAME); + if (component_be) { +- be_chan = soc_component_to_pcm(component_be)->chan[substream->stream]; ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component_be); ++ ++ be_chan = pcm->chan[substream->stream]; + tmp_chan = be_chan; + } + if (!tmp_chan) { +diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c +--- a/sound/soc/fsl/fsl_asrc_m2m.c ++++ b/sound/soc/fsl/fsl_asrc_m2m.c +@@ -367,13 +367,13 @@ static int fsl_asrc_m2m_comp_set_params(struct snd_compr_stream *stream, + if (ret) + return -EINVAL; + +- if (pcm_format_to_bits((__force snd_pcm_format_t)params->codec.format) & cap.fmt_in) +- pair->sample_format[IN] = (__force snd_pcm_format_t)params->codec.format; ++ if (pcm_format_to_bits(params->codec.format) & cap.fmt_in) ++ pair->sample_format[IN] = params->codec.format; + else + return -EINVAL; + +- if (pcm_format_to_bits((__force snd_pcm_format_t)params->codec.pcm_format) & cap.fmt_out) +- pair->sample_format[OUT] = (__force snd_pcm_format_t)params->codec.pcm_format; ++ if (pcm_format_to_bits(params->codec.pcm_format) & cap.fmt_out) ++ pair->sample_format[OUT] = params->codec.pcm_format; + else + return -EINVAL; + +@@ -600,7 +600,7 @@ static int fsl_asrc_m2m_fill_codec_caps(struct fsl_asrc *asrc, + cap.rate_in, + cap.rate_in_count * sizeof(__u32)); + codec->descriptor[j].num_sample_rates = cap.rate_in_count; +- codec->descriptor[j].formats = (__force __u32)k; ++ codec->descriptor[j].formats = k; + codec->descriptor[j].pcm_formats = cap.fmt_out; + codec->descriptor[j].src.out_sample_rate_min = cap.rate_out[0]; + codec->descriptor[j].src.out_sample_rate_max = +diff --git a/sound/soc/fsl/fsl_audmix.c b/sound/soc/fsl/fsl_audmix.c +--- a/sound/soc/fsl/fsl_audmix.c ++++ b/sound/soc/fsl/fsl_audmix.c +@@ -286,7 +286,6 @@ static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) + { + struct fsl_audmix *priv = snd_soc_dai_get_drvdata(dai); +- unsigned long lock_flags; + + /* Capture stream shall not be handled */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) +@@ -296,16 +295,14 @@ static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int cmd, + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +- spin_lock_irqsave(&priv->lock, lock_flags); +- priv->tdms |= BIT(dai->driver->id); +- spin_unlock_irqrestore(&priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &priv->lock) ++ priv->tdms |= BIT(dai->driver->id); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +- spin_lock_irqsave(&priv->lock, lock_flags); +- priv->tdms &= ~BIT(dai->driver->id); +- spin_unlock_irqrestore(&priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &priv->lock) ++ priv->tdms &= ~BIT(dai->driver->id); + break; + default: + return -EINVAL; +@@ -457,6 +454,9 @@ static const struct of_device_id fsl_audmix_ids[] = { + }; + MODULE_DEVICE_TABLE(of, fsl_audmix_ids); + ++static int fsl_audmix_runtime_resume(struct device *dev); ++static int fsl_audmix_runtime_suspend(struct device *dev); ++ + static int fsl_audmix_probe(struct platform_device *pdev) + { + struct device *dev = &pdev->dev; +@@ -488,13 +488,25 @@ static int fsl_audmix_probe(struct platform_device *pdev) + spin_lock_init(&priv->lock); + platform_set_drvdata(pdev, priv); + pm_runtime_enable(dev); ++ if (!pm_runtime_enabled(dev)) { ++ ret = fsl_audmix_runtime_resume(dev); ++ if (ret) ++ goto err_disable_pm; ++ } ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) ++ goto err_pm_get_sync; ++ ++ /* To enable regmap cache only when runtime PM enabled */ ++ pm_runtime_put(dev); + + ret = devm_snd_soc_register_component(dev, &fsl_audmix_component, + fsl_audmix_dai, + ARRAY_SIZE(fsl_audmix_dai)); + if (ret) { + dev_err(dev, "failed to register ASoC DAI\n"); +- goto err_disable_pm; ++ goto err_pm_get_sync; + } + + /* +@@ -506,12 +518,15 @@ static int fsl_audmix_probe(struct platform_device *pdev) + if (IS_ERR(priv->pdev)) { + ret = PTR_ERR(priv->pdev); + dev_err(dev, "failed to register platform: %d\n", ret); +- goto err_disable_pm; ++ goto err_pm_get_sync; + } + } + + return 0; + ++err_pm_get_sync: ++ if (!pm_runtime_status_suspended(dev)) ++ fsl_audmix_runtime_suspend(dev); + err_disable_pm: + pm_runtime_disable(dev); + return ret; +@@ -522,6 +537,8 @@ static void fsl_audmix_remove(struct platform_device *pdev) + struct fsl_audmix *priv = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); ++ if (!pm_runtime_status_suspended(&pdev->dev)) ++ fsl_audmix_runtime_suspend(&pdev->dev); + + if (priv->pdev) + platform_device_unregister(priv->pdev); +diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c +--- a/sound/soc/fsl/fsl_dma.c ++++ b/sound/soc/fsl/fsl_dma.c +@@ -18,8 +18,6 @@ + #include + #include + #include +-#include +-#include + #include + #include + +@@ -824,9 +822,34 @@ static int fsl_soc_dma_probe(struct platform_device *pdev) + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np; + struct resource res; ++ void __iomem *channel; + const uint32_t *iprop; ++ int irq; + int ret; + ++ channel = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(channel)) ++ return PTR_ERR(channel); ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return irq; ++ ++ dma = devm_kzalloc(&pdev->dev, sizeof(*dma), GFP_KERNEL); ++ if (!dma) ++ return -ENOMEM; ++ ++ dma->dai.name = DRV_NAME; ++ dma->dai.open = fsl_dma_open; ++ dma->dai.close = fsl_dma_close; ++ dma->dai.hw_params = fsl_dma_hw_params; ++ dma->dai.hw_free = fsl_dma_hw_free; ++ dma->dai.pointer = fsl_dma_pointer; ++ dma->dai.pcm_new = fsl_dma_new; ++ ++ dma->channel = channel; ++ dma->irq = irq; ++ + /* Find the SSI node that points to us. */ + ssi_np = find_ssi_node(np); + if (!ssi_np) { +@@ -842,55 +865,19 @@ static int fsl_soc_dma_probe(struct platform_device *pdev) + return ret; + } + +- dma = kzalloc_obj(*dma); +- if (!dma) { +- of_node_put(ssi_np); +- return -ENOMEM; +- } +- +- dma->dai.name = DRV_NAME; +- dma->dai.open = fsl_dma_open; +- dma->dai.close = fsl_dma_close; +- dma->dai.hw_params = fsl_dma_hw_params; +- dma->dai.hw_free = fsl_dma_hw_free; +- dma->dai.pointer = fsl_dma_pointer; +- dma->dai.pcm_new = fsl_dma_new; +- + /* Store the SSI-specific information that we need */ + dma->ssi_stx_phys = res.start + REG_SSI_STX0; + dma->ssi_srx_phys = res.start + REG_SSI_SRX0; + + iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL); ++ of_node_put(ssi_np); + if (iprop) + dma->ssi_fifo_depth = be32_to_cpup(iprop); + else + /* Older 8610 DTs didn't have the fifo-depth property */ + dma->ssi_fifo_depth = 8; + +- of_node_put(ssi_np); +- +- ret = devm_snd_soc_register_component(&pdev->dev, &dma->dai, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "could not register platform\n"); +- kfree(dma); +- return ret; +- } +- +- dma->channel = of_iomap(np, 0); +- dma->irq = irq_of_parse_and_map(np, 0); +- +- dev_set_drvdata(&pdev->dev, dma); +- +- return 0; +-} +- +-static void fsl_soc_dma_remove(struct platform_device *pdev) +-{ +- struct dma_object *dma = dev_get_drvdata(&pdev->dev); +- +- iounmap(dma->channel); +- irq_dispose_mapping(dma->irq); +- kfree(dma); ++ return devm_snd_soc_register_component(&pdev->dev, &dma->dai, NULL, 0); + } + + static const struct of_device_id fsl_soc_dma_ids[] = { +@@ -905,7 +892,6 @@ static struct platform_driver fsl_soc_dma_driver = { + .of_match_table = fsl_soc_dma_ids, + }, + .probe = fsl_soc_dma_probe, +- .remove = fsl_soc_dma_remove, + }; + + module_platform_driver(fsl_soc_dma_driver); +diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h +--- a/sound/soc/fsl/fsl_dma.h ++++ b/sound/soc/fsl/fsl_dma.h +@@ -92,7 +92,7 @@ static inline u32 CCSR_DMA_ECLNDAR_ADDR(u64 x) + #define CCSR_DMA_ATR_SNOOP 0x00050000 + #define CCSR_DMA_ATR_ESAD_MASK 0x0000000F + +-/** ++/* + * List Descriptor for extended chaining mode DMA operations. + * + * The CLSDAR register points to the first (in a linked-list) List +@@ -107,7 +107,7 @@ struct fsl_dma_list_descriptor { + u8 res[8]; /* Reserved */ + } __attribute__ ((aligned(32), packed)); + +-/** ++/* + * Link Descriptor for basic and extended chaining mode DMA operations. + * + * A Link Descriptor points to a single DMA buffer. Each link descriptor +diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c +--- a/sound/soc/fsl/fsl_easrc.c ++++ b/sound/soc/fsl/fsl_easrc.c +@@ -1025,7 +1025,6 @@ static int fsl_easrc_config_context(struct fsl_asrc *easrc, unsigned int ctx_id) + struct fsl_easrc_ctx_priv *ctx_priv; + struct fsl_asrc_pair *ctx; + struct device *dev; +- unsigned long lock_flags; + int ret; + + if (!easrc) +@@ -1053,9 +1052,8 @@ static int fsl_easrc_config_context(struct fsl_asrc *easrc, unsigned int ctx_id) + if (ret) + return ret; + +- spin_lock_irqsave(&easrc->lock, lock_flags); +- ret = fsl_easrc_config_slot(easrc, ctx->index); +- spin_unlock_irqrestore(&easrc->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &easrc->lock) ++ ret = fsl_easrc_config_slot(easrc, ctx->index); + if (ret) + return ret; + +@@ -1301,13 +1299,12 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) + enum asrc_pair_index index = ASRC_INVALID_PAIR; + struct fsl_asrc *easrc = ctx->asrc; + struct device *dev; +- unsigned long lock_flags; + int ret = 0; + int i; + + dev = &easrc->pdev->dev; + +- spin_lock_irqsave(&easrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&easrc->lock); + + for (i = ASRC_PAIR_A; i < EASRC_CTX_MAX_NUM; i++) { + if (easrc->pair[i]) +@@ -1331,8 +1328,6 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) + easrc->channel_avail -= channels; + } + +- spin_unlock_irqrestore(&easrc->lock, lock_flags); +- + return ret; + } + +@@ -1343,7 +1338,6 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) + */ + static void fsl_easrc_release_context(struct fsl_asrc_pair *ctx) + { +- unsigned long lock_flags; + struct fsl_asrc *easrc; + + if (!ctx) +@@ -1351,14 +1345,12 @@ static void fsl_easrc_release_context(struct fsl_asrc_pair *ctx) + + easrc = ctx->asrc; + +- spin_lock_irqsave(&easrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&easrc->lock); + + fsl_easrc_release_slot(easrc, ctx->index); + + easrc->channel_avail += ctx->channels; + easrc->pair[ctx->index] = NULL; +- +- spin_unlock_irqrestore(&easrc->lock, lock_flags); + } + + /* +@@ -2227,7 +2219,7 @@ static int fsl_easrc_probe(struct platform_device *pdev) + } + + ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt); +- easrc->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ easrc->asrc_format = asrc_fmt; + if (ret) { + dev_err(dev, "failed to asrc format\n"); + return ret; +@@ -2292,15 +2284,13 @@ static int fsl_easrc_runtime_suspend(struct device *dev) + { + struct fsl_asrc *easrc = dev_get_drvdata(dev); + struct fsl_easrc_priv *easrc_priv = easrc->private; +- unsigned long lock_flags; + + regcache_cache_only(easrc->regmap, true); + + clk_disable_unprepare(easrc->mem_clk); + +- spin_lock_irqsave(&easrc->lock, lock_flags); +- easrc_priv->firmware_loaded = 0; +- spin_unlock_irqrestore(&easrc->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &easrc->lock) ++ easrc_priv->firmware_loaded = 0; + + return 0; + } +@@ -2311,7 +2301,6 @@ static int fsl_easrc_runtime_resume(struct device *dev) + struct fsl_easrc_priv *easrc_priv = easrc->private; + struct fsl_easrc_ctx_priv *ctx_priv; + struct fsl_asrc_pair *ctx; +- unsigned long lock_flags; + int ret; + int i; + +@@ -2323,13 +2312,11 @@ static int fsl_easrc_runtime_resume(struct device *dev) + regcache_mark_dirty(easrc->regmap); + regcache_sync(easrc->regmap); + +- spin_lock_irqsave(&easrc->lock, lock_flags); +- if (easrc_priv->firmware_loaded) { +- spin_unlock_irqrestore(&easrc->lock, lock_flags); +- goto skip_load; ++ scoped_guard(spinlock_irqsave, &easrc->lock) { ++ if (easrc_priv->firmware_loaded) ++ return 0; ++ easrc_priv->firmware_loaded = 1; + } +- easrc_priv->firmware_loaded = 1; +- spin_unlock_irqrestore(&easrc->lock, lock_flags); + + ret = fsl_easrc_get_firmware(easrc); + if (ret) { +@@ -2377,9 +2364,7 @@ static int fsl_easrc_runtime_resume(struct device *dev) + goto disable_mem_clk; + } + +-skip_load: + return 0; +- + disable_mem_clk: + clk_disable_unprepare(easrc->mem_clk); + return ret; +diff --git a/sound/soc/fsl/fsl_easrc.h b/sound/soc/fsl/fsl_easrc.h +--- a/sound/soc/fsl/fsl_easrc.h ++++ b/sound/soc/fsl/fsl_easrc.h +@@ -584,7 +584,7 @@ struct fsl_easrc_slot { + }; + + /** +- * fsl_easrc_ctx_priv: EASRC context private data ++ * struct fsl_easrc_ctx_priv - EASRC context private data + * + * @in_params: input parameter + * @out_params: output parameter +@@ -625,7 +625,7 @@ struct fsl_easrc_ctx_priv { + }; + + /** +- * fsl_easrc_priv: EASRC private data ++ * struct fsl_easrc_priv - EASRC private data + * + * @slot: slot setting + * @firmware_hdr: the header of firmware +diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c +--- a/sound/soc/fsl/fsl_esai.c ++++ b/sound/soc/fsl/fsl_esai.c +@@ -709,10 +709,9 @@ static void fsl_esai_hw_reset(struct work_struct *work) + { + struct fsl_esai *esai_priv = container_of(work, struct fsl_esai, work); + bool tx = true, rx = false, enabled[2]; +- unsigned long lock_flags; + u32 tfcr, rfcr; + +- spin_lock_irqsave(&esai_priv->lock, lock_flags); ++ guard(spinlock_irqsave)(&esai_priv->lock); + /* Save the registers */ + regmap_read(esai_priv->regmap, REG_ESAI_TFCR, &tfcr); + regmap_read(esai_priv->regmap, REG_ESAI_RFCR, &rfcr); +@@ -750,8 +749,6 @@ static void fsl_esai_hw_reset(struct work_struct *work) + fsl_esai_trigger_start(esai_priv, tx); + if (enabled[rx]) + fsl_esai_trigger_start(esai_priv, rx); +- +- spin_unlock_irqrestore(&esai_priv->lock, lock_flags); + } + + static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, +@@ -759,7 +756,6 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, + { + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; +- unsigned long lock_flags; + + esai_priv->channels[tx] = substream->runtime->channels; + +@@ -767,16 +763,14 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +- spin_lock_irqsave(&esai_priv->lock, lock_flags); +- fsl_esai_trigger_start(esai_priv, tx); +- spin_unlock_irqrestore(&esai_priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &esai_priv->lock) ++ fsl_esai_trigger_start(esai_priv, tx); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +- spin_lock_irqsave(&esai_priv->lock, lock_flags); +- fsl_esai_trigger_stop(esai_priv, tx); +- spin_unlock_irqrestore(&esai_priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &esai_priv->lock) ++ fsl_esai_trigger_stop(esai_priv, tx); + break; + default: + return -EINVAL; +diff --git a/sound/soc/fsl/fsl_qmc_audio.c b/sound/soc/fsl/fsl_qmc_audio.c +--- a/sound/soc/fsl/fsl_qmc_audio.c ++++ b/sound/soc/fsl/fsl_qmc_audio.c +@@ -503,8 +503,8 @@ static int qmc_dai_constraints_interleaved(struct snd_pcm_substream *substream, + return ret; + } + +- access = 1ULL << (__force int)SNDRV_PCM_ACCESS_MMAP_INTERLEAVED | +- 1ULL << (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED; ++ access = 1ULL << SNDRV_PCM_ACCESS_MMAP_INTERLEAVED | ++ 1ULL << SNDRV_PCM_ACCESS_RW_INTERLEAVED; + ret = snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_ACCESS, + access); + if (ret) { +@@ -532,8 +532,8 @@ static int qmc_dai_constraints_noninterleaved(struct snd_pcm_substream *substrea + return ret; + } + +- access = 1ULL << (__force int)SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED | +- 1ULL << (__force int)SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; ++ access = 1ULL << SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED | ++ 1ULL << SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; + ret = snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_ACCESS, + access); + if (ret) { +diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c +--- a/sound/soc/fsl/fsl_spdif.c ++++ b/sound/soc/fsl/fsl_spdif.c +@@ -853,17 +853,15 @@ static int fsl_spdif_subcode_get(struct snd_kcontrol *kcontrol, + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; +- unsigned long flags; + int ret = -EAGAIN; + +- spin_lock_irqsave(&ctrl->ctl_lock, flags); ++ guard(spinlock_irqsave)(&ctrl->ctl_lock); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_UBITS_SIZE; + memcpy(&ucontrol->value.iec958.subcode[0], + &ctrl->subcode[idx], SPDIF_UBITS_SIZE); + ret = 0; + } +- spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; + } +@@ -885,17 +883,15 @@ static int fsl_spdif_qget(struct snd_kcontrol *kcontrol, + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; +- unsigned long flags; + int ret = -EAGAIN; + +- spin_lock_irqsave(&ctrl->ctl_lock, flags); ++ guard(spinlock_irqsave)(&ctrl->ctl_lock); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_QSUB_SIZE; + memcpy(&ucontrol->value.bytes.data[0], + &ctrl->qsub[idx], SPDIF_QSUB_SIZE); + ret = 0; + } +- spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; + } +diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c +--- a/sound/soc/fsl/fsl_ssi.c ++++ b/sound/soc/fsl/fsl_ssi.c +@@ -1218,13 +1218,13 @@ static void fsl_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + if (reg > 0x7f) + return; + +- mutex_lock(&fsl_ac97_data->ac97_reg_lock); ++ guard(mutex)(&fsl_ac97_data->ac97_reg_lock); + + ret = clk_prepare_enable(fsl_ac97_data->clk); + if (ret) { + pr_err("ac97 write clk_prepare_enable failed: %d\n", + ret); +- goto ret_unlock; ++ return; + } + + lreg = reg << 12; +@@ -1238,9 +1238,6 @@ static void fsl_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + udelay(100); + + clk_disable_unprepare(fsl_ac97_data->clk); +- +-ret_unlock: +- mutex_unlock(&fsl_ac97_data->ac97_reg_lock); + } + + static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, +@@ -1252,12 +1249,12 @@ static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, + unsigned int lreg; + int ret; + +- mutex_lock(&fsl_ac97_data->ac97_reg_lock); ++ guard(mutex)(&fsl_ac97_data->ac97_reg_lock); + + ret = clk_prepare_enable(fsl_ac97_data->clk); + if (ret) { + pr_err("ac97 read clk_prepare_enable failed: %d\n", ret); +- goto ret_unlock; ++ return val; + } + + lreg = (reg & 0x7f) << 12; +@@ -1272,8 +1269,6 @@ static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, + + clk_disable_unprepare(fsl_ac97_data->clk); + +-ret_unlock: +- mutex_unlock(&fsl_ac97_data->ac97_reg_lock); + return val; + } + +diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c +--- a/sound/soc/fsl/fsl_xcvr.c ++++ b/sound/soc/fsl/fsl_xcvr.c +@@ -797,10 +797,9 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + { + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; +- unsigned long lock_flags; + int ret = 0; + +- spin_lock_irqsave(&xcvr->lock, lock_flags); ++ guard(spinlock_irqsave)(&xcvr->lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: +@@ -812,7 +811,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_EXT_CTRL_DPTH_RESET(tx)); + if (ret < 0) { + dev_err(dai->dev, "Failed to set DPATH RESET: %d\n", ret); +- goto release_lock; ++ return ret; + } + + if (tx) { +@@ -824,7 +823,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_ISR_CMDC_TX_EN); + if (ret < 0) { + dev_err(dai->dev, "err updating isr %d\n", ret); +- goto release_lock; ++ return ret; + } + fallthrough; + case FSL_XCVR_MODE_SPDIF: +@@ -833,7 +832,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX); + if (ret < 0) { + dev_err(dai->dev, "Failed to start DATA_TX: %d\n", ret); +- goto release_lock; ++ return ret; + } + break; + } +@@ -844,14 +843,14 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_EXT_CTRL_DMA_DIS(tx), 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to enable DMA: %d\n", ret); +- goto release_lock; ++ return ret; + } + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0, + FSL_XCVR_IRQ_EARC_ALL, FSL_XCVR_IRQ_EARC_ALL); + if (ret < 0) { + dev_err(dai->dev, "Error while setting IER0: %d\n", ret); +- goto release_lock; ++ return ret; + } + + /* clear DPATH RESET */ +@@ -860,7 +859,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to clear DPATH RESET: %d\n", ret); +- goto release_lock; ++ return ret; + } + + break; +@@ -873,14 +872,14 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_EXT_CTRL_DMA_DIS(tx)); + if (ret < 0) { + dev_err(dai->dev, "Failed to disable DMA: %d\n", ret); +- goto release_lock; ++ return ret; + } + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0, + FSL_XCVR_IRQ_EARC_ALL, 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to clear IER0: %d\n", ret); +- goto release_lock; ++ return ret; + } + + if (tx) { +@@ -891,7 +890,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX); + if (ret < 0) { + dev_err(dai->dev, "Failed to stop DATA_TX: %d\n", ret); +- goto release_lock; ++ return ret; + } + if (xcvr->soc_data->spdif_only) + break; +@@ -905,7 +904,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + if (ret < 0) { + dev_err(dai->dev, + "Err updating ISR %d\n", ret); +- goto release_lock; ++ return ret; + } + break; + } +@@ -916,18 +915,16 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + break; + } + +-release_lock: +- spin_unlock_irqrestore(&xcvr->lock, lock_flags); + return ret; + } + + static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + { + struct device *dev = &xcvr->pdev->dev; +- const struct firmware *fw; + int ret = 0, rem, off, out, page = 0, size = FSL_XCVR_REG_OFFSET; + u32 mask, val; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, xcvr->soc_data->fw_name, dev); + if (ret) { + dev_err(dev, "failed to request firmware.\n"); +@@ -939,7 +936,6 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + /* RAM is 20KiB = 16KiB code + 4KiB data => max 10 pages 2KiB each */ + if (rem > 16384) { + dev_err(dev, "FW size %d is bigger than 16KiB.\n", rem); +- release_firmware(fw); + return -ENOMEM; + } + +@@ -950,7 +946,7 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + if (ret < 0) { + dev_err(dev, "FW: failed to set page %d, err=%d\n", + page, ret); +- goto err_firmware; ++ return ret; + } + + off = page * size; +@@ -971,11 +967,6 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + } + } + +-err_firmware: +- release_firmware(fw); +- if (ret < 0) +- return ret; +- + /* configure watermarks */ + mask = FSL_XCVR_EXT_CTRL_RX_FWM_MASK | FSL_XCVR_EXT_CTRL_TX_FWM_MASK; + val = FSL_XCVR_EXT_CTRL_RX_FWM(FSL_XCVR_FIFO_WMK_RX); +@@ -1448,11 +1439,10 @@ static void reset_rx_work(struct work_struct *work) + { + struct fsl_xcvr *xcvr = container_of(work, struct fsl_xcvr, work_rst); + struct device *dev = &xcvr->pdev->dev; +- unsigned long lock_flags; + u32 ext_ctrl; + + dev_dbg(dev, "reset rx path\n"); +- spin_lock_irqsave(&xcvr->lock, lock_flags); ++ guard(spinlock_irqsave)(&xcvr->lock); + regmap_read(xcvr->regmap, FSL_XCVR_EXT_CTRL, &ext_ctrl); + + if (!(ext_ctrl & FSL_XCVR_EXT_CTRL_DMA_RD_DIS)) { +@@ -1469,7 +1459,6 @@ static void reset_rx_work(struct work_struct *work) + FSL_XCVR_EXT_CTRL_RX_DPTH_RESET, + 0); + } +- spin_unlock_irqrestore(&xcvr->lock, lock_flags); + } + + static irqreturn_t irq0_isr(int irq, void *devid) +diff --git a/sound/soc/fsl/imx-audio-rpmsg.c b/sound/soc/fsl/imx-audio-rpmsg.c +--- a/sound/soc/fsl/imx-audio-rpmsg.c ++++ b/sound/soc/fsl/imx-audio-rpmsg.c +@@ -22,7 +22,6 @@ static int imx_audio_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + struct rpmsg_r_msg *r_msg = (struct rpmsg_r_msg *)data; + struct rpmsg_info *info; + struct rpmsg_msg *msg; +- unsigned long flags; + + if (!rpmsg->rpmsg_pdev) + return 0; +@@ -37,21 +36,21 @@ static int imx_audio_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + /* TYPE C is notification from M core */ + switch (r_msg->header.cmd) { + case TX_PERIOD_DONE: +- spin_lock_irqsave(&info->lock[TX], flags); +- msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; +- msg->r_msg.param.buffer_tail = +- r_msg->param.buffer_tail; +- msg->r_msg.param.buffer_tail %= info->num_period[TX]; +- spin_unlock_irqrestore(&info->lock[TX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[TX]) { ++ msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; ++ msg->r_msg.param.buffer_tail = ++ r_msg->param.buffer_tail; ++ msg->r_msg.param.buffer_tail %= info->num_period[TX]; ++ } + info->callback[TX](info->callback_param[TX]); + break; + case RX_PERIOD_DONE: +- spin_lock_irqsave(&info->lock[RX], flags); +- msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; +- msg->r_msg.param.buffer_tail = +- r_msg->param.buffer_tail; +- msg->r_msg.param.buffer_tail %= info->num_period[1]; +- spin_unlock_irqrestore(&info->lock[RX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[RX]) { ++ msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; ++ msg->r_msg.param.buffer_tail = ++ r_msg->param.buffer_tail; ++ msg->r_msg.param.buffer_tail %= info->num_period[1]; ++ } + info->callback[RX](info->callback_param[RX]); + break; + default: +diff --git a/sound/soc/fsl/imx-card.c b/sound/soc/fsl/imx-card.c +--- a/sound/soc/fsl/imx-card.c ++++ b/sound/soc/fsl/imx-card.c +@@ -531,7 +531,7 @@ static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); +- snd_mask_set(mask, (__force unsigned int)data->asrc_format); ++ snd_mask_set(mask, data->asrc_format); + + return 0; + } +@@ -684,7 +684,7 @@ static int imx_card_parse_of(struct imx_card_data *data) + } + + ret = of_property_read_u32(args.np, "fsl,asrc-format", &asrc_fmt); +- data->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ data->asrc_format = asrc_fmt; + if (ret) { + /* Fallback to old binding; translate to asrc_format */ + ret = of_property_read_u32(args.np, "fsl,asrc-width", &width); +diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c +--- a/sound/soc/fsl/imx-pcm-rpmsg.c ++++ b/sound/soc/fsl/imx-pcm-rpmsg.c +@@ -39,10 +39,9 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + struct rpmsg_device *rpdev = info->rpdev; + int ret = 0; + +- mutex_lock(&info->msg_lock); ++ guard(mutex)(&info->msg_lock); + if (!rpdev) { + dev_err(info->dev, "rpmsg channel not ready\n"); +- mutex_unlock(&info->msg_lock); + return -EINVAL; + } + +@@ -55,15 +54,12 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + sizeof(struct rpmsg_s_msg)); + if (ret) { + dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret); +- mutex_unlock(&info->msg_lock); + return ret; + } + + /* No receive msg for TYPE_C command */ +- if (msg->s_msg.header.type == MSG_TYPE_C) { +- mutex_unlock(&info->msg_lock); ++ if (msg->s_msg.header.type == MSG_TYPE_C) + return 0; +- } + + /* wait response from rpmsg */ + ret = wait_for_completion_timeout(&info->cmd_complete, +@@ -71,7 +67,6 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + if (!ret) { + dev_err(&rpdev->dev, "rpmsg_send cmd %d timeout!\n", + msg->s_msg.header.cmd); +- mutex_unlock(&info->msg_lock); + return -ETIMEDOUT; + } + +@@ -100,8 +95,6 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + dev_dbg(&rpdev->dev, "cmd:%d, resp %d\n", msg->s_msg.header.cmd, + info->r_msg.param.resp); + +- mutex_unlock(&info->msg_lock); +- + return 0; + } + +@@ -109,14 +102,13 @@ static int imx_rpmsg_insert_workqueue(struct snd_pcm_substream *substream, + struct rpmsg_msg *msg, + struct rpmsg_info *info) + { +- unsigned long flags; + int ret = 0; + + /* + * Queue the work to workqueue. + * If the queue is full, drop the message. + */ +- spin_lock_irqsave(&info->wq_lock, flags); ++ guard(spinlock_irqsave)(&info->wq_lock); + if (info->work_write_index != info->work_read_index) { + int index = info->work_write_index; + +@@ -130,7 +122,6 @@ static int imx_rpmsg_insert_workqueue(struct snd_pcm_substream *substream, + info->msg_drop_count[substream->stream]++; + ret = -EPIPE; + } +- spin_unlock_irqrestore(&info->wq_lock, flags); + + return ret; + } +@@ -523,7 +514,6 @@ static int imx_rpmsg_pcm_ack(struct snd_soc_component *component, + snd_pcm_sframes_t avail; + struct timer_list *timer; + struct rpmsg_msg *msg; +- unsigned long flags; + int buffer_tail = 0; + int written_num; + +@@ -553,11 +543,11 @@ static int imx_rpmsg_pcm_ack(struct snd_soc_component *component, + msg->s_msg.param.buffer_tail = buffer_tail; + + /* The notification message is updated to latest */ +- spin_lock_irqsave(&info->lock[substream->stream], flags); +- memcpy(&info->notify[substream->stream], msg, +- sizeof(struct rpmsg_s_msg)); +- info->notify_updated[substream->stream] = true; +- spin_unlock_irqrestore(&info->lock[substream->stream], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[substream->stream]) { ++ memcpy(&info->notify[substream->stream], msg, ++ sizeof(struct rpmsg_s_msg)); ++ info->notify_updated[substream->stream] = true; ++ } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + avail = snd_pcm_playback_hw_avail(runtime); +@@ -641,7 +631,7 @@ static void imx_rpmsg_pcm_work(struct work_struct *work) + bool is_notification = false; + struct rpmsg_info *info; + struct rpmsg_msg msg; +- unsigned long flags; ++ bool updated; + + work_of_rpmsg = container_of(work, struct work_of_rpmsg, work); + info = work_of_rpmsg->info; +@@ -652,25 +642,26 @@ static void imx_rpmsg_pcm_work(struct work_struct *work) + * enough data in M core side, need to let M core know + * data is updated immediately. + */ +- spin_lock_irqsave(&info->lock[TX], flags); +- if (info->notify_updated[TX]) { +- memcpy(&msg, &info->notify[TX], sizeof(struct rpmsg_s_msg)); +- info->notify_updated[TX] = false; +- spin_unlock_irqrestore(&info->lock[TX], flags); +- info->send_message(&msg, info); +- } else { +- spin_unlock_irqrestore(&info->lock[TX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[TX]) { ++ updated = info->notify_updated[TX]; ++ if (updated) { ++ memcpy(&msg, &info->notify[TX], sizeof(struct rpmsg_s_msg)); ++ info->notify_updated[TX] = false; ++ } + } ++ if (updated) ++ info->send_message(&msg, info); + +- spin_lock_irqsave(&info->lock[RX], flags); +- if (info->notify_updated[RX]) { +- memcpy(&msg, &info->notify[RX], sizeof(struct rpmsg_s_msg)); +- info->notify_updated[RX] = false; +- spin_unlock_irqrestore(&info->lock[RX], flags); +- info->send_message(&msg, info); +- } else { +- spin_unlock_irqrestore(&info->lock[RX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[RX]) { ++ updated = info->notify_updated[RX]; ++ if (updated) { ++ memcpy(&msg, &info->notify[RX], sizeof(struct rpmsg_s_msg)); ++ info->notify_updated[RX] = false; ++ } + } ++ if (updated) ++ info->send_message(&msg, info); ++ + + /* Skip the notification message for it has been processed above */ + if (work_of_rpmsg->msg.s_msg.header.type == MSG_TYPE_C && +@@ -682,10 +673,10 @@ static void imx_rpmsg_pcm_work(struct work_struct *work) + info->send_message(&work_of_rpmsg->msg, info); + + /* update read index */ +- spin_lock_irqsave(&info->wq_lock, flags); +- info->work_read_index++; +- info->work_read_index %= WORK_MAX_NUM; +- spin_unlock_irqrestore(&info->wq_lock, flags); ++ scoped_guard(spinlock_irqsave, &info->wq_lock) { ++ info->work_read_index++; ++ info->work_read_index %= WORK_MAX_NUM; ++ } + } + + static int imx_rpmsg_pcm_probe(struct platform_device *pdev) +@@ -819,9 +810,9 @@ static const struct dev_pm_ops imx_rpmsg_pcm_pm_ops = { + }; + + static const struct platform_device_id imx_rpmsg_pcm_id_table[] = { +- { .name = "rpmsg-audio-channel" }, +- { .name = "rpmsg-micfil-channel" }, +- { }, ++ { .name = "rpmsg-audio-channel" }, ++ { .name = "rpmsg-micfil-channel" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, imx_rpmsg_pcm_id_table); + +diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c +--- a/sound/soc/fsl/mpc5200_dma.c ++++ b/sound/soc/fsl/mpc5200_dma.c +@@ -77,18 +77,20 @@ static irqreturn_t psc_dma_bcom_irq(int irq, void *_psc_dma_stream) + { + struct psc_dma_stream *s = _psc_dma_stream; + +- spin_lock(&s->psc_dma->lock); +- /* For each finished period, dequeue the completed period buffer +- * and enqueue a new one in it's place. */ +- while (bcom_buffer_done(s->bcom_task)) { +- bcom_retrieve_buffer(s->bcom_task, NULL, NULL); ++ scoped_guard(spinlock, &s->psc_dma->lock) { ++ /* ++ * For each finished period, dequeue the completed period buffer ++ * and enqueue a new one in its place ++ */ ++ while (bcom_buffer_done(s->bcom_task)) { ++ bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + +- s->period_current = (s->period_current+1) % s->runtime->periods; +- s->period_count++; ++ s->period_current = (s->period_current+1) % s->runtime->periods; ++ s->period_count++; + +- psc_dma_bcom_enqueue_next_buffer(s); ++ psc_dma_bcom_enqueue_next_buffer(s); ++ } + } +- spin_unlock(&s->psc_dma->lock); + + /* If the stream is active, then also inform the PCM middle layer + * of the period finished event. */ +@@ -116,7 +118,6 @@ static int psc_dma_trigger(struct snd_soc_component *component, + struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma); + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + u16 imr; +- unsigned long flags; + int i; + + switch (cmd) { +@@ -135,19 +136,18 @@ static int psc_dma_trigger(struct snd_soc_component *component, + /* Fill up the bestcomm bd queue and enable DMA. + * This will begin filling the PSC's fifo. + */ +- spin_lock_irqsave(&psc_dma->lock, flags); ++ scoped_guard(spinlock_irqsave, &psc_dma->lock) { ++ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) ++ bcom_gen_bd_rx_reset(s->bcom_task); ++ else ++ bcom_gen_bd_tx_reset(s->bcom_task); + +- if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) +- bcom_gen_bd_rx_reset(s->bcom_task); +- else +- bcom_gen_bd_tx_reset(s->bcom_task); ++ for (i = 0; i < runtime->periods; i++) ++ if (!bcom_queue_full(s->bcom_task)) ++ psc_dma_bcom_enqueue_next_buffer(s); + +- for (i = 0; i < runtime->periods; i++) +- if (!bcom_queue_full(s->bcom_task)) +- psc_dma_bcom_enqueue_next_buffer(s); +- +- bcom_enable(s->bcom_task); +- spin_unlock_irqrestore(&psc_dma->lock, flags); ++ bcom_enable(s->bcom_task); ++ } + + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + +@@ -158,13 +158,13 @@ static int psc_dma_trigger(struct snd_soc_component *component, + substream->pstr->stream, s->period_count); + s->active = 0; + +- spin_lock_irqsave(&psc_dma->lock, flags); +- bcom_disable(s->bcom_task); +- if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) +- bcom_gen_bd_rx_reset(s->bcom_task); +- else +- bcom_gen_bd_tx_reset(s->bcom_task); +- spin_unlock_irqrestore(&psc_dma->lock, flags); ++ scoped_guard(spinlock_irqsave, &psc_dma->lock) { ++ bcom_disable(s->bcom_task); ++ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) ++ bcom_gen_bd_rx_reset(s->bcom_task); ++ else ++ bcom_gen_bd_tx_reset(s->bcom_task); ++ } + + break; + +@@ -314,35 +314,29 @@ int mpc5200_audio_dma_create(struct platform_device *op) + { + phys_addr_t fifo; + struct psc_dma *psc_dma; +- struct resource res; ++ struct resource *res; + int size, irq, rc; + const __be32 *prop; + void __iomem *regs; +- int ret; ++ ++ regs = devm_platform_get_and_ioremap_resource(op, 0, &res); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); + + /* Fetch the registers and IRQ of the PSC */ +- irq = irq_of_parse_and_map(op->dev.of_node, 0); +- if (of_address_to_resource(op->dev.of_node, 0, &res)) { +- dev_err(&op->dev, "Missing reg property\n"); +- return -ENODEV; +- } +- regs = devm_ioremap(&op->dev, res.start, resource_size(&res)); +- if (!regs) { +- dev_err(&op->dev, "Could not map registers\n"); +- return -ENODEV; +- } ++ irq = platform_get_irq(op, 0); ++ if (irq < 0) ++ return irq; + + /* Allocate and initialize the driver private data */ +- psc_dma = kzalloc_obj(*psc_dma); ++ psc_dma = devm_kzalloc(&op->dev, sizeof(*psc_dma), GFP_KERNEL); + if (!psc_dma) + return -ENOMEM; + + /* Get the PSC ID */ + prop = of_get_property(op->dev.of_node, "cell-index", &size); +- if (!prop || size < sizeof *prop) { +- ret = -ENODEV; +- goto out_free; +- } ++ if (!prop || size < sizeof *prop) ++ return -ENODEV; + + spin_lock_init(&psc_dma->lock); + mutex_init(&psc_dma->mutex); +@@ -357,7 +351,7 @@ int mpc5200_audio_dma_create(struct platform_device *op) + + /* Find the address of the fifo data registers and setup the + * DMA tasks */ +- fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32); ++ fifo = res->start + offsetof(struct mpc52xx_psc, buffer.buffer_32); + psc_dma->capture.bcom_task = + bcom_psc_gen_bd_rx_init(psc_dma->id, 10, fifo, 512); + psc_dma->playback.bcom_task = +@@ -365,8 +359,7 @@ int mpc5200_audio_dma_create(struct platform_device *op) + if (!psc_dma->capture.bcom_task || + !psc_dma->playback.bcom_task) { + dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); +- ret = -ENODEV; +- goto out_free; ++ return -ENODEV; + } + + /* Disable all interrupts and reset the PSC */ +@@ -399,16 +392,14 @@ int mpc5200_audio_dma_create(struct platform_device *op) + psc_dma->capture.irq = + bcom_get_task_irq(psc_dma->capture.bcom_task); + +- rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, ++ rc = devm_request_irq(&op->dev, psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, + "psc-dma-status", psc_dma); +- rc |= request_irq(psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED, ++ rc |= devm_request_irq(&op->dev, psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-capture", &psc_dma->capture); +- rc |= request_irq(psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED, ++ rc |= devm_request_irq(&op->dev, psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-playback", &psc_dma->playback); +- if (rc) { +- ret = -ENODEV; +- goto out_irq; +- } ++ if (rc) ++ return -ENODEV; + + /* Save what we've done so it can be found again later */ + dev_set_drvdata(&op->dev, psc_dma); +@@ -416,13 +407,6 @@ int mpc5200_audio_dma_create(struct platform_device *op) + /* Tell the ASoC OF helpers about it */ + return devm_snd_soc_register_component(&op->dev, + &mpc5200_audio_dma_component, NULL, 0); +-out_irq: +- free_irq(psc_dma->irq, psc_dma); +- free_irq(psc_dma->capture.irq, &psc_dma->capture); +- free_irq(psc_dma->playback.irq, &psc_dma->playback); +-out_free: +- kfree(psc_dma); +- return ret; + } + EXPORT_SYMBOL_GPL(mpc5200_audio_dma_create); + +@@ -435,12 +419,6 @@ int mpc5200_audio_dma_destroy(struct platform_device *op) + bcom_gen_bd_rx_release(psc_dma->capture.bcom_task); + bcom_gen_bd_tx_release(psc_dma->playback.bcom_task); + +- /* Release irqs */ +- free_irq(psc_dma->irq, psc_dma); +- free_irq(psc_dma->capture.irq, &psc_dma->capture); +- free_irq(psc_dma->playback.irq, &psc_dma->playback); +- +- kfree(psc_dma); + dev_set_drvdata(&op->dev, NULL); + + return 0; +diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c +--- a/sound/soc/fsl/mpc5200_psc_ac97.c ++++ b/sound/soc/fsl/mpc5200_psc_ac97.c +@@ -30,14 +30,13 @@ static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) + int status; + unsigned int val; + +- mutex_lock(&psc_dma->mutex); ++ guard(mutex)(&psc_dma->mutex); + + /* Wait for command send status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (rdy)\n"); +- mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + +@@ -53,19 +52,16 @@ static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) + if (status == 0) { + pr_err("timeout on ac97 read (val) %x\n", + in_be16(&psc_dma->psc_regs->sr_csr.status)); +- mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + /* Get the data */ + val = in_be32(&psc_dma->psc_regs->ac97_data); + if (((val >> 24) & 0x7f) != reg) { + pr_err("reg echo error on ac97 read\n"); +- mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + val = (val >> 8) & 0xffff; + +- mutex_unlock(&psc_dma->mutex); + return (unsigned short) val; + } + +@@ -74,52 +70,46 @@ static void psc_ac97_write(struct snd_ac97 *ac97, + { + int status; + +- mutex_lock(&psc_dma->mutex); ++ guard(mutex)(&psc_dma->mutex); + + /* Wait for command status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (write)\n"); +- goto out; ++ return; + } + /* Write data */ + out_be32(&psc_dma->psc_regs->ac97_cmd, + ((reg & 0x7f) << 24) | (val << 8)); +- +- out: +- mutex_unlock(&psc_dma->mutex); + } + + static void psc_ac97_warm_reset(struct snd_ac97 *ac97) + { + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + +- mutex_lock(&psc_dma->mutex); ++ guard(mutex)(&psc_dma->mutex); + + out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR); + udelay(3); + out_be32(®s->sicr, psc_dma->sicr); +- +- mutex_unlock(&psc_dma->mutex); + } + + static void psc_ac97_cold_reset(struct snd_ac97 *ac97) + { + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + +- mutex_lock(&psc_dma->mutex); +- dev_dbg(psc_dma->dev, "cold reset\n"); ++ scoped_guard(mutex, &psc_dma->mutex) { ++ dev_dbg(psc_dma->dev, "cold reset\n"); + +- mpc5200_psc_ac97_gpio_reset(psc_dma->id); ++ mpc5200_psc_ac97_gpio_reset(psc_dma->id); + +- /* Notify the PSC that a reset has occurred */ +- out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB); ++ /* Notify the PSC that a reset has occurred */ ++ out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB); + +- /* Re-enable RX and TX */ +- out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); +- +- mutex_unlock(&psc_dma->mutex); ++ /* Re-enable RX and TX */ ++ out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); ++ } + + usleep_range(1000, 2000); + psc_ac97_warm_reset(ac97); +@@ -286,7 +276,7 @@ static int psc_ac97_of_probe(struct platform_device *op) + return rc; + } + +- rc = snd_soc_register_component(&op->dev, &psc_ac97_component, ++ rc = devm_snd_soc_register_component(&op->dev, &psc_ac97_component, + psc_ac97_dai, ARRAY_SIZE(psc_ac97_dai)); + if (rc != 0) { + dev_err(&op->dev, "Failed to register DAI\n"); +@@ -312,8 +302,6 @@ static int psc_ac97_of_probe(struct platform_device *op) + static void psc_ac97_of_remove(struct platform_device *op) + { + mpc5200_audio_dma_destroy(op); +- snd_soc_unregister_component(&op->dev); +- snd_soc_set_ac97_ops(NULL); + } + + /* Match table for of_platform binding */ +diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c +--- a/sound/soc/fsl/mpc5200_psc_i2s.c ++++ b/sound/soc/fsl/mpc5200_psc_i2s.c +@@ -79,9 +79,12 @@ static int psc_i2s_hw_params(struct snd_pcm_substream *substream, + * and we don't care about the frequency. Return an error if the direction + * is not SND_SOC_CLOCK_IN. + * ++ * @cpu_dai: DAI runtime data pointer + * @clk_id: reserved, should be zero + * @freq: the frequency of the given clock ID, currently ignored + * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master) ++ * ++ * Returns: %0 on success or %-EINVAL on failure. + */ + static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +@@ -101,7 +104,10 @@ static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + * This driver only supports I2S mode. Return an error if the format is + * not SND_SOC_DAIFMT_I2S. + * ++ * @cpu_dai: DAI runtime data pointer + * @format: one of SND_SOC_DAIFMT_xxx ++ * ++ * Returns: %0 on success or %-EINVAL on failure. + */ + static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) + { +@@ -119,7 +125,7 @@ static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) + */ + + /** +- * psc_i2s_dai_template: template CPU Digital Audio Interface ++ * var psc_i2s_dai_ops - template CPU Digital Audio Interface + */ + static const struct snd_soc_dai_ops psc_i2s_dai_ops = { + .hw_params = psc_i2s_hw_params, +@@ -166,7 +172,7 @@ static int psc_i2s_of_probe(struct platform_device *op) + if (rc != 0) + return rc; + +- rc = snd_soc_register_component(&op->dev, &psc_i2s_component, ++ rc = devm_snd_soc_register_component(&op->dev, &psc_i2s_component, + psc_i2s_dai, ARRAY_SIZE(psc_i2s_dai)); + if (rc != 0) { + pr_err("Failed to register DAI\n"); +@@ -213,7 +219,6 @@ static int psc_i2s_of_probe(struct platform_device *op) + static void psc_i2s_of_remove(struct platform_device *op) + { + mpc5200_audio_dma_destroy(op); +- snd_soc_unregister_component(&op->dev); + } + + /* Match table for of_platform binding */ +diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c +--- a/sound/soc/fsl/p1022_ds.c ++++ b/sound/soc/fsl/p1022_ds.c +@@ -73,6 +73,8 @@ struct machine_data { + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ + }; + ++#define card_to_mdata(_card) container_of(_card, struct machine_data, card) ++ + /** + * p1022_ds_machine_probe: initialize the board + * +@@ -82,8 +84,7 @@ struct machine_data { + */ + static int p1022_ds_machine_probe(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -122,8 +123,7 @@ static int p1022_ds_machine_probe(struct snd_soc_card *card) + static int p1022_ds_startup(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); +- struct machine_data *mdata = +- container_of(rtd->card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(rtd->card); + struct device *dev = rtd->card->dev; + int ret = 0; + +@@ -156,8 +156,7 @@ static int p1022_ds_startup(struct snd_pcm_substream *substream) + */ + static int p1022_ds_machine_remove(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -399,8 +398,7 @@ static int p1022_ds_probe(struct platform_device *pdev) + static void p1022_ds_remove(struct platform_device *pdev) + { + struct snd_soc_card *card = platform_get_drvdata(pdev); +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + + snd_soc_unregister_card(card); + kfree(mdata); +diff --git a/sound/soc/fsl/p1022_rdk.c b/sound/soc/fsl/p1022_rdk.c +--- a/sound/soc/fsl/p1022_rdk.c ++++ b/sound/soc/fsl/p1022_rdk.c +@@ -79,6 +79,8 @@ struct machine_data { + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ + }; + ++#define card_to_mdata(_card) container_of(_card, struct machine_data, card) ++ + /** + * p1022_rdk_machine_probe - initialize the board + * @card: ASoC card instance +@@ -91,8 +93,7 @@ struct machine_data { + */ + static int p1022_rdk_machine_probe(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -134,8 +135,7 @@ static int p1022_rdk_machine_probe(struct snd_soc_card *card) + static int p1022_rdk_startup(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); +- struct machine_data *mdata = +- container_of(rtd->card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(rtd->card); + struct device *dev = rtd->card->dev; + int ret = 0; + +@@ -169,8 +169,7 @@ static int p1022_rdk_startup(struct snd_pcm_substream *substream) + */ + static int p1022_rdk_machine_remove(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -361,8 +360,7 @@ static int p1022_rdk_probe(struct platform_device *pdev) + static void p1022_rdk_remove(struct platform_device *pdev) + { + struct snd_soc_card *card = platform_get_drvdata(pdev); +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + + snd_soc_unregister_card(card); + kfree(mdata); +diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c +--- a/sound/soc/generic/audio-graph-card.c ++++ b/sound/soc/generic/audio-graph-card.c +@@ -579,11 +579,11 @@ int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) + goto end; + } + +- ret = simple_util_parse_widgets(card, NULL); ++ ret = simple_util_parse_widgets(priv, NULL); + if (ret < 0) + goto end; + +- ret = simple_util_parse_routing(card, NULL); ++ ret = simple_util_parse_routing(priv, NULL); + if (ret < 0) + goto end; + +@@ -594,6 +594,7 @@ int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) + if (ret < 0) + goto err; + ++ /* Card name should be set after graph_for_each_link() */ + ret = simple_util_parse_card_name(priv, NULL); + if (ret < 0) + goto err; +@@ -603,14 +604,13 @@ int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) + simple_util_debug_info(priv); + + ret = devm_snd_soc_register_card(dev, card); +- if (ret < 0) +- goto err; +- +- return 0; + err: +- simple_util_clean_reference(card); ++ if (ret < 0) { ++ simple_util_clean_reference(priv); ++ return dev_err_probe(dev, ret, "parse error\n"); ++ } + end: +- return dev_err_probe(dev, ret, "parse error\n"); ++ return graph_ret(priv, ret); + } + EXPORT_SYMBOL_GPL(audio_graph_parse_of); + +diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c +--- a/sound/soc/generic/audio-graph-card2.c ++++ b/sound/soc/generic/audio-graph-card2.c +@@ -1303,11 +1303,11 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + struct graph2_custom_hooks *hooks) + { + struct snd_soc_card *card = simple_priv_to_card(priv); +- int ret; ++ int ret = -ENOMEM; + + struct link_info *li __free(kfree) = kzalloc_obj(*li); + if (!li) +- return -ENOMEM; ++ goto end; + + card->probe = graph_util_card_probe; + card->owner = THIS_MODULE; +@@ -1316,31 +1316,35 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + if ((hooks) && (hooks)->hook_pre) { + ret = (hooks)->hook_pre(priv); + if (ret < 0) +- goto err; ++ goto end; + } + + ret = graph_for_each_link(priv, hooks, li, graph_count); + if (!li->link) + ret = -EINVAL; + if (ret < 0) +- goto err; ++ goto end; + + ret = simple_util_init_priv(priv, li); + if (ret < 0) +- goto err; ++ goto end; + + priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW); + if (IS_ERR(priv->pa_gpio)) { + ret = PTR_ERR(priv->pa_gpio); + dev_err(dev, "failed to get amplifier gpio: %d\n", ret); +- goto err; ++ goto end; + } + +- ret = simple_util_parse_widgets(card, NULL); ++ ret = simple_util_parse_widgets(priv, NULL); + if (ret < 0) +- goto err; ++ goto end; + +- ret = simple_util_parse_routing(card, NULL); ++ ret = simple_util_parse_routing(priv, NULL); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_aux_devs(priv, NULL); + if (ret < 0) + goto err; + +@@ -1349,6 +1353,7 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + if (ret < 0) + goto err; + ++ /* Card name should be set after graph_for_each_link() */ + ret = simple_util_parse_card_name(priv, NULL); + if (ret < 0) + goto err; +@@ -1363,15 +1368,13 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + + simple_util_debug_info(priv); + +- ret = snd_soc_of_parse_aux_devs(card, "aux-devs"); +- if (ret < 0) +- goto err; +- + ret = devm_snd_soc_register_card(dev, card); + err: +- if (ret < 0) +- dev_err_probe(dev, ret, "parse error\n"); +- ++ if (ret < 0) { ++ simple_util_clean_reference(priv); ++ return dev_err_probe(dev, ret, "parse error\n"); ++ } ++end: + return graph_ret(priv, ret); + } + EXPORT_SYMBOL_GPL(audio_graph2_parse_of); +diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c +--- a/sound/soc/generic/simple-card-utils.c ++++ b/sound/soc/generic/simple-card-utils.c +@@ -216,6 +216,27 @@ int simple_util_set_dailink_name(struct simple_util_priv *priv, + } + EXPORT_SYMBOL_GPL(simple_util_set_dailink_name); + ++int simple_util_parse_property(struct simple_util_priv *priv, ++ int (*func)(struct snd_soc_card *card, const char *propname), ++ char *prefix, char *property) ++{ ++ struct snd_soc_card *card = simple_priv_to_card(priv); ++ struct device_node *node = card->dev->of_node; ++ char prop[128]; ++ ++ if (!prefix) ++ prefix = ""; ++ ++ snprintf(prop, sizeof(prop), "%s%s", prefix, property); ++ ++ /* no property is not error */ ++ if (!of_property_present(node, prop)) ++ return 0; ++ ++ return func(card, prop); ++} ++EXPORT_SYMBOL_GPL(simple_util_parse_property); ++ + int simple_util_parse_card_name(struct simple_util_priv *priv, + char *prefix) + { +@@ -731,11 +752,12 @@ void simple_util_canonicalize_cpu(struct snd_soc_dai_link_component *cpus, + } + EXPORT_SYMBOL_GPL(simple_util_canonicalize_cpu); + +-void simple_util_clean_reference(struct snd_soc_card *card) ++void simple_util_clean_reference(struct simple_util_priv *priv) + { + struct snd_soc_dai_link *dai_link; + struct snd_soc_dai_link_component *cpu; + struct snd_soc_dai_link_component *codec; ++ struct snd_soc_card *card = simple_priv_to_card(priv); + int i, j; + + for_each_card_prelinks(card, i, dai_link) { +@@ -747,57 +769,6 @@ void simple_util_clean_reference(struct snd_soc_card *card) + } + EXPORT_SYMBOL_GPL(simple_util_clean_reference); + +-int simple_util_parse_routing(struct snd_soc_card *card, +- char *prefix) +-{ +- struct device_node *node = card->dev->of_node; +- char prop[128]; +- +- if (!prefix) +- prefix = ""; +- +- snprintf(prop, sizeof(prop), "%s%s", prefix, "routing"); +- +- if (!of_property_present(node, prop)) +- return 0; +- +- return snd_soc_of_parse_audio_routing(card, prop); +-} +-EXPORT_SYMBOL_GPL(simple_util_parse_routing); +- +-int simple_util_parse_widgets(struct snd_soc_card *card, +- char *prefix) +-{ +- struct device_node *node = card->dev->of_node; +- char prop[128]; +- +- if (!prefix) +- prefix = ""; +- +- snprintf(prop, sizeof(prop), "%s%s", prefix, "widgets"); +- +- if (of_property_present(node, prop)) +- return snd_soc_of_parse_audio_simple_widgets(card, prop); +- +- /* no widgets is not error */ +- return 0; +-} +-EXPORT_SYMBOL_GPL(simple_util_parse_widgets); +- +-int simple_util_parse_pin_switches(struct snd_soc_card *card, +- char *prefix) +-{ +- char prop[128]; +- +- if (!prefix) +- prefix = ""; +- +- snprintf(prop, sizeof(prop), "%s%s", prefix, "pin-switches"); +- +- return snd_soc_of_parse_pin_switches(card, prop); +-} +-EXPORT_SYMBOL_GPL(simple_util_parse_pin_switches); +- + int simple_util_init_jack(struct snd_soc_card *card, + struct simple_util_jack *sjack, + int is_hp, char *prefix, +@@ -854,9 +825,9 @@ int simple_util_init_jack(struct snd_soc_card *card, + } + EXPORT_SYMBOL_GPL(simple_util_init_jack); + +-int simple_util_init_aux_jacks(struct simple_util_priv *priv, char *prefix) ++int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix) + { +- struct snd_soc_card *card = simple_priv_to_card(priv); ++ struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component; + int found_jack_index = 0; + int type = 0; +@@ -1026,8 +997,9 @@ EXPORT_SYMBOL_GPL(simple_util_init_priv); + void simple_util_remove(struct platform_device *pdev) + { + struct snd_soc_card *card = platform_get_drvdata(pdev); ++ struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + +- simple_util_clean_reference(card); ++ simple_util_clean_reference(priv); + } + EXPORT_SYMBOL_GPL(simple_util_remove); + +diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c +--- a/sound/soc/generic/simple-card.c ++++ b/sound/soc/generic/simple-card.c +@@ -517,44 +517,6 @@ static int simple_populate_aux(struct simple_util_priv *priv) + return simple_ret(priv, ret); + } + +-static int simple_parse_of(struct simple_util_priv *priv, struct link_info *li) +-{ +- struct snd_soc_card *card = simple_priv_to_card(priv); +- int ret; +- +- ret = simple_util_parse_widgets(card, PREFIX); +- if (ret < 0) +- goto end; +- +- ret = simple_util_parse_routing(card, PREFIX); +- if (ret < 0) +- goto end; +- +- ret = simple_util_parse_pin_switches(card, PREFIX); +- if (ret < 0) +- goto end; +- +- /* Single/Muti DAI link(s) & New style of DT node */ +- memset(li, 0, sizeof(*li)); +- ret = simple_for_each_link(priv, li, +- simple_dai_link_of, +- simple_dai_link_of_dpcm); +- if (ret < 0) +- goto end; +- +- ret = simple_util_parse_card_name(priv, PREFIX); +- if (ret < 0) +- goto end; +- +- ret = simple_populate_aux(priv); +- if (ret < 0) +- goto end; +- +- ret = snd_soc_of_parse_aux_devs(card, PREFIX "aux-devs"); +-end: +- return simple_ret(priv, ret); +-} +- + static int simple_count_noml(struct simple_util_priv *priv, + struct device_node *np, + struct device_node *codec, +@@ -699,28 +661,19 @@ static int simple_soc_probe(struct snd_soc_card *card) + if (ret < 0) + goto end; + +- ret = simple_util_init_aux_jacks(priv, PREFIX); ++ ret = simple_util_init_aux_jacks(card, PREFIX); + end: + return simple_ret(priv, ret); + } + +-static int simple_probe(struct platform_device *pdev) ++static int simple_parse_of(struct simple_util_priv *priv) + { +- struct simple_util_priv *priv; +- struct device *dev = &pdev->dev; +- struct snd_soc_card *card; +- int ret; ++ struct snd_soc_card *card = simple_priv_to_card(priv); ++ struct device *dev = card->dev; ++ int ret = -EINVAL; + +- /* Allocate the private data and the DAI link array */ +- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); +- if (!priv) +- return -ENOMEM; +- +- card = simple_priv_to_card(priv); +- card->owner = THIS_MODULE; +- card->dev = dev; +- card->probe = simple_soc_probe; +- card->driver_name = "simple-card"; ++ if (!dev) ++ return simple_ret(priv, ret); + + ret = -ENOMEM; + struct link_info *li __free(kfree) = kzalloc_obj(*li); +@@ -739,25 +692,71 @@ static int simple_probe(struct platform_device *pdev) + if (ret < 0) + goto end; + +- ret = simple_parse_of(priv, li); +- if (ret < 0) { +- dev_err_probe(dev, ret, "parse error\n"); ++ ret = simple_util_parse_widgets(priv, PREFIX); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_routing(priv, PREFIX); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_pin_switches(priv, PREFIX); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_aux_devs(priv, PREFIX); ++ if (ret < 0) ++ goto err; ++ ++ /* Single/Muti DAI link(s) & New style of DT node */ ++ memset(li, 0, sizeof(*li)); ++ ret = simple_for_each_link(priv, li, ++ simple_dai_link_of, ++ simple_dai_link_of_dpcm); ++ if (ret < 0) ++ goto err; ++ ++ /* Card name should be set after simple_for_each_link() */ ++ ret = simple_util_parse_card_name(priv, PREFIX); ++ if (ret < 0) ++ goto err; ++ ++ ret = simple_populate_aux(priv); ++ if (ret < 0) + goto err; +- } + + snd_soc_card_set_drvdata(card, priv); + + simple_util_debug_info(priv); + + ret = devm_snd_soc_register_card(dev, card); +- if (ret < 0) +- goto err; +- +- return 0; + err: +- simple_util_clean_reference(card); ++ if (ret < 0) { ++ simple_util_clean_reference(priv); ++ return dev_err_probe(dev, ret, "parse error\n"); ++ } + end: +- return dev_err_probe(dev, ret, "parse error\n"); ++ return simple_ret(priv, ret); ++} ++ ++static int simple_probe(struct platform_device *pdev) ++{ ++ struct simple_util_priv *priv; ++ struct device *dev = &pdev->dev; ++ struct snd_soc_card *card; ++ ++ /* Allocate the private data and the DAI link array */ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ card = simple_priv_to_card(priv); ++ card->owner = THIS_MODULE; ++ card->dev = dev; ++ card->probe = simple_soc_probe; ++ card->driver_name = "simple-card"; ++ ++ return simple_parse_of(priv); + } + + static const struct of_device_id simple_of_match[] = { +diff --git a/sound/soc/intel/atom/sst-atom-controls.c b/sound/soc/intel/atom/sst-atom-controls.c +--- a/sound/soc/intel/atom/sst-atom-controls.c ++++ b/sound/soc/intel/atom/sst-atom-controls.c +@@ -14,6 +14,7 @@ + */ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + ++#include + #include + #include + #include +@@ -73,14 +74,10 @@ static int sst_fill_and_send_cmd(struct sst_data *drv, + u8 ipc_msg, u8 block, u8 task_id, u8 pipe_id, + void *cmd_data, u16 len) + { +- int ret; ++ guard(mutex)(&drv->lock); + +- mutex_lock(&drv->lock); +- ret = sst_fill_and_send_cmd_unlocked(drv, ipc_msg, block, +- task_id, pipe_id, cmd_data, len); +- mutex_unlock(&drv->lock); +- +- return ret; ++ return sst_fill_and_send_cmd_unlocked(drv, ipc_msg, block, ++ task_id, pipe_id, cmd_data, len); + } + + /* +@@ -167,7 +164,7 @@ static int sst_slot_get(struct snd_kcontrol *kcontrol, + unsigned int val, mux; + u8 *map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + val = 1 << ctl_no; + /* search which slot/channel has this bit set - there should be only one */ + for (mux = e->max; mux > 0; mux--) +@@ -175,7 +172,6 @@ static int sst_slot_get(struct snd_kcontrol *kcontrol, + break; + + ucontrol->value.enumerated.item[0] = mux; +- mutex_unlock(&drv->lock); + + dev_dbg(c->dev, "%s - %s map = %#x\n", + is_tx ? "tx channel" : "rx slot", +@@ -235,7 +231,7 @@ static int sst_slot_put(struct snd_kcontrol *kcontrol, + if (mux > e->max - 1) + return -EINVAL; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + /* first clear all registers of this bit */ + for (i = 0; i < e->max; i++) + map[i] &= ~val; +@@ -244,7 +240,6 @@ static int sst_slot_put(struct snd_kcontrol *kcontrol, + /* kctl set to 'none' and we reset the bits so send IPC */ + ret = sst_check_and_send_slot_map(drv, kcontrol); + +- mutex_unlock(&drv->lock); + return ret; + } + +@@ -258,7 +253,6 @@ static int sst_slot_put(struct snd_kcontrol *kcontrol, + + ret = sst_check_and_send_slot_map(drv, kcontrol); + +- mutex_unlock(&drv->lock); + return ret; + } + +@@ -354,13 +348,12 @@ static int sst_algo_control_set(struct snd_kcontrol *kcontrol, + struct sst_algo_control *bc = (void *)kcontrol->private_value; + + dev_dbg(cmpnt->dev, "control_name=%s\n", kcontrol->id.name); +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + switch (bc->type) { + case SST_ALGO_PARAMS: + memcpy(bc->params, ucontrol->value.bytes.data, bc->max); + break; + default: +- mutex_unlock(&drv->lock); + dev_err(cmpnt->dev, "Invalid Input- algo type:%d\n", + bc->type); + return -EINVAL; +@@ -368,7 +361,6 @@ static int sst_algo_control_set(struct snd_kcontrol *kcontrol, + /*if pipe is enabled, need to send the algo params from here*/ + if (bc->w && bc->w->power) + ret = sst_send_algo_cmd(drv, bc); +- mutex_unlock(&drv->lock); + + return ret; + } +@@ -475,7 +467,7 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol, + struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; + struct sst_gain_value *gv = mc->gain_val; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + + switch (mc->type) { + case SST_GAIN_TLV: +@@ -497,7 +489,6 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol, + break; + + default: +- mutex_unlock(&drv->lock); + dev_err(cmpnt->dev, "Invalid Input- gain type:%d\n", + mc->type); + return -EINVAL; +@@ -506,7 +497,6 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol, + if (mc->w && mc->w->power) + ret = sst_send_gain_cmd(drv, gv, mc->task_id, + mc->pipe_id | mc->instance_id, mc->module_id, 0); +- mutex_unlock(&drv->lock); + + return ret; + } +@@ -521,10 +511,9 @@ static int sst_send_pipe_module_params(struct snd_soc_dapm_widget *w, + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_ids *ids = w->priv; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + sst_find_and_send_pipe_algo(drv, w->name, ids); + sst_set_pipe_gain(ids, drv, 0); +- mutex_unlock(&drv->lock); + + return 0; + } +@@ -761,27 +750,29 @@ int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable) + return ret; + } + +- mutex_lock(&drv->lock); +- if (enable) +- timer_usage++; +- else +- timer_usage--; +- +- /* +- * Send the command only if this call is the first enable or last +- * disable +- */ +- if ((enable && (timer_usage == 1)) || +- (!enable && (timer_usage == 0))) { +- ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD, +- SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd, +- sizeof(cmd.header) + cmd.header.length); +- if (ret && enable) { ++ scoped_guard(mutex, &drv->lock) { ++ if (enable) ++ timer_usage++; ++ else + timer_usage--; +- enable = false; ++ ++ /* ++ * Send the command only if this call is the first enable or last ++ * disable ++ */ ++ if ((enable && timer_usage == 1) || ++ (!enable && timer_usage == 0)) { ++ ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD, ++ SST_FLAG_BLOCKED, ++ SST_TASK_SBA, 0, &cmd, ++ sizeof(cmd.header) + ++ cmd.header.length); ++ if (ret && enable) { ++ timer_usage--; ++ enable = false; ++ } + } + } +- mutex_unlock(&drv->lock); + + if (!enable) + sst->ops->power(sst->dev, false); +diff --git a/sound/soc/intel/atom/sst-mfld-platform-pcm.c b/sound/soc/intel/atom/sst-mfld-platform-pcm.c +--- a/sound/soc/intel/atom/sst-mfld-platform-pcm.c ++++ b/sound/soc/intel/atom/sst-mfld-platform-pcm.c +@@ -11,6 +11,7 @@ + */ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + ++#include + #include + #include + #include +@@ -32,7 +33,7 @@ int sst_register_dsp(struct sst_device *dev) + return -EINVAL; + if (!try_module_get(dev->dev->driver->owner)) + return -ENODEV; +- mutex_lock(&sst_lock); ++ guard(mutex)(&sst_lock); + if (sst) { + dev_err(dev->dev, "we already have a device %s\n", sst->name); + module_put(dev->dev->driver->owner); +@@ -41,7 +42,7 @@ int sst_register_dsp(struct sst_device *dev) + } + dev_dbg(dev->dev, "registering device %s\n", dev->name); + sst = dev; +- mutex_unlock(&sst_lock); ++ + return 0; + } + EXPORT_SYMBOL_GPL(sst_register_dsp); +@@ -53,17 +54,15 @@ int sst_unregister_dsp(struct sst_device *dev) + if (dev != sst) + return -EINVAL; + +- mutex_lock(&sst_lock); ++ guard(mutex)(&sst_lock); + +- if (!sst) { +- mutex_unlock(&sst_lock); ++ if (!sst) + return -EIO; +- } + + module_put(sst->dev->driver->owner); + dev_dbg(dev->dev, "unreg %s\n", sst->name); + sst = NULL; +- mutex_unlock(&sst_lock); ++ + return 0; + } + EXPORT_SYMBOL_GPL(sst_unregister_dsp); +@@ -103,21 +102,14 @@ static int sst_media_digital_mute(struct snd_soc_dai *dai, int mute, int stream) + void sst_set_stream_status(struct sst_runtime_stream *stream, + int state) + { +- unsigned long flags; +- spin_lock_irqsave(&stream->status_lock, flags); ++ guard(spinlock_irqsave)(&stream->status_lock); + stream->stream_status = state; +- spin_unlock_irqrestore(&stream->status_lock, flags); + } + + static inline int sst_get_stream_status(struct sst_runtime_stream *stream) + { +- int state; +- unsigned long flags; +- +- spin_lock_irqsave(&stream->status_lock, flags); +- state = stream->stream_status; +- spin_unlock_irqrestore(&stream->status_lock, flags); +- return state; ++ guard(spinlock_irqsave)(&stream->status_lock); ++ return stream->stream_status; + } + + static void sst_fill_alloc_params(struct snd_pcm_substream *substream, +@@ -244,12 +236,7 @@ static int sst_platform_alloc_stream(struct snd_pcm_substream *substream, + + stream->stream_info.str_id = str_params.stream_id; + +- ret_val = stream->ops->open(sst->dev, &str_params); +- if (ret_val <= 0) +- return ret_val; +- +- +- return ret_val; ++ return stream->ops->open(sst->dev, &str_params); + } + + static void sst_period_elapsed(void *arg) +@@ -304,7 +291,7 @@ static int sst_media_open(struct snd_pcm_substream *substream, + { + int ret_val = 0; + struct snd_pcm_runtime *runtime = substream->runtime; +- struct sst_runtime_stream *stream; ++ struct sst_runtime_stream *stream __free(kfree) = NULL; + + stream = kzalloc_obj(*stream); + if (!stream) +@@ -312,15 +299,14 @@ static int sst_media_open(struct snd_pcm_substream *substream, + spin_lock_init(&stream->status_lock); + + /* get the sst ops */ +- mutex_lock(&sst_lock); +- if (!sst || +- !try_module_get(sst->dev->driver->owner)) { +- dev_err(dai->dev, "no device available to run\n"); +- ret_val = -ENODEV; +- goto out_ops; ++ scoped_guard(mutex, &sst_lock) { ++ if (!sst || ++ !try_module_get(sst->dev->driver->owner)) { ++ dev_err(dai->dev, "no device available to run\n"); ++ return -ENODEV; ++ } ++ stream->ops = sst->ops; + } +- stream->ops = sst->ops; +- mutex_unlock(&sst_lock); + + stream->stream_info.str_id = 0; + +@@ -330,7 +316,7 @@ static int sst_media_open(struct snd_pcm_substream *substream, + + ret_val = power_up_sst(stream); + if (ret_val < 0) +- goto out_power_up; ++ return ret_val; + + /* + * Make sure the period to be multiple of 1ms to align the +@@ -347,12 +333,14 @@ static int sst_media_open(struct snd_pcm_substream *substream, + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIODS, 2); + +- return snd_pcm_hw_constraint_integer(runtime, +- SNDRV_PCM_HW_PARAM_PERIODS); +-out_ops: +- mutex_unlock(&sst_lock); +-out_power_up: +- kfree(stream); ++ ret_val = snd_pcm_hw_constraint_integer(runtime, ++ SNDRV_PCM_HW_PARAM_PERIODS); ++ ++ if (ret_val < 0) ++ return ret_val; ++ ++ stream = NULL; ++ + return ret_val; + } + +diff --git a/sound/soc/intel/atom/sst/sst_ipc.c b/sound/soc/intel/atom/sst/sst_ipc.c +--- a/sound/soc/intel/atom/sst/sst_ipc.c ++++ b/sound/soc/intel/atom/sst/sst_ipc.c +@@ -11,6 +11,7 @@ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ ++#include + #include + #include + #include +@@ -180,9 +181,8 @@ void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) + union interrupt_reg_mrfld isr; + union interrupt_reg_mrfld imr; + union ipc_header_mrfld clear_ipc; +- unsigned long irq_flags; + +- spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); ++ guard(spinlock_irqsave)(&sst_drv_ctx->ipc_spin_lock); + imr.full = sst_shim_read64(sst_drv_ctx->shim, SST_IMRX); + isr.full = sst_shim_read64(sst_drv_ctx->shim, SST_ISRX); + +@@ -200,7 +200,6 @@ void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) + /* un mask busy interrupt */ + imr.part.busy_interrupt = 0; + sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, imr.full); +- spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + } + + +diff --git a/sound/soc/intel/atom/sst/sst_pvt.c b/sound/soc/intel/atom/sst/sst_pvt.c +--- a/sound/soc/intel/atom/sst/sst_pvt.c ++++ b/sound/soc/intel/atom/sst/sst_pvt.c +@@ -11,6 +11,7 @@ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ ++#include + #include + #include + #include +@@ -64,9 +65,8 @@ u64 sst_shim_read64(void __iomem *addr, int offset) + void sst_set_fw_state_locked( + struct intel_sst_drv *sst_drv_ctx, int sst_state) + { +- mutex_lock(&sst_drv_ctx->sst_lock); ++ guard(mutex)(&sst_drv_ctx->sst_lock); + sst_drv_ctx->sst_state = sst_state; +- mutex_unlock(&sst_drv_ctx->sst_lock); + } + + /* +@@ -302,18 +302,17 @@ int sst_assign_pvt_id(struct intel_sst_drv *drv) + { + int local; + +- spin_lock(&drv->block_lock); ++ guard(spinlock)(&drv->block_lock); + /* find first zero index from lsb */ + local = ffz(drv->pvt_id); + dev_dbg(drv->dev, "pvt_id assigned --> %d\n", local); + if (local >= SST_MAX_BLOCKS){ +- spin_unlock(&drv->block_lock); + dev_err(drv->dev, "PVT _ID error: no free id blocks "); + return -EINVAL; + } + /* toggle the index */ + change_bit(local, &drv->pvt_id); +- spin_unlock(&drv->block_lock); ++ + return local; + } + +diff --git a/sound/soc/intel/avs/apl.c b/sound/soc/intel/avs/apl.c +--- a/sound/soc/intel/avs/apl.c ++++ b/sound/soc/intel/avs/apl.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -190,7 +191,7 @@ static bool avs_apl_lp_streaming(struct avs_dev *adev) + { + struct avs_path *path; + +- spin_lock(&adev->path_list_lock); ++ guard(spinlock)(&adev->path_list_lock); + /* Any gateway without buffer allocated in LP area disqualifies D0IX. */ + list_for_each_entry(path, &adev->path_list, node) { + struct avs_path_pipeline *ppl; +@@ -210,14 +211,11 @@ static bool avs_apl_lp_streaming(struct avs_dev *adev) + if (cfg->copier.dma_type == INVALID_OBJECT_ID) + continue; + +- if (!mod->gtw_attrs.lp_buffer_alloc) { +- spin_unlock(&adev->path_list_lock); ++ if (!mod->gtw_attrs.lp_buffer_alloc) + return false; +- } + } + } + } +- spin_unlock(&adev->path_list_lock); + + return true; + } +diff --git a/sound/soc/intel/avs/avs.h b/sound/soc/intel/avs/avs.h +--- a/sound/soc/intel/avs/avs.h ++++ b/sound/soc/intel/avs/avs.h +@@ -336,14 +336,13 @@ int avs_icl_load_basefw(struct avs_dev *adev, struct firmware *fw); + /* Soc component members */ + + struct avs_soc_component { +- struct snd_soc_component base; ++ struct snd_soc_component *base; + struct avs_tplg *tplg; + + struct list_head node; + }; + +-#define to_avs_soc_component(comp) \ +- container_of(comp, struct avs_soc_component, base) ++#define to_avs_soc_component(comp) snd_soc_component_to_priv(comp) + + extern const struct snd_soc_dai_ops avs_dai_fe_ops; + +diff --git a/sound/soc/intel/avs/boards/da7219.c b/sound/soc/intel/avs/boards/da7219.c +--- a/sound/soc/intel/avs/boards/da7219.c ++++ b/sound/soc/intel/avs/boards/da7219.c +@@ -175,7 +175,6 @@ static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + if (!dl || !platform) + return -ENOMEM; + +- dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); +@@ -259,10 +258,8 @@ static int avs_da7219_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_da7219_driver_ids[] = { +- { +- .name = "avs_da7219", +- }, +- {}, ++ { .name = "avs_da7219" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_da7219_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/dmic.c b/sound/soc/intel/avs/boards/dmic.c +--- a/sound/soc/intel/avs/boards/dmic.c ++++ b/sound/soc/intel/avs/boards/dmic.c +@@ -104,10 +104,8 @@ static int avs_dmic_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_dmic_driver_ids[] = { +- { +- .name = "avs_dmic", +- }, +- {}, ++ { .name = "avs_dmic" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_dmic_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/es8336.c b/sound/soc/intel/avs/boards/es8336.c +--- a/sound/soc/intel/avs/boards/es8336.c ++++ b/sound/soc/intel/avs/boards/es8336.c +@@ -309,10 +309,8 @@ static int avs_es8336_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_es8336_driver_ids[] = { +- { +- .name = "avs_es8336", +- }, +- {}, ++ { .name = "avs_es8336" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_es8336_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c +--- a/sound/soc/intel/avs/boards/hdaudio.c ++++ b/sound/soc/intel/avs/boards/hdaudio.c +@@ -231,10 +231,8 @@ static int avs_hdaudio_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_hdaudio_driver_ids[] = { +- { +- .name = "avs_hdaudio", +- }, +- {}, ++ { .name = "avs_hdaudio" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_hdaudio_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/i2s_test.c b/sound/soc/intel/avs/boards/i2s_test.c +--- a/sound/soc/intel/avs/boards/i2s_test.c ++++ b/sound/soc/intel/avs/boards/i2s_test.c +@@ -107,10 +107,8 @@ static int avs_i2s_test_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_i2s_test_driver_ids[] = { +- { +- .name = "avs_i2s_test", +- }, +- {}, ++ { .name = "avs_i2s_test" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_i2s_test_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/max98357a.c b/sound/soc/intel/avs/boards/max98357a.c +--- a/sound/soc/intel/avs/boards/max98357a.c ++++ b/sound/soc/intel/avs/boards/max98357a.c +@@ -136,10 +136,8 @@ static int avs_max98357a_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_max98357a_driver_ids[] = { +- { +- .name = "avs_max98357a", +- }, +- {}, ++ { .name = "avs_max98357a" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_max98357a_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/max98373.c b/sound/soc/intel/avs/boards/max98373.c +--- a/sound/soc/intel/avs/boards/max98373.c ++++ b/sound/soc/intel/avs/boards/max98373.c +@@ -191,10 +191,8 @@ static int avs_max98373_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_max98373_driver_ids[] = { +- { +- .name = "avs_max98373", +- }, +- {}, ++ { .name = "avs_max98373" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_max98373_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/max98927.c b/sound/soc/intel/avs/boards/max98927.c +--- a/sound/soc/intel/avs/boards/max98927.c ++++ b/sound/soc/intel/avs/boards/max98927.c +@@ -188,10 +188,8 @@ static int avs_max98927_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_max98927_driver_ids[] = { +- { +- .name = "avs_max98927", +- }, +- {}, ++ { .name = "avs_max98927" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_max98927_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/nau8825.c b/sound/soc/intel/avs/boards/nau8825.c +--- a/sound/soc/intel/avs/boards/nau8825.c ++++ b/sound/soc/intel/avs/boards/nau8825.c +@@ -293,10 +293,8 @@ static int avs_nau8825_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_nau8825_driver_ids[] = { +- { +- .name = "avs_nau8825", +- }, +- {}, ++ { .name = "avs_nau8825" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_nau8825_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/pcm3168a.c b/sound/soc/intel/avs/boards/pcm3168a.c +--- a/sound/soc/intel/avs/boards/pcm3168a.c ++++ b/sound/soc/intel/avs/boards/pcm3168a.c +@@ -132,10 +132,8 @@ static int avs_pcm3168a_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_pcm3168a_driver_ids[] = { +- { +- .name = "avs_pcm3168a", +- }, +- {}, ++ { .name = "avs_pcm3168a" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_pcm3168a_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/probe.c b/sound/soc/intel/avs/boards/probe.c +--- a/sound/soc/intel/avs/boards/probe.c ++++ b/sound/soc/intel/avs/boards/probe.c +@@ -64,7 +64,7 @@ static const struct platform_device_id avs_probe_mb_driver_ids[] = { + { + .name = "avs_probe_mb", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_probe_mb_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt274.c b/sound/soc/intel/avs/boards/rt274.c +--- a/sound/soc/intel/avs/boards/rt274.c ++++ b/sound/soc/intel/avs/boards/rt274.c +@@ -261,7 +261,7 @@ static const struct platform_device_id avs_rt274_driver_ids[] = { + { + .name = "avs_rt274", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt274_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt286.c b/sound/soc/intel/avs/boards/rt286.c +--- a/sound/soc/intel/avs/boards/rt286.c ++++ b/sound/soc/intel/avs/boards/rt286.c +@@ -231,7 +231,7 @@ static const struct platform_device_id avs_rt286_driver_ids[] = { + { + .name = "avs_rt286", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt286_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt298.c b/sound/soc/intel/avs/boards/rt298.c +--- a/sound/soc/intel/avs/boards/rt298.c ++++ b/sound/soc/intel/avs/boards/rt298.c +@@ -250,7 +250,7 @@ static const struct platform_device_id avs_rt298_driver_ids[] = { + { + .name = "avs_rt298", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt298_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5514.c b/sound/soc/intel/avs/boards/rt5514.c +--- a/sound/soc/intel/avs/boards/rt5514.c ++++ b/sound/soc/intel/avs/boards/rt5514.c +@@ -178,7 +178,7 @@ static const struct platform_device_id avs_rt5514_driver_ids[] = { + { + .name = "avs_rt5514", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5514_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5640.c b/sound/soc/intel/avs/boards/rt5640.c +--- a/sound/soc/intel/avs/boards/rt5640.c ++++ b/sound/soc/intel/avs/boards/rt5640.c +@@ -252,7 +252,7 @@ static const struct platform_device_id avs_rt5640_driver_ids[] = { + { + .name = "avs_rt5640", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5640_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5663.c b/sound/soc/intel/avs/boards/rt5663.c +--- a/sound/soc/intel/avs/boards/rt5663.c ++++ b/sound/soc/intel/avs/boards/rt5663.c +@@ -249,7 +249,7 @@ static const struct platform_device_id avs_rt5663_driver_ids[] = { + { + .name = "avs_rt5663", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5663_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5682.c b/sound/soc/intel/avs/boards/rt5682.c +--- a/sound/soc/intel/avs/boards/rt5682.c ++++ b/sound/soc/intel/avs/boards/rt5682.c +@@ -325,7 +325,7 @@ static const struct platform_device_id avs_rt5682_driver_ids[] = { + { + .name = "avs_rt5682", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5682_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/ssm4567.c b/sound/soc/intel/avs/boards/ssm4567.c +--- a/sound/soc/intel/avs/boards/ssm4567.c ++++ b/sound/soc/intel/avs/boards/ssm4567.c +@@ -180,7 +180,7 @@ static const struct platform_device_id avs_ssm4567_driver_ids[] = { + { + .name = "avs_ssm4567", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_ssm4567_driver_ids); + +diff --git a/sound/soc/intel/avs/control.c b/sound/soc/intel/avs/control.c +--- a/sound/soc/intel/avs/control.c ++++ b/sound/soc/intel/avs/control.c +@@ -27,7 +27,7 @@ static struct avs_path_module *avs_get_volume_module(struct avs_dev *adev, u32 i + struct avs_path_pipeline *ppl; + struct avs_path_module *mod; + +- spin_lock(&adev->path_list_lock); ++ guard(spinlock)(&adev->path_list_lock); + list_for_each_entry(path, &adev->path_list, node) { + list_for_each_entry(ppl, &path->ppl_list, node) { + list_for_each_entry(mod, &ppl->mod_list, node) { +@@ -35,14 +35,11 @@ static struct avs_path_module *avs_get_volume_module(struct avs_dev *adev, u32 i + + if ((guid_equal(type, &AVS_PEAKVOL_MOD_UUID) || + guid_equal(type, &AVS_GAIN_MOD_UUID)) && +- mod->template->ctl_id == id) { +- spin_unlock(&adev->path_list_lock); ++ mod->template->ctl_id == id) + return mod; +- } + } + } + } +- spin_unlock(&adev->path_list_lock); + + return NULL; + } +diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c +--- a/sound/soc/intel/avs/core.c ++++ b/sound/soc/intel/avs/core.c +@@ -14,6 +14,7 @@ + // foundation of this driver + // + ++#include + #include + #include + #include +@@ -273,7 +274,7 @@ static irqreturn_t avs_hda_interrupt(struct hdac_bus *bus) + if (snd_hdac_bus_handle_stream_irq(bus, status, hdac_update_stream)) + ret = IRQ_HANDLED; + +- spin_lock_irq(&bus->reg_lock); ++ guard(spinlock_irq)(&bus->reg_lock); + /* Clear RIRB interrupt. */ + status = snd_hdac_chip_readb(bus, RIRBSTS); + if (status & RIRB_INT_MASK) { +@@ -283,7 +284,6 @@ static irqreturn_t avs_hda_interrupt(struct hdac_bus *bus) + ret = IRQ_HANDLED; + } + +- spin_unlock_irq(&bus->reg_lock); + return ret; + } + +diff --git a/sound/soc/intel/avs/debug.h b/sound/soc/intel/avs/debug.h +--- a/sound/soc/intel/avs/debug.h ++++ b/sound/soc/intel/avs/debug.h +@@ -9,6 +9,7 @@ + #ifndef __SOUND_SOC_INTEL_AVS_DEBUG_H + #define __SOUND_SOC_INTEL_AVS_DEBUG_H + ++#include + #include "messages.h" + #include "registers.h" + +@@ -26,14 +27,9 @@ struct avs_dev; + + static inline int avs_log_buffer_status_locked(struct avs_dev *adev, union avs_notify_msg *msg) + { +- unsigned long flags; +- int ret; ++ guard(spinlock_irqsave)(&adev->trace_lock); + +- spin_lock_irqsave(&adev->trace_lock, flags); +- ret = avs_dsp_op(adev, log_buffer_status, msg); +- spin_unlock_irqrestore(&adev->trace_lock, flags); +- +- return ret; ++ return avs_dsp_op(adev, log_buffer_status, msg); + } + + struct avs_apl_log_buffer_layout { +diff --git a/sound/soc/intel/avs/debugfs.c b/sound/soc/intel/avs/debugfs.c +--- a/sound/soc/intel/avs/debugfs.c ++++ b/sound/soc/intel/avs/debugfs.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -251,24 +252,22 @@ static int strace_release(struct inode *inode, struct file *file) + union avs_notify_msg msg = AVS_NOTIFICATION(LOG_BUFFER_STATUS); + struct avs_dev *adev = file->private_data; + unsigned long resource_mask; +- unsigned long flags, i; ++ unsigned long i; + u32 num_cores; + + resource_mask = adev->logged_resources; + num_cores = adev->hw_cfg.dsp_cores; + +- spin_lock_irqsave(&adev->trace_lock, flags); ++ scoped_guard(spinlock_irqsave, &adev->trace_lock) { ++ /* Gather any remaining logs. */ ++ for_each_set_bit(i, &resource_mask, num_cores) { ++ msg.log.core = i; ++ avs_dsp_op(adev, log_buffer_status, &msg); ++ } + +- /* Gather any remaining logs. */ +- for_each_set_bit(i, &resource_mask, num_cores) { +- msg.log.core = i; +- avs_dsp_op(adev, log_buffer_status, &msg); ++ kfifo_free(&adev->trace_fifo); + } + +- kfifo_free(&adev->trace_fifo); +- +- spin_unlock_irqrestore(&adev->trace_lock, flags); +- + module_put(adev->dev->driver->owner); + return 0; + } +diff --git a/sound/soc/intel/avs/ipc.c b/sound/soc/intel/avs/ipc.c +--- a/sound/soc/intel/avs/ipc.c ++++ b/sound/soc/intel/avs/ipc.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -99,39 +100,39 @@ static void avs_dsp_recovery(struct avs_dev *adev) + unsigned int core_mask; + int ret; + +- mutex_lock(&adev->comp_list_mutex); +- /* disconnect all running streams */ +- list_for_each_entry(acomp, &adev->comp_list, node) { +- struct snd_soc_pcm_runtime *rtd; +- struct snd_soc_card *card; ++ scoped_guard(mutex, &adev->comp_list_mutex) { ++ /* disconnect all running streams */ ++ list_for_each_entry(acomp, &adev->comp_list, node) { ++ struct snd_soc_pcm_runtime *rtd; ++ struct snd_soc_card *card; + +- card = acomp->base.card; +- if (!card) +- continue; +- +- for_each_card_rtds(card, rtd) { +- struct snd_pcm *pcm; +- int dir; +- +- pcm = rtd->pcm; +- if (!pcm || rtd->dai_link->no_pcm) ++ card = acomp->base->card; ++ if (!card) + continue; + +- for_each_pcm_streams(dir) { +- struct snd_pcm_substream *substream; ++ for_each_card_rtds(card, rtd) { ++ struct snd_pcm *pcm; ++ int dir; + +- substream = pcm->streams[dir].substream; +- if (!substream || !substream->runtime) ++ pcm = rtd->pcm; ++ if (!pcm || rtd->dai_link->no_pcm) + continue; + +- /* No need for _irq() as we are in nonatomic context. */ +- snd_pcm_stream_lock(substream); +- snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); +- snd_pcm_stream_unlock(substream); ++ for_each_pcm_streams(dir) { ++ struct snd_pcm_substream *substream; ++ ++ substream = pcm->streams[dir].substream; ++ if (!substream || !substream->runtime) ++ continue; ++ ++ /* No need for _irq() as we are in nonatomic context. */ ++ snd_pcm_stream_lock(substream); ++ snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); ++ snd_pcm_stream_unlock(substream); ++ } + } + } + } +- mutex_unlock(&adev->comp_list_mutex); + + /* forcibly shutdown all cores */ + core_mask = GENMASK(adev->hw_cfg.dsp_cores - 1, 0); +@@ -397,7 +398,7 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + if (!ipc->ready) + return -EPERM; + +- mutex_lock(&ipc->msg_mutex); ++ guard(mutex)(&ipc->msg_mutex); + + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, reply); +@@ -412,7 +413,7 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + /* Same treatment as on exception, just stack_dump=0. */ + avs_dsp_exception_caught(adev, &msg); + } +- goto exit; ++ return ret; + } + + ret = ipc->rx.rsp.status; +@@ -436,8 +437,6 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + memcpy(reply->data, ipc->rx.data, reply->size); + } + +-exit: +- mutex_unlock(&ipc->msg_mutex); + return ret; + } + +@@ -501,7 +500,7 @@ static int avs_dsp_do_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *req + struct avs_ipc *ipc = adev->ipc; + int ret; + +- mutex_lock(&ipc->msg_mutex); ++ guard(mutex)(&ipc->msg_mutex); + + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, NULL); +@@ -522,8 +521,6 @@ static int avs_dsp_do_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *req + dev_err(adev->dev, "%s (0x%08x 0x%08x) failed: %d\n", + name, request->glb.primary, request->glb.ext.val, ret); + +- mutex_unlock(&ipc->msg_mutex); +- + return ret; + } + +diff --git a/sound/soc/intel/avs/loader.c b/sound/soc/intel/avs/loader.c +--- a/sound/soc/intel/avs/loader.c ++++ b/sound/soc/intel/avs/loader.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -630,15 +631,15 @@ static int avs_load_firmware(struct avs_dev *adev, bool purge) + if (ret) + goto reenable_gating; + +- mutex_lock(&adev->comp_list_mutex); +- list_for_each_entry(acomp, &adev->comp_list, node) { +- struct avs_tplg *tplg = acomp->tplg; ++ scoped_guard(mutex, &adev->comp_list_mutex) { ++ list_for_each_entry(acomp, &adev->comp_list, node) { ++ struct avs_tplg *tplg = acomp->tplg; + +- ret = avs_dsp_load_libraries(adev, tplg->libs, tplg->num_libs); +- if (ret < 0) +- break; ++ ret = avs_dsp_load_libraries(adev, tplg->libs, tplg->num_libs); ++ if (ret < 0) ++ break; ++ } + } +- mutex_unlock(&adev->comp_list_mutex); + + reenable_gating: + avs_hda_l1sen_enable(adev, true); +diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c +--- a/sound/soc/intel/avs/path.c ++++ b/sound/soc/intel/avs/path.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -69,16 +70,13 @@ avs_path_find_path(struct avs_dev *adev, const char *name, u32 template_id) + if (!template) + return NULL; + +- spin_lock(&adev->path_list_lock); ++ guard(spinlock)(&adev->path_list_lock); + /* Only one variant of given path template may be instantiated at a time. */ + list_for_each_entry(path, &adev->path_list, node) { +- if (path->template->owner == template) { +- spin_unlock(&adev->path_list_lock); ++ if (path->template->owner == template) + return path; +- } + } + +- spin_unlock(&adev->path_list_lock); + return NULL; + } + +@@ -1305,7 +1303,7 @@ void avs_path_free(struct avs_path *path) + struct avs_path *cpath, *csave; + struct avs_dev *adev = path->owner; + +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + + /* Free all condpaths this path spawned. */ + list_for_each_entry_safe(cpath, csave, &path->source_list, source_node) +@@ -1314,8 +1312,6 @@ void avs_path_free(struct avs_path *path) + avs_condpath_free(path->owner, cpath); + + avs_path_free_unlocked(path); +- +- mutex_unlock(&adev->path_mutex); + } + + struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, +@@ -1334,13 +1330,13 @@ struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, + } + + /* Serialize path and its components creation. */ +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + /* Satisfy needs of avs_path_find_tplg(). */ +- mutex_lock(&adev->comp_list_mutex); ++ guard(mutex)(&adev->comp_list_mutex); + + path = avs_path_create_unlocked(adev, dma_id, variant); + if (IS_ERR(path)) +- goto exit; ++ return path; + + ret = avs_condpaths_walk_all(adev, path); + if (ret) { +@@ -1348,10 +1344,6 @@ struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, + path = ERR_PTR(ret); + } + +-exit: +- mutex_unlock(&adev->comp_list_mutex); +- mutex_unlock(&adev->path_mutex); +- + return path; + } + +@@ -1496,15 +1488,13 @@ static void avs_condpaths_pause(struct avs_dev *adev, struct avs_path *path) + { + struct avs_path *cpath; + +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + + /* If either source or sink stops, so do the attached conditional paths. */ + list_for_each_entry(cpath, &path->source_list, source_node) + avs_condpath_pause(adev, cpath); + list_for_each_entry(cpath, &path->sink_list, sink_node) + avs_condpath_pause(adev, cpath); +- +- mutex_unlock(&adev->path_mutex); + } + + int avs_path_pause(struct avs_path *path) +@@ -1560,7 +1550,7 @@ static void avs_condpaths_run(struct avs_dev *adev, struct avs_path *path, int t + { + struct avs_path *cpath; + +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + + /* Run conditional paths only if source and sink are both running. */ + list_for_each_entry(cpath, &path->source_list, source_node) +@@ -1572,8 +1562,6 @@ static void avs_condpaths_run(struct avs_dev *adev, struct avs_path *path, int t + if (cpath->source->state == AVS_PPL_STATE_RUNNING && + cpath->sink->state == AVS_PPL_STATE_RUNNING) + avs_condpath_run(adev, cpath, trigger); +- +- mutex_unlock(&adev->path_mutex); + } + + int avs_path_run(struct avs_path *path, int trigger) +diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c +--- a/sound/soc/intel/avs/pcm.c ++++ b/sound/soc/intel/avs/pcm.c +@@ -958,7 +958,7 @@ static const struct file_operations topology_name_fops = { + static int avs_component_load_libraries(struct avs_soc_component *acomp) + { + struct avs_tplg *tplg = acomp->tplg; +- struct avs_dev *adev = to_avs_dev(acomp->base.dev); ++ struct avs_dev *adev = to_avs_dev(acomp->base->dev); + int ret; + + if (!tplg->num_libs) +@@ -1387,25 +1387,28 @@ int avs_register_component(struct device *dev, const char *name, + struct snd_soc_dai_driver *cpu_dais, int num_cpu_dais) + { + struct avs_soc_component *acomp; +- int ret; ++ const char *comp_name; + + acomp = devm_kzalloc(dev, sizeof(*acomp), GFP_KERNEL); + if (!acomp) + return -ENOMEM; + +- acomp->base.name = devm_kstrdup(dev, name, GFP_KERNEL); +- if (!acomp->base.name) ++ acomp->base = snd_soc_component_alloc(dev); ++ if (!acomp->base) ++ return -ENOMEM; ++ ++ comp_name = devm_kstrdup(dev, name, GFP_KERNEL); ++ if (!comp_name) + return -ENOMEM; + + INIT_LIST_HEAD(&acomp->node); + + drv->use_dai_pcm_id = !obsolete_card_names; + +- ret = snd_soc_component_initialize(&acomp->base, drv, dev); +- if (ret < 0) +- return ret; ++ snd_soc_component_set_name(acomp->base, comp_name); ++ snd_soc_component_set_priv(acomp->base, acomp); + +- return snd_soc_add_component(&acomp->base, cpu_dais, num_cpu_dais); ++ return snd_soc_register_component(acomp->base, drv, cpu_dais, num_cpu_dais); + } + + static struct snd_soc_dai_driver dmic_cpu_dais[] = { +diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c +--- a/sound/soc/intel/avs/probes.c ++++ b/sound/soc/intel/avs/probes.c +@@ -144,7 +144,7 @@ static int avs_probe_compr_set_params(struct snd_compr_stream *cstream, + ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); + if (ret < 0) + return ret; +- bps = snd_pcm_format_physical_width((__force snd_pcm_format_t)params->codec.format); ++ bps = snd_pcm_format_physical_width(params->codec.format); + if (bps < 0) + return bps; + format_val = snd_hdac_stream_format(params->codec.ch_out, bps, params->codec.sample_rate); +@@ -296,19 +296,19 @@ static const struct snd_soc_component_driver avs_probe_component_driver = { + int avs_register_probe_component(struct avs_dev *adev, const char *name) + { + struct snd_soc_component *component; +- int ret; ++ const char *comp_name; + +- component = devm_kzalloc(adev->dev, sizeof(*component), GFP_KERNEL); ++ component = snd_soc_component_alloc(adev->dev); + if (!component) + return -ENOMEM; + +- component->name = devm_kstrdup(adev->dev, name, GFP_KERNEL); +- if (!component->name) ++ comp_name = devm_kstrdup(adev->dev, name, GFP_KERNEL); ++ if (!comp_name) + return -ENOMEM; + +- ret = snd_soc_component_initialize(component, &avs_probe_component_driver, adev->dev); +- if (ret) +- return ret; ++ snd_soc_component_set_name(component, comp_name); + +- return snd_soc_add_component(component, probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); ++ return snd_soc_register_component(component, ++ &avs_probe_component_driver, ++ probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); + } +diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c +--- a/sound/soc/intel/avs/topology.c ++++ b/sound/soc/intel/avs/topology.c +@@ -2222,7 +2222,7 @@ struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp) + + int avs_load_topology(struct snd_soc_component *comp, const char *filename) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw, filename, comp->dev); +@@ -2235,7 +2235,6 @@ int avs_load_topology(struct snd_soc_component *comp, const char *filename) + if (ret < 0) + dev_err(comp->dev, "load topology \"%s\" failed: %d\n", filename, ret); + +- release_firmware(fw); + return ret; + } + +diff --git a/sound/soc/intel/avs/utils.c b/sound/soc/intel/avs/utils.c +--- a/sound/soc/intel/avs/utils.c ++++ b/sound/soc/intel/avs/utils.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -48,13 +49,12 @@ int avs_get_module_entry(struct avs_dev *adev, const guid_t *uuid, struct avs_mo + { + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_entry_index(adev, uuid); + if (idx >= 0) + memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); + +- mutex_unlock(&adev->modres_mutex); + return (idx < 0) ? idx : 0; + } + +@@ -62,13 +62,12 @@ int avs_get_module_id_entry(struct avs_dev *adev, u32 module_id, struct avs_modu + { + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx >= 0) + memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); + +- mutex_unlock(&adev->modres_mutex); + return (idx < 0) ? idx : 0; + } + +@@ -86,13 +85,12 @@ bool avs_is_module_ida_empty(struct avs_dev *adev, u32 module_id) + bool ret = false; + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx >= 0) + ret = ida_is_empty(adev->mod_idas[idx]); + +- mutex_unlock(&adev->modres_mutex); + return ret; + } + +@@ -163,68 +161,57 @@ int avs_module_info_init(struct avs_dev *adev, bool purge) + if (ret) + return AVS_IPC_RET(ret); + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + ret = avs_module_ida_alloc(adev, info, purge); + if (ret < 0) { + dev_err(adev->dev, "initialize module idas failed: %d\n", ret); +- goto exit; ++ return ret; + } + + /* Refresh current information with newly received table. */ + kfree(adev->mods_info); + adev->mods_info = info; + +-exit: +- mutex_unlock(&adev->modres_mutex); + return ret; + } + + void avs_module_info_free(struct avs_dev *adev) + { +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + avs_module_ida_destroy(adev); + kfree(adev->mods_info); + adev->mods_info = NULL; +- +- mutex_unlock(&adev->modres_mutex); + } + + int avs_module_id_alloc(struct avs_dev *adev, u16 module_id) + { +- int ret, idx, max_id; ++ int idx, max_id; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx == -ENOENT) { + dev_err(adev->dev, "invalid module id: %d", module_id); +- ret = -EINVAL; +- goto exit; ++ return -EINVAL; + } + max_id = adev->mods_info->entries[idx].instance_max_count - 1; +- ret = ida_alloc_max(adev->mod_idas[idx], max_id, GFP_KERNEL); +-exit: +- mutex_unlock(&adev->modres_mutex); +- return ret; ++ ++ return ida_alloc_max(adev->mod_idas[idx], max_id, GFP_KERNEL); + } + + void avs_module_id_free(struct avs_dev *adev, u16 module_id, u8 instance_id) + { + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); +- if (idx == -ENOENT) { ++ if (idx == -ENOENT) + dev_err(adev->dev, "invalid module id: %d", module_id); +- goto exit; +- } +- +- ida_free(adev->mod_idas[idx], instance_id); +-exit: +- mutex_unlock(&adev->modres_mutex); ++ else ++ ida_free(adev->mod_idas[idx], instance_id); + } + + /* +diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c +--- a/sound/soc/intel/boards/sof_sdw.c ++++ b/sound/soc/intel/boards/sof_sdw.c +@@ -1288,7 +1288,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) + goto err_dai; + } + +- ret = asoc_sdw_parse_sdw_endpoints(card, sof_aux, sof_dais, sof_ends, &num_confs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); + if (ret < 0) + goto err_end; + +@@ -1483,12 +1483,12 @@ static int mc_probe(struct platform_device *pdev) + dmi_check_system(sof_sdw_quirk_table); + + if (quirk_override != -1) { +- dev_info(card->dev, "Overriding quirk 0x%lx => 0x%x\n", ++ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + sof_sdw_quirk, quirk_override); + sof_sdw_quirk = quirk_override; + } + +- log_quirks(card->dev); ++ log_quirks(&pdev->dev); + + ctx->mc_quirk = sof_sdw_quirk; + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ +@@ -1507,13 +1507,13 @@ static int mc_probe(struct platform_device *pdev) + for (i = 0; i < ctx->codec_info_list_count; i++) + amp_num += codec_info_list[i].amp_num; + +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + " cfg-amp:%d", amp_num); + if (!card->components) + return -ENOMEM; + + if (mach->mach_params.dmic_num) { +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%s mic:dmic cfg-mics:%d", + card->components, + mach->mach_params.dmic_num); +@@ -1524,7 +1524,7 @@ static int mc_probe(struct platform_device *pdev) + /* Register the card */ + ret = devm_snd_soc_register_card(card->dev, card); + if (ret) { +- dev_err_probe(card->dev, ret, "snd_soc_register_card failed %d\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed %d\n", ret); + asoc_sdw_mc_dailink_exit_loop(card); + return ret; + } +@@ -1542,8 +1542,8 @@ static void mc_remove(struct platform_device *pdev) + } + + static const struct platform_device_id mc_id_table[] = { +- { "sof_sdw", }, +- {} ++ { .name = "sof_sdw" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, mc_id_table); + +diff --git a/sound/soc/intel/catpt/core.h b/sound/soc/intel/catpt/core.h +--- a/sound/soc/intel/catpt/core.h ++++ b/sound/soc/intel/catpt/core.h +@@ -139,9 +139,7 @@ int catpt_dsp_send_msg(struct catpt_dev *cdev, struct catpt_ipc_msg request, + + int catpt_first_boot_firmware(struct catpt_dev *cdev); + int catpt_boot_firmware(struct catpt_dev *cdev, bool restore); +-int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan); +-int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan); +-int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan); ++int catpt_store_firmware_context(struct catpt_dev *cdev); + int catpt_coredump(struct catpt_dev *cdev); + + #include +diff --git a/sound/soc/intel/catpt/device.c b/sound/soc/intel/catpt/device.c +--- a/sound/soc/intel/catpt/device.c ++++ b/sound/soc/intel/catpt/device.c +@@ -28,44 +28,17 @@ + static int catpt_do_suspend(struct device *dev) + { + struct catpt_dev *cdev = dev_get_drvdata(dev); +- struct dma_chan *chan; + int ret; + +- chan = catpt_dma_request_config_chan(cdev); +- if (IS_ERR(chan)) +- return PTR_ERR(chan); +- + memset(&cdev->dx_ctx, 0, sizeof(cdev->dx_ctx)); + ret = catpt_ipc_enter_dxstate(cdev, CATPT_DX_STATE_D3, &cdev->dx_ctx); +- if (ret) { +- ret = CATPT_IPC_RET(ret); +- goto release_dma_chan; +- } +- +- ret = catpt_dsp_stall(cdev, true); + if (ret) +- goto release_dma_chan; ++ return CATPT_IPC_RET(ret); + +- ret = catpt_store_memdumps(cdev, chan); +- if (ret) { +- dev_err(cdev->dev, "store memdumps failed: %d\n", ret); +- goto release_dma_chan; +- } +- +- ret = catpt_store_module_states(cdev, chan); +- if (ret) { +- dev_err(cdev->dev, "store module states failed: %d\n", ret); +- goto release_dma_chan; +- } +- +- ret = catpt_store_streams_context(cdev, chan); +- if (ret) +- dev_err(cdev->dev, "store streams ctx failed: %d\n", ret); +- +-release_dma_chan: +- dma_release_channel(chan); ++ ret = catpt_store_firmware_context(cdev); + if (ret) + return ret; ++ + return catpt_dsp_power_down(cdev); + } + +diff --git a/sound/soc/intel/catpt/loader.c b/sound/soc/intel/catpt/loader.c +--- a/sound/soc/intel/catpt/loader.c ++++ b/sound/soc/intel/catpt/loader.c +@@ -26,7 +26,7 @@ struct catpt_fw_hdr { + u32 reserved[4]; + } __packed; + +-struct catpt_fw_mod_hdr { ++struct catpt_fw_module_hdr { + char signature[FW_SIGNATURE_SIZE]; + u32 mod_size; + u32 blocks; +@@ -81,7 +81,7 @@ catpt_request_region(struct resource *root, resource_size_t size) + return __request_region(root, addr, size, NULL, 0); + } + +-int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + { + struct catpt_stream_runtime *stream; + +@@ -108,7 +108,7 @@ int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) + { + int i; + +@@ -138,7 +138,7 @@ int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_store_dram_data(struct catpt_dev *cdev, struct dma_chan *chan) + { + int i; + +@@ -171,6 +171,39 @@ int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + ++int catpt_store_firmware_context(struct catpt_dev *cdev) ++{ ++ struct dma_chan *chan; ++ int ret; ++ ++ chan = catpt_dma_request_config_chan(cdev); ++ if (IS_ERR(chan)) ++ return PTR_ERR(chan); ++ ++ ret = catpt_dsp_stall(cdev, true); ++ if (ret) ++ goto exit; ++ ++ ret = catpt_store_dram_data(cdev, chan); ++ if (ret) { ++ dev_err(cdev->dev, "store memdumps failed: %d\n", ret); ++ goto exit; ++ } ++ ++ ret = catpt_store_module_states(cdev, chan); ++ if (ret) { ++ dev_err(cdev->dev, "store module states failed: %d\n", ret); ++ goto exit; ++ } ++ ++ ret = catpt_store_streams_context(cdev, chan); ++ if (ret) ++ dev_err(cdev->dev, "store streams ctx failed: %d\n", ret); ++exit: ++ dma_release_channel(chan); ++ return ret; ++} ++ + static int + catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + { +@@ -199,7 +232,7 @@ catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-static int catpt_restore_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_restore_dram_data(struct catpt_dev *cdev, struct dma_chan *chan) + { + int i; + +@@ -234,9 +267,9 @@ static int catpt_restore_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-static int catpt_restore_fwimage(struct catpt_dev *cdev, +- struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_block_hdr *blk) ++static int catpt_restore_dram_rodata(struct catpt_dev *cdev, ++ struct dma_chan *chan, dma_addr_t paddr, ++ struct catpt_fw_block_hdr *blk) + { + struct resource r1 = {}; + int i; +@@ -324,28 +357,26 @@ static int catpt_load_block(struct catpt_dev *cdev, + + static int catpt_restore_basefw(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_mod_hdr *basefw) ++ struct catpt_fw_module_hdr *basefw) + { +- u32 offset = sizeof(*basefw); ++ u32 off = sizeof(*basefw); + int ret, i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + basefw, sizeof(*basefw), false); + +- /* restore basefw image */ ++ /* Restore IRAM and .rodata for DRAM based on the firmware image. */ + for (i = 0; i < basefw->blocks; i++) { + struct catpt_fw_block_hdr *blk; + +- blk = (struct catpt_fw_block_hdr *)((u8 *)basefw + offset); ++ blk = (struct catpt_fw_block_hdr *)((u8 *)basefw + off); + + switch (blk->ram_type) { + case CATPT_RAM_TYPE_IRAM: +- ret = catpt_load_block(cdev, chan, paddr + offset, +- blk, false); ++ ret = catpt_load_block(cdev, chan, paddr + off, blk, false); + break; + default: +- ret = catpt_restore_fwimage(cdev, chan, paddr + offset, +- blk); ++ ret = catpt_restore_dram_rodata(cdev, chan, paddr + off, blk); + break; + } + +@@ -354,11 +385,11 @@ static int catpt_restore_basefw(struct catpt_dev *cdev, + return ret; + } + +- offset += sizeof(*blk) + blk->size; ++ off += sizeof(*blk) + blk->size; + } + +- /* then proceed with memory dumps */ +- ret = catpt_restore_memdumps(cdev, chan); ++ /* Then proceed with DRAM .data saved before D3. */ ++ ret = catpt_restore_dram_data(cdev, chan); + if (ret) + dev_err(cdev->dev, "restore memdumps failed: %d\n", ret); + +@@ -367,9 +398,9 @@ static int catpt_restore_basefw(struct catpt_dev *cdev, + + static int catpt_restore_module(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_mod_hdr *mod) ++ struct catpt_fw_module_hdr *mod) + { +- u32 offset = sizeof(*mod); ++ u32 off = sizeof(*mod); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, +@@ -379,7 +410,7 @@ static int catpt_restore_module(struct catpt_dev *cdev, + struct catpt_fw_block_hdr *blk; + int ret; + +- blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset); ++ blk = (struct catpt_fw_block_hdr *)((u8 *)mod + off); + + switch (blk->ram_type) { + case CATPT_RAM_TYPE_INSTANCE: +@@ -390,7 +421,7 @@ static int catpt_restore_module(struct catpt_dev *cdev, + ALIGN(blk->size, 4)); + break; + default: +- ret = catpt_load_block(cdev, chan, paddr + offset, ++ ret = catpt_load_block(cdev, chan, paddr + off, + blk, false); + break; + } +@@ -400,7 +431,7 @@ static int catpt_restore_module(struct catpt_dev *cdev, + return ret; + } + +- offset += sizeof(*blk) + blk->size; ++ off += sizeof(*blk) + blk->size; + } + + return 0; +@@ -408,10 +439,10 @@ static int catpt_restore_module(struct catpt_dev *cdev, + + static int catpt_load_module(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_mod_hdr *mod) ++ struct catpt_fw_module_hdr *mod) + { + struct catpt_module_type *type; +- u32 offset = sizeof(*mod); ++ u32 off = sizeof(*mod); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, +@@ -423,9 +454,9 @@ static int catpt_load_module(struct catpt_dev *cdev, + struct catpt_fw_block_hdr *blk; + int ret; + +- blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset); ++ blk = (struct catpt_fw_block_hdr *)((u8 *)mod + off); + +- ret = catpt_load_block(cdev, chan, paddr + offset, blk, true); ++ ret = catpt_load_block(cdev, chan, paddr + off, blk, true); + if (ret) { + dev_err(cdev->dev, "load block failed: %d\n", ret); + return ret; +@@ -440,7 +471,7 @@ static int catpt_load_module(struct catpt_dev *cdev, + type->state_size = blk->size; + } + +- offset += sizeof(*blk) + blk->size; ++ off += sizeof(*blk) + blk->size; + } + + /* init module type static info */ +@@ -457,17 +488,17 @@ static int catpt_restore_firmware(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_hdr *fw) + { +- u32 offset = sizeof(*fw); ++ u32 off = sizeof(*fw); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + fw, sizeof(*fw), false); + + for (i = 0; i < fw->modules; i++) { +- struct catpt_fw_mod_hdr *mod; ++ struct catpt_fw_module_hdr *mod; + int ret; + +- mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset); ++ mod = (struct catpt_fw_module_hdr *)((u8 *)fw + off); + if (strncmp(fw->signature, mod->signature, + FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "module signature mismatch\n"); +@@ -479,12 +510,10 @@ static int catpt_restore_firmware(struct catpt_dev *cdev, + + switch (mod->module_id) { + case CATPT_MODID_BASE_FW: +- ret = catpt_restore_basefw(cdev, chan, paddr + offset, +- mod); ++ ret = catpt_restore_basefw(cdev, chan, paddr + off, mod); + break; + default: +- ret = catpt_restore_module(cdev, chan, paddr + offset, +- mod); ++ ret = catpt_restore_module(cdev, chan, paddr + off, mod); + break; + } + +@@ -493,7 +522,7 @@ static int catpt_restore_firmware(struct catpt_dev *cdev, + return ret; + } + +- offset += sizeof(*mod) + mod->mod_size; ++ off += sizeof(*mod) + mod->mod_size; + } + + return 0; +@@ -503,17 +532,17 @@ static int catpt_load_firmware(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_hdr *fw) + { +- u32 offset = sizeof(*fw); ++ u32 off = sizeof(*fw); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + fw, sizeof(*fw), false); + + for (i = 0; i < fw->modules; i++) { +- struct catpt_fw_mod_hdr *mod; ++ struct catpt_fw_module_hdr *mod; + int ret; + +- mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset); ++ mod = (struct catpt_fw_module_hdr *)((u8 *)fw + off); + if (strncmp(fw->signature, mod->signature, + FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "module signature mismatch\n"); +@@ -523,44 +552,40 @@ static int catpt_load_firmware(struct catpt_dev *cdev, + if (mod->module_id > CATPT_MODID_LAST) + return -EINVAL; + +- ret = catpt_load_module(cdev, chan, paddr + offset, mod); ++ ret = catpt_load_module(cdev, chan, paddr + off, mod); + if (ret) { + dev_err(cdev->dev, "load module failed: %d\n", ret); + return ret; + } + +- offset += sizeof(*mod) + mod->mod_size; ++ off += sizeof(*mod) + mod->mod_size; + } + + return 0; + } + +-static int catpt_load_image(struct catpt_dev *cdev, struct dma_chan *chan, +- const char *name, const char *signature, +- bool restore) ++static int catpt_request_load_firmware(struct catpt_dev *cdev, struct dma_chan *chan, ++ const char *name, bool restore) + { + struct catpt_fw_hdr *fw; +- struct firmware *img; + dma_addr_t paddr; + void *vaddr; + int ret; + +- ret = request_firmware((const struct firmware **)&img, name, cdev->dev); ++ const struct firmware *img __free(firmware) = NULL; ++ ret = request_firmware(&img, name, cdev->dev); + if (ret) + return ret; + + fw = (struct catpt_fw_hdr *)img->data; +- if (strncmp(fw->signature, signature, FW_SIGNATURE_SIZE)) { ++ if (strncmp(fw->signature, FW_SIGNATURE, FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "firmware signature mismatch\n"); +- ret = -EINVAL; +- goto release_fw; ++ return -EINVAL; + } + + vaddr = dma_alloc_coherent(cdev->dev, img->size, &paddr, GFP_KERNEL); +- if (!vaddr) { +- ret = -ENOMEM; +- goto release_fw; +- } ++ if (!vaddr) ++ return -ENOMEM; + + memcpy(vaddr, img->data, img->size); + fw = (struct catpt_fw_hdr *)vaddr; +@@ -570,12 +595,10 @@ static int catpt_load_image(struct catpt_dev *cdev, struct dma_chan *chan, + ret = catpt_load_firmware(cdev, chan, paddr, fw); + + dma_free_coherent(cdev->dev, img->size, vaddr, paddr); +-release_fw: +- release_firmware(img); + return ret; + } + +-static int catpt_load_images(struct catpt_dev *cdev, bool restore) ++static int catpt_request_dma_load_firmware(struct catpt_dev *cdev, bool restore) + { + struct dma_chan *chan; + int ret; +@@ -584,8 +607,7 @@ static int catpt_load_images(struct catpt_dev *cdev, bool restore) + if (IS_ERR(chan)) + return PTR_ERR(chan); + +- ret = catpt_load_image(cdev, chan, cdev->spec->fw_name, +- FW_SIGNATURE, restore); ++ ret = catpt_request_load_firmware(cdev, chan, cdev->spec->fw_name, restore); + if (ret) + goto release_dma_chan; + +@@ -605,7 +627,7 @@ int catpt_boot_firmware(struct catpt_dev *cdev, bool restore) + + catpt_dsp_stall(cdev, true); + +- ret = catpt_load_images(cdev, restore); ++ ret = catpt_request_dma_load_firmware(cdev, restore); + if (ret) { + dev_err(cdev->dev, "load binaries failed: %d\n", ret); + return ret; +@@ -619,10 +641,10 @@ int catpt_boot_firmware(struct catpt_dev *cdev, bool restore) + if (!ret) { + dev_err(cdev->dev, "firmware ready timeout\n"); + return -ETIMEDOUT; +- /* Wake up does not mean FW is ready, an exception could occur. */ +- } else if (!cdev->ipc.ready) { +- return -EREMOTEIO; + } ++ /* Wake up does not mean FW is ready, an exception could occur. */ ++ if (!cdev->ipc.ready) ++ return -EREMOTEIO; + + /* update sram pg & clock once done booting */ + catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask); +diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c +--- a/sound/soc/intel/catpt/pcm.c ++++ b/sound/soc/intel/catpt/pcm.c +@@ -75,8 +75,8 @@ static struct catpt_stream_template *catpt_topology[] = { + static struct catpt_stream_template * + catpt_get_stream_template(struct snd_pcm_substream *substream) + { +- struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); +- struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtm, 0); ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + enum catpt_stream_type type; + + type = cpu_dai->driver->id; +@@ -159,11 +159,11 @@ static void catpt_stream_read_position(struct catpt_dev *cdev, + static void catpt_arrange_page_table(struct snd_pcm_substream *substream, + struct snd_dma_buffer *pgtbl) + { +- struct snd_pcm_runtime *rtm = substream->runtime; ++ struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *databuf = snd_pcm_get_dma_buf(substream); + int i, pages; + +- pages = snd_sgbuf_aligned_pages(rtm->dma_bytes); ++ pages = snd_sgbuf_aligned_pages(runtime->dma_bytes); + + for (i = 0; i < pages; i++) { + u32 pfn, offset; +@@ -386,7 +386,7 @@ static int catpt_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { +- struct snd_pcm_runtime *rtm = substream->runtime; ++ struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *dmab; + struct catpt_stream_runtime *stream; + struct catpt_audio_format afmt; +@@ -412,8 +412,8 @@ static int catpt_dai_hw_params(struct snd_pcm_substream *substream, + + memset(&rinfo, 0, sizeof(rinfo)); + rinfo.page_table_addr = stream->pgtbl.addr; +- rinfo.num_pages = DIV_ROUND_UP(rtm->dma_bytes, PAGE_SIZE); +- rinfo.size = rtm->dma_bytes; ++ rinfo.num_pages = DIV_ROUND_UP(runtime->dma_bytes, PAGE_SIZE); ++ rinfo.size = runtime->dma_bytes; + rinfo.offset = 0; + rinfo.ring_first_page_pfn = PFN_DOWN(snd_sgbuf_get_addr(dmab, 0)); + +@@ -544,11 +544,11 @@ void catpt_stream_update_position(struct catpt_dev *cdev, + struct catpt_notify_position *pos) + { + struct snd_pcm_substream *substream = stream->substream; +- struct snd_pcm_runtime *r = substream->runtime; ++ struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t dsppos, newpos; + int ret; + +- dsppos = bytes_to_frames(r, pos->stream_position); ++ dsppos = bytes_to_frames(runtime, pos->stream_position); + + if (!stream->prepared) + goto exit; +@@ -556,8 +556,8 @@ void catpt_stream_update_position(struct catpt_dev *cdev, + if (stream->template->type != CATPT_STRM_TYPE_RENDER) + goto exit; + +- if (dsppos >= r->buffer_size / 2) +- newpos = r->buffer_size / 2; ++ if (dsppos >= runtime->buffer_size / 2) ++ newpos = runtime->buffer_size / 2; + else + newpos = 0; + /* +@@ -565,7 +565,7 @@ void catpt_stream_update_position(struct catpt_dev *cdev, + * (buffer half consumed) update wp to allow stream progression. + */ + ret = catpt_ipc_set_write_pos(cdev, stream->info.stream_hw_id, +- frames_to_bytes(r, newpos), ++ frames_to_bytes(runtime, newpos), + false, false); + if (ret) { + dev_err(cdev->dev, "update position for stream %d failed: %d\n", +@@ -600,11 +600,11 @@ static const struct snd_pcm_hardware catpt_pcm_hardware = { + }; + + static int catpt_component_pcm_new(struct snd_soc_component *component, +- struct snd_soc_pcm_runtime *rtm) ++ struct snd_soc_pcm_runtime *rtd) + { + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + +- snd_pcm_set_managed_buffer_all(rtm->pcm, SNDRV_DMA_TYPE_DEV_SG, ++ snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV_SG, + cdev->dev, + catpt_pcm_hardware.buffer_bytes_max, + catpt_pcm_hardware.buffer_bytes_max); +@@ -615,9 +615,9 @@ static int catpt_component_pcm_new(struct snd_soc_component *component, + static int catpt_component_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + +- if (!rtm->dai_link->no_pcm) ++ if (!rtd->dai_link->no_pcm) + snd_soc_set_runtime_hwparams(substream, &catpt_pcm_hardware); + return 0; + } +@@ -626,13 +626,13 @@ static snd_pcm_uframes_t + catpt_component_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); +- struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtm, 0); ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct catpt_stream_runtime *stream; + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + u32 pos; + +- if (rtm->dai_link->no_pcm) ++ if (rtd->dai_link->no_pcm) + return 0; + + stream = snd_soc_dai_get_dma_data(cpu_dai, substream); +@@ -650,10 +650,10 @@ static const struct snd_soc_dai_ops catpt_fe_dai_ops = { + .trigger = catpt_dai_trigger, + }; + +-static int catpt_dai_pcm_new(struct snd_soc_pcm_runtime *rtm, ++static int catpt_dai_pcm_new(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *dai) + { +- struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtm, 0); ++ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct catpt_ssp_device_format devfmt; + struct catpt_dev *cdev = dev_get_drvdata(dai->dev); + int ret; +@@ -871,8 +871,7 @@ static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol) + return CATPT_IPC_RET(ret); + } + +-static int catpt_volume_info(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_info *uinfo) ++static int catpt_volume_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) + { + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = CATPT_CHANNELS_MAX; +@@ -1073,18 +1072,14 @@ int catpt_arm_stream_templates(struct catpt_dev *cdev) + int catpt_register_plat_component(struct catpt_dev *cdev) + { + struct snd_soc_component *component; +- int ret; + +- component = devm_kzalloc(cdev->dev, sizeof(*component), GFP_KERNEL); ++ component = snd_soc_component_alloc(cdev->dev); + if (!component) + return -ENOMEM; + +- ret = snd_soc_component_initialize(component, &catpt_comp_driver, +- cdev->dev); +- if (ret) +- return ret; ++ snd_soc_component_set_name(component, catpt_comp_driver.name); + +- component->name = catpt_comp_driver.name; +- return snd_soc_add_component(component, dai_drivers, +- ARRAY_SIZE(dai_drivers)); ++ return snd_soc_register_component(component, ++ &catpt_comp_driver, ++ dai_drivers, ARRAY_SIZE(dai_drivers)); + } +diff --git a/sound/soc/intel/common/soc-acpi-intel-arl-match.c b/sound/soc/intel/common/soc-acpi-intel-arl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-arl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-arl-match.c +@@ -572,6 +572,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_sdw_machines[] = { + .links = arl_rt711_l0_rt1316_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-rt711-l0-rt1316-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(3), +@@ -600,12 +601,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_sdw_machines[] = { + .links = arl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = 0x1, /* link0 required */ + .links = arl_sdca_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-rt711-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(2), +diff --git a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +@@ -717,24 +717,28 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .links = lnl_3_in_1_sdca, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt711-l0-rt1316-l23-rt714-l1.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2) | BIT(3), + .links = lnl_cs42l43_l0_cs35l56_l23, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l0-cs35l56-l23.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(1) | BIT(2) | BIT(3), + .links = lnl_cs42l43_l2_cs35l56x6_l13, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l2-cs35l56x6-l13.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(3), + .links = lnl_cs42l43_l0_cs35l56_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l0-cs35l56-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -748,12 +752,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .links = lnl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(2) | BIT(3), + .links = lnl_712_only, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt712-l2-rt1712-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -766,19 +772,22 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .link_mask = GENMASK(2, 0), + .links = lnl_sdw_rt1318_l12_rt714_l0, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-lnl-rt1318-l12-rt714-l0.tplg" ++ .sof_tplg_filename = "sof-lnl-rt1318-l12-rt714-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(2, 0), + .links = lnl_sdw_rt1320_l12_rt714_l0, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-lnl-rt1320-l12-rt714-l0.tplg" ++ .sof_tplg_filename = "sof-lnl-rt1320-l12-rt714-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(1), + .links = lnl_sdw_rt713_l0_rt1318_l1, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-lnl-rt713-l0-rt1318-l1.tplg" ++ .sof_tplg_filename = "sof-lnl-rt713-l0-rt1318-l1.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(1) | BIT(2), +diff --git a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c +@@ -1321,60 +1321,70 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .links = tac5572_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tac5572.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tac5672_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tac5672.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tac5682_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tac5682.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tas2783_link0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tas2783.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tas2883_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tas2883.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(3, 0), + .links = mtl_rt713_l0_rt1316_l12_rt1713_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1316-l12-rt1713-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(3, 0), + .links = mtl_rt713_l0_rt1318_l12_rt1713_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1318-l12-rt1713-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(1) | BIT(3), + .links = mtl_rt713_l0_rt1318_l1_rt1713_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1318-l1-rt1713-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(2, 0), + .links = mtl_rt713_l0_rt1316_l12, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1316-l12.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(3) | BIT(0), + .links = mtl_712_l0_1712_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt712-l0-rt1712-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -1395,7 +1405,8 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .link_mask = GENMASK(2, 0), + .links = mtl_sdw_rt1318_l12_rt714_l0, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-mtl-rt1318-l12-rt714-l0.tplg" ++ .sof_tplg_filename = "sof-mtl-rt1318-l12-rt714-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2) | BIT(3), +@@ -1455,12 +1466,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .links = mtl_3_in_1_sdca, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711-l0-rt1316-l23-rt714-l1.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = 0x9, /* 2 active links required */ + .links = mtl_rt711_l0_rt1316_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711-l0-rt1316-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -1474,18 +1487,21 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .links = mtl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2), + .links = rt5682_link2_max98373_link0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-sdw-rt5682-l2-max98373-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2), + .links = cs42l42_link0_max98363_link2, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-sdw-cs42l42-l0-max98363-l2.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + {}, + }; +diff --git a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +@@ -492,7 +492,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { + .links = ptl_rt722_l0_rt1320_l23, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-rt722-l0-rt1320-l23.tplg", +- .get_function_tplg_files = sof_sdw_get_tplg_files, ++ .machine_check = snd_soc_acpi_intel_no_function_topology, + }, + { + .link_mask = BIT(1) | BIT(2), +@@ -522,14 +522,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { + .links = ptl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(3), + .links = ptl_sdw_rt712_vb_l3_rt1320_l3, + .drv_name = "sof_sdw", +- .machine_check = snd_soc_acpi_intel_sdca_is_device_rt712_vb, ++ .machine_check = snd_soc_acpi_intel_rt712_vb_no_function_topology, + .sof_tplg_filename = "sof-ptl-rt712-l3-rt1320-l3.tplg", +- .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + {}, + }; +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +@@ -53,6 +53,14 @@ bool snd_soc_acpi_intel_no_function_topology(void *arg) + } + EXPORT_SYMBOL_NS(snd_soc_acpi_intel_no_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); + ++bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg) ++{ ++ return snd_soc_acpi_intel_sdca_is_device_rt712_vb(arg) && ++ snd_soc_acpi_intel_no_function_topology(arg); ++} ++EXPORT_SYMBOL_NS(snd_soc_acpi_intel_rt712_vb_no_function_topology, ++ "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); ++ + MODULE_DESCRIPTION("ASoC ACPI Intel SDCA quirks"); + MODULE_LICENSE("GPL"); + MODULE_IMPORT_NS("SND_SOC_SDCA"); +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +@@ -11,5 +11,6 @@ + + bool snd_soc_acpi_intel_sdca_is_device_rt712_vb(void *arg); + bool snd_soc_acpi_intel_no_function_topology(void *arg); ++bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg); + + #endif +diff --git a/sound/soc/intel/keembay/kmb_platform.c b/sound/soc/intel/keembay/kmb_platform.c +--- a/sound/soc/intel/keembay/kmb_platform.c ++++ b/sound/soc/intel/keembay/kmb_platform.c +@@ -873,6 +873,8 @@ static int kmb_plat_dai_probe(struct platform_device *pdev) + + if (kmb_i2s->use_pio) { + irq = platform_get_irq_optional(pdev, 0); ++ if (irq == -EPROBE_DEFER) ++ return irq; + if (irq > 0) { + ret = devm_request_irq(dev, irq, kmb_i2s_irq_handler, 0, + pdev->name, kmb_i2s); +diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c +--- a/sound/soc/loongson/loongson_card.c ++++ b/sound/soc/loongson/loongson_card.c +@@ -2,24 +2,131 @@ + // + // Loongson ASoC Audio Machine driver + // +-// Copyright (C) 2023 Loongson Technology Corporation Limited ++// Copyright (C) 2023-2026 Loongson Technology Corporation Limited + // Author: Yingkun Meng ++// Binbin Zhou + // + ++#include ++#include + #include ++#include ++#include ++#include + #include + #include +-#include +-#include +-#include + + static char codec_name[SND_ACPI_I2C_ID_LEN]; + + struct loongson_card_data { + struct snd_soc_card snd_card; + unsigned int mclk_fs; ++ struct gpio_desc *gpiod_hp_det; ++ struct gpio_desc *gpiod_hp_ctl; ++ struct gpio_desc *gpiod_spkr_en; ++ const struct loongson_card_config *cfg; + }; + ++struct loongson_card_config { ++ unsigned int fmt; ++ bool add_hp_jack; ++ bool add_dapm_widgets; ++ bool add_dapm_routes; ++}; ++ ++static const struct loongson_card_config ls2k1000_card_config = { ++ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBC_CFC, ++ .add_hp_jack = false, ++ .add_dapm_widgets = false, ++ .add_dapm_routes = false, ++}; ++ ++static const struct loongson_card_config ls2k0300_forever_pi_card_config = { ++ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC, ++ .add_hp_jack = false, ++ .add_dapm_widgets = false, ++ .add_dapm_routes = false, ++}; ++ ++static const struct loongson_card_config ls2k0300_dl2k0300b_card_config = { ++ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC, ++ .add_hp_jack = true, ++ .add_dapm_widgets = true, ++ .add_dapm_routes = true, ++}; ++ ++static int tegra_machine_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *k, int event) ++{ ++ struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); ++ struct loongson_card_data *priv = snd_soc_card_get_drvdata(card); ++ ++ if (!snd_soc_dapm_widget_name_cmp(w, "Speaker")) ++ gpiod_set_value_cansleep(priv->gpiod_spkr_en, ++ SND_SOC_DAPM_EVENT_ON(event)); ++ ++ if (!snd_soc_dapm_widget_name_cmp(w, "Headphone")) ++ gpiod_set_value_cansleep(priv->gpiod_hp_ctl, ++ SND_SOC_DAPM_EVENT_ON(event)); ++ ++ return 0; ++} ++ ++static const struct snd_soc_dapm_widget loongson_aosc_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone", tegra_machine_event), ++ SND_SOC_DAPM_SPK("Speaker", tegra_machine_event), ++}; ++ ++/* Headphones Jack */ ++ ++static struct snd_soc_jack loongson_asoc_hp_jack; ++ ++static struct snd_soc_jack_pin loongson_asoc_hp_jack_pins[] = { ++ { ++ .pin = "Headphone", ++ .mask = SND_JACK_HEADPHONE ++ }, ++ { ++ .pin = "Speaker", ++ .mask = SND_JACK_HEADPHONE, ++ .invert = 1 ++ }, ++}; ++ ++static struct snd_soc_jack_gpio loongson_asoc_hp_jack_gpio = { ++ .name = "Headphones detection", ++ .report = SND_JACK_HEADPHONE, ++ .debounce_time = 150, ++}; ++ ++static int loongson_asoc_machine_init(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct snd_soc_card *card = rtd->card; ++ struct loongson_card_data *ls_priv = snd_soc_card_get_drvdata(card); ++ int ret = 0; ++ ++ if (!ls_priv->cfg->add_hp_jack || !ls_priv->gpiod_hp_det) ++ return 0; ++ ++ ret = snd_soc_card_jack_new_pins(card, "Headphones Jack", ++ SND_JACK_HEADPHONE, ++ &loongson_asoc_hp_jack, ++ loongson_asoc_hp_jack_pins, ++ ARRAY_SIZE(loongson_asoc_hp_jack_pins)); ++ if (ret) { ++ dev_err(rtd->dev, "Headphones Jack creation failed: %d\n", ret); ++ return ret; ++ } ++ ++ loongson_asoc_hp_jack_gpio.desc = ls_priv->gpiod_hp_det; ++ ++ ret = snd_soc_jack_add_gpios(&loongson_asoc_hp_jack, 1, &loongson_asoc_hp_jack_gpio); ++ if (ret) ++ dev_err(rtd->dev, "Headphone GPIO not added: %d\n", ret); ++ ++ return ret; ++} ++ + static int loongson_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) + { +@@ -45,7 +152,7 @@ static int loongson_card_hw_params(struct snd_pcm_substream *substream, + return ret; + } + +- return 0; ++ return snd_soc_runtime_set_dai_fmt(rtd, ls_card->cfg->fmt); + } + + static const struct snd_soc_ops loongson_ops = { +@@ -61,8 +168,7 @@ static struct snd_soc_dai_link loongson_dai_links[] = { + { + .name = "Loongson Audio Port", + .stream_name = "Loongson Audio", +- .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF +- | SND_SOC_DAIFMT_CBC_CFC, ++ .init = loongson_asoc_machine_init, + SND_SOC_DAILINK_REG(analog), + .ops = &loongson_ops, + }, +@@ -123,16 +229,35 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data) + + static int loongson_card_parse_of(struct loongson_card_data *data) + { +- struct device_node *cpu, *codec; + struct snd_soc_card *card = &data->snd_card; ++ struct device_node *cpu, *codec; + struct device *dev = card->dev; + int ret, i; + ++ data->gpiod_hp_det = devm_gpiod_get_optional(dev, "hp-det", GPIOD_IN); ++ if (IS_ERR(data->gpiod_hp_det)) ++ return PTR_ERR(data->gpiod_hp_det); ++ ++ data->gpiod_hp_ctl = devm_gpiod_get_optional(dev, "hp-ctl", GPIOD_OUT_LOW); ++ if (IS_ERR(data->gpiod_hp_ctl)) ++ return PTR_ERR(data->gpiod_hp_ctl); ++ ++ data->gpiod_spkr_en = devm_gpiod_get_optional(dev, "spkr-en", GPIOD_OUT_LOW); ++ if (IS_ERR(data->gpiod_spkr_en)) ++ return PTR_ERR(data->gpiod_spkr_en); ++ ++ if (data->cfg->add_dapm_routes) { ++ ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); ++ if (ret) ++ return ret; ++ } ++ + cpu = of_get_child_by_name(dev->of_node, "cpu"); + if (!cpu) { + dev_err(dev, "platform property missing or invalid\n"); + return -EINVAL; + } ++ + codec = of_get_child_by_name(dev->of_node, "codec"); + if (!codec) { + dev_err(dev, "audio-codec property missing or invalid\n"); +@@ -177,12 +302,22 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) + if (!ls_priv) + return -ENOMEM; + ++ ls_priv->cfg = (const struct loongson_card_config *)device_get_match_data(dev); ++ if (!ls_priv->cfg) ++ return -EINVAL; ++ + card = &ls_priv->snd_card; + + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = loongson_dai_links; + card->num_links = ARRAY_SIZE(loongson_dai_links); ++ ++ if (ls_priv->cfg->add_dapm_widgets) { ++ card->dapm_widgets = loongson_aosc_dapm_widgets; ++ card->num_dapm_widgets = ARRAY_SIZE(loongson_aosc_dapm_widgets); ++ } ++ + snd_soc_card_set_drvdata(card, ls_priv); + + ret = device_property_read_string(dev, "model", &card->name); +@@ -202,7 +337,19 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) + } + + static const struct of_device_id loongson_asoc_dt_ids[] = { +- { .compatible = "loongson,ls-audio-card" }, ++ /* Loongson-2K1000/Loongson-2K2000/LS7A */ ++ { ++ .compatible = "loongson,ls-audio-card", ++ .data = &ls2k1000_card_config ++ }, ++ { ++ .compatible = "loongson,ls2k0300-forever-pi-audio-card", ++ .data = &ls2k0300_forever_pi_card_config ++ }, ++ { ++ .compatible = "loongson,ls2k0300-dl2k0300b-audio-card", ++ .data = &ls2k0300_dl2k0300b_card_config ++ }, + { /* sentinel */ }, + }; + MODULE_DEVICE_TABLE(of, loongson_asoc_dt_ids); +diff --git a/sound/soc/loongson/loongson_i2s_plat.c b/sound/soc/loongson/loongson_i2s_plat.c +--- a/sound/soc/loongson/loongson_i2s_plat.c ++++ b/sound/soc/loongson/loongson_i2s_plat.c +@@ -2,7 +2,7 @@ + // + // Loongson I2S controller master mode dirver(platform device) + // +-// Copyright (C) 2023-2024 Loongson Technology Corporation Limited ++// Copyright (C) 2023-2026 Loongson Technology Corporation Limited + // + // Author: Yingkun Meng + // Binbin Zhou +@@ -21,6 +21,7 @@ + #include "loongson_i2s.h" + #include "loongson_dma.h" + ++/* Loongson-2K1000 APBDMA routing */ + #define LOONGSON_I2S_RX_DMA_OFFSET 21 + #define LOONGSON_I2S_TX_DMA_OFFSET 18 + +@@ -30,6 +31,11 @@ + #define LOONGSON_DMA3_CONF 0x3 + #define LOONGSON_DMA4_CONF 0x4 + ++struct loongson_i2s_plat_config { ++ int rev_id; ++ int (*i2s_dma_config)(struct platform_device *pdev); ++}; ++ + static int loongson_i2s_apbdma_config(struct platform_device *pdev) + { + int val; +@@ -47,8 +53,18 @@ static int loongson_i2s_apbdma_config(struct platform_device *pdev) + return 0; + } + ++static const struct loongson_i2s_plat_config ls2k0300_i2s_plat_config = { ++ .rev_id = 1, ++}; ++ ++static const struct loongson_i2s_plat_config ls2k1000_i2s_plat_config = { ++ .rev_id = 0, ++ .i2s_dma_config = loongson_i2s_apbdma_config, ++}; ++ + static int loongson_i2s_plat_probe(struct platform_device *pdev) + { ++ const struct loongson_i2s_plat_config *plat_config; + struct device *dev = &pdev->dev; + struct loongson_i2s *i2s; + struct resource *res; +@@ -59,12 +75,17 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev) + if (!i2s) + return -ENOMEM; + +- ret = loongson_i2s_apbdma_config(pdev); +- if (ret) +- return ret; ++ plat_config = device_get_match_data(dev); ++ if (!plat_config) ++ return -EINVAL; + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- i2s->reg_base = devm_ioremap_resource(&pdev->dev, res); ++ if (plat_config->i2s_dma_config) { ++ ret = plat_config->i2s_dma_config(pdev); ++ if (ret) ++ return ret; ++ } ++ ++ i2s->reg_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(i2s->reg_base)) + return dev_err_probe(dev, PTR_ERR(i2s->reg_base), + "devm_ioremap_resource failed\n"); +@@ -87,11 +108,17 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev) + if (IS_ERR(i2s_clk)) + return dev_err_probe(dev, PTR_ERR(i2s_clk), "clock property invalid\n"); + i2s->clk_rate = clk_get_rate(i2s_clk); ++ i2s->rev_id = plat_config->rev_id; + + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + dev_set_name(dev, LS_I2S_DRVNAME); + dev_set_drvdata(dev, i2s); + ++ if (i2s->rev_id == 1) { ++ regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET, I2S_CTRL_RESET); ++ fsleep(200); ++ } ++ + ret = devm_snd_soc_register_component(dev, &loongson_i2s_edma_component, + &loongson_i2s_dai, 1); + if (ret) +@@ -102,7 +129,8 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev) + } + + static const struct of_device_id loongson_i2s_ids[] = { +- { .compatible = "loongson,ls2k1000-i2s" }, ++ { .compatible = "loongson,ls2k0300-i2s", .data = &ls2k0300_i2s_plat_config }, ++ { .compatible = "loongson,ls2k1000-i2s", .data = &ls2k1000_i2s_plat_config }, + { /* sentinel */ }, + }; + MODULE_DEVICE_TABLE(of, loongson_i2s_ids); +diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig +--- a/sound/soc/mediatek/Kconfig ++++ b/sound/soc/mediatek/Kconfig +@@ -7,7 +7,7 @@ config SND_SOC_MEDIATEK + + config SND_SOC_MT2701 + tristate "ASoC support for Mediatek MT2701 chip" +- depends on ARCH_MEDIATEK ++ depends on ARCH_MEDIATEK || COMPILE_TEST + select SND_SOC_MEDIATEK + help + This adds ASoC driver for Mediatek MT2701 boards +diff --git a/sound/soc/mediatek/common/mtk-dsp-sof-common.c b/sound/soc/mediatek/common/mtk-dsp-sof-common.c +--- a/sound/soc/mediatek/common/mtk-dsp-sof-common.c ++++ b/sound/soc/mediatek/common/mtk-dsp-sof-common.c +@@ -232,6 +232,7 @@ int mtk_sof_dailink_parse_of(struct device *dev, struct snd_soc_card *card, + const char *propname) + { + struct device_node *np = dev->of_node; ++ struct snd_soc_dai_link *dai_link; + struct snd_soc_dai_link *parsed_dai_link; + const char *dai_name = NULL; + int i, j, ret, num_links, parsed_num_links = 0; +@@ -254,9 +255,9 @@ int mtk_sof_dailink_parse_of(struct device *dev, struct snd_soc_card *card, + return ret; + } + dev_dbg(dev, "ASoC: Property get dai_name:%s\n", dai_name); +- for (j = 0; j < card->num_links; j++) { +- if (!strcmp(dai_name, card->dai_link[j].name)) { +- memcpy(&parsed_dai_link[parsed_num_links++], &card->dai_link[j], ++ for_each_card_prelinks(card, j, dai_link) { ++ if (!strcmp(dai_name, dai_link->name)) { ++ memcpy(&parsed_dai_link[parsed_num_links++], dai_link, + sizeof(struct snd_soc_dai_link)); + break; + } +diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650.c b/sound/soc/mediatek/mt8173/mt8173-rt5650.c +--- a/sound/soc/mediatek/mt8173/mt8173-rt5650.c ++++ b/sound/soc/mediatek/mt8173/mt8173-rt5650.c +@@ -315,7 +315,7 @@ static int mt8173_rt5650_dev_probe(struct platform_device *pdev) + &mt8173_rt5650_priv.pll_from); + if (ret) { + dev_err(&pdev->dev, +- "%s snd_soc_register_card fail %d\n", ++ "%s device_property_read_u32() fail %d\n", + __func__, ret); + } + } +diff --git a/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c +--- a/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c ++++ b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c +@@ -172,7 +172,7 @@ static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S32_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + +@@ -184,7 +184,7 @@ static int mt8183_rt1015_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +diff --git a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c +--- a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c ++++ b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c +@@ -99,7 +99,7 @@ static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to S32_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + return 0; +@@ -112,7 +112,7 @@ static int mt8183_rt1015_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + return 0; +diff --git a/sound/soc/mediatek/mt8186/mt8186-mt6366.c b/sound/soc/mediatek/mt8186/mt8186-mt6366.c +--- a/sound/soc/mediatek/mt8186/mt8186-mt6366.c ++++ b/sound/soc/mediatek/mt8186/mt8186-mt6366.c +@@ -387,7 +387,7 @@ static int mt8186_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, fmt); + +diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c +--- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c ++++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c +@@ -623,7 +623,7 @@ static int mt8188_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + +diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-adda.c b/sound/soc/mediatek/mt8189/mt8189-dai-adda.c +--- a/sound/soc/mediatek/mt8189/mt8189-dai-adda.c ++++ b/sound/soc/mediatek/mt8189/mt8189-dai-adda.c +@@ -385,8 +385,6 @@ static int mtk_adda_pad_top_event(struct snd_soc_dapm_widget *w, + if (event == SND_SOC_DAPM_PRE_PMU) { + if (afe_priv->mtkaif_protocol == MTKAIF_PROTOCOL_2_CLK_P2) + regmap_write(afe->regmap, AFE_AUD_PAD_TOP_CFG0, 0xB8); +- else if (afe_priv->mtkaif_protocol == MTKAIF_PROTOCOL_2) +- regmap_write(afe->regmap, AFE_AUD_PAD_TOP_CFG0, 0xB0); + else + regmap_write(afe->regmap, AFE_AUD_PAD_TOP_CFG0, 0xB0); + } +diff --git a/sound/soc/mediatek/mt8189/mt8189-nau8825.c b/sound/soc/mediatek/mt8189/mt8189-nau8825.c +--- a/sound/soc/mediatek/mt8189/mt8189-nau8825.c ++++ b/sound/soc/mediatek/mt8189/mt8189-nau8825.c +@@ -174,7 +174,7 @@ static int mt8189_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + +diff --git a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +--- a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c ++++ b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +@@ -382,7 +382,7 @@ static int mt8192_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +@@ -1019,7 +1019,7 @@ static struct snd_soc_card mt8192_mt6359_rt1015p_rt5682x_card = { + .num_dapm_routes = ARRAY_SIZE(mt8192_mt6359_rt1015p_rt5682x_routes), + }; + +-static int mt8192_mt6359_card_set_be_link(struct snd_soc_card *card, ++static int mt8192_mt6359_card_set_be_link(struct device *dev, + struct snd_soc_dai_link *link, + struct device_node *node, + char *link_name) +@@ -1027,9 +1027,9 @@ static int mt8192_mt6359_card_set_be_link(struct snd_soc_card *card, + int ret; + + if (node && strcmp(link->name, link_name) == 0) { +- ret = snd_soc_of_get_dai_link_codecs(card->dev, node, link); ++ ret = snd_soc_of_get_dai_link_codecs(dev, node, link); + if (ret < 0) { +- dev_err_probe(card->dev, ret, "get dai link codecs fail\n"); ++ dev_err_probe(dev, ret, "get dai link codecs fail\n"); + return ret; + } + } +@@ -1065,21 +1065,21 @@ static int mt8192_mt6359_legacy_probe(struct mtk_soc_card_data *soc_card_data) + } + + for_each_card_prelinks(card, i, dai_link) { +- ret = mt8192_mt6359_card_set_be_link(card, dai_link, speaker_codec, "I2S3"); ++ ret = mt8192_mt6359_card_set_be_link(dev, dai_link, speaker_codec, "I2S3"); + if (ret) { + dev_err_probe(dev, ret, "%s set speaker_codec fail\n", + dai_link->name); + break; + } + +- ret = mt8192_mt6359_card_set_be_link(card, dai_link, headset_codec, "I2S8"); ++ ret = mt8192_mt6359_card_set_be_link(dev, dai_link, headset_codec, "I2S8"); + if (ret) { + dev_err_probe(dev, ret, "%s set headset_codec fail\n", + dai_link->name); + break; + } + +- ret = mt8192_mt6359_card_set_be_link(card, dai_link, headset_codec, "I2S9"); ++ ret = mt8192_mt6359_card_set_be_link(dev, dai_link, headset_codec, "I2S9"); + if (ret) { + dev_err_probe(dev, ret, "%s set headset_codec fail\n", + dai_link->name); +diff --git a/sound/soc/mediatek/mt8195/mt8195-mt6359.c b/sound/soc/mediatek/mt8195/mt8195-mt6359.c +--- a/sound/soc/mediatek/mt8195/mt8195-mt6359.c ++++ b/sound/soc/mediatek/mt8195/mt8195-mt6359.c +@@ -387,7 +387,7 @@ static int mt8195_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +@@ -650,7 +650,7 @@ static int mt8195_etdm_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +diff --git a/sound/soc/mediatek/mt8196/mt8196-nau8825.c b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +--- a/sound/soc/mediatek/mt8196/mt8196-nau8825.c ++++ b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +@@ -104,8 +104,6 @@ static const struct snd_kcontrol_new mt8196_nau8825_controls[] = { + + #define EXT_SPK_AMP_W_NAME "Ext_Speaker_Amp" + +-static struct snd_soc_card mt8196_nau8825_soc_card; +- + static const struct snd_soc_dapm_widget mt8196_nau8825_card_widgets[] = { + /* SOF Uplink */ + SND_SOC_DAPM_MIXER("SOF_DMA_UL0", SND_SOC_NOPM, 0, 0, NULL, 0), +@@ -180,7 +178,7 @@ static int mt8196_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + return 0; +diff --git a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c +--- a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c ++++ b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c +@@ -2120,19 +2120,15 @@ static int mt8365_afe_pcm_dev_probe(struct platform_device *pdev) + spin_lock_init(&afe_priv->afe_ctrl_lock); + mutex_init(&afe_priv->afe_clk_mutex); + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- afe->base_addr = devm_ioremap_resource(&pdev->dev, res); ++ afe->base_addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(afe->base_addr)) + return PTR_ERR(afe->base_addr); + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 1); +- if (res) { +- afe_priv->afe_sram_vir_addr = +- devm_ioremap_resource(&pdev->dev, res); +- if (!IS_ERR(afe_priv->afe_sram_vir_addr)) { +- afe_priv->afe_sram_phy_addr = res->start; +- afe_priv->afe_sram_size = resource_size(res); +- } ++ afe_priv->afe_sram_vir_addr = ++ devm_platform_get_and_ioremap_resource(pdev, 1, &res); ++ if (!IS_ERR(afe_priv->afe_sram_vir_addr)) { ++ afe_priv->afe_sram_phy_addr = res->start; ++ afe_priv->afe_sram_size = resource_size(res); + } + + /* initial audio related clock */ +diff --git a/sound/soc/mediatek/mt8365/mt8365-mt6357.c b/sound/soc/mediatek/mt8365/mt8365-mt6357.c +--- a/sound/soc/mediatek/mt8365/mt8365-mt6357.c ++++ b/sound/soc/mediatek/mt8365/mt8365-mt6357.c +@@ -70,7 +70,8 @@ static const struct snd_soc_dapm_route mt8365_mt6357_routes[] = { + static int mt8365_mt6357_int_adda_startup(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = substream->private_data; +- struct mt8365_mt6357_priv *priv = snd_soc_card_get_drvdata(rtd->card); ++ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(rtd->card); ++ struct mt8365_mt6357_priv *priv = soc_card_data->mach_priv; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +@@ -101,7 +102,8 @@ static int mt8365_mt6357_int_adda_startup(struct snd_pcm_substream *substream) + static void mt8365_mt6357_int_adda_shutdown(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = substream->private_data; +- struct mt8365_mt6357_priv *priv = snd_soc_card_get_drvdata(rtd->card); ++ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(rtd->card); ++ struct mt8365_mt6357_priv *priv = soc_card_data->mach_priv; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +@@ -245,7 +247,8 @@ static struct snd_soc_dai_link mt8365_mt6357_dais[] = { + + static int mt8365_mt6357_gpio_probe(struct snd_soc_card *card) + { +- struct mt8365_mt6357_priv *priv = snd_soc_card_get_drvdata(card); ++ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(card); ++ struct mt8365_mt6357_priv *priv = soc_card_data->mach_priv; + struct device *dev = card->dev; + int ret, i; + +@@ -294,7 +297,6 @@ static int mt8365_mt6357_dev_probe(struct mtk_soc_card_data *soc_card_data, bool + struct mt8365_mt6357_priv *mach_priv; + int ret; + +- card->dev = dev; + ret = parse_dai_link_info(card); + if (ret) + goto err; +diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile +--- a/sound/soc/meson/Makefile ++++ b/sound/soc/meson/Makefile +@@ -4,6 +4,8 @@ snd-soc-meson-aiu-y := aiu.o + snd-soc-meson-aiu-y += aiu-acodec-ctrl.o + snd-soc-meson-aiu-y += aiu-codec-ctrl.o + snd-soc-meson-aiu-y += aiu-encoder-i2s.o ++snd-soc-meson-aiu-y += gx-formatter.o ++snd-soc-meson-aiu-y += aiu-formatter-i2s.o + snd-soc-meson-aiu-y += aiu-encoder-spdif.o + snd-soc-meson-aiu-y += aiu-fifo.o + snd-soc-meson-aiu-y += aiu-fifo-i2s.o +diff --git a/sound/soc/meson/aiu-encoder-i2s.c b/sound/soc/meson/aiu-encoder-i2s.c +--- a/sound/soc/meson/aiu-encoder-i2s.c ++++ b/sound/soc/meson/aiu-encoder-i2s.c +@@ -10,14 +10,11 @@ + #include + + #include "aiu.h" ++#include "gx-formatter.h" ++#include "gx-interface.h" + +-#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0) +-#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5) +-#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9) + #define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11) +-#define AIU_RST_SOFT_I2S_FAST BIT(0) + +-#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2) + #define AIU_CLK_CTRL_I2S_DIV_EN BIT(0) + #define AIU_CLK_CTRL_I2S_DIV GENMASK(3, 2) + #define AIU_CLK_CTRL_AOCLK_INVERT BIT(6) +@@ -35,49 +32,6 @@ static void aiu_encoder_i2s_divider_enable(struct snd_soc_component *component, + enable ? AIU_CLK_CTRL_I2S_DIV_EN : 0); + } + +-static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component, +- struct snd_pcm_hw_params *params) +-{ +- /* Always operate in split (classic interleaved) mode */ +- unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT; +- +- /* Reset required to update the pipeline */ +- snd_soc_component_write(component, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST); +- snd_soc_component_read(component, AIU_I2S_SYNC); +- +- switch (params_physical_width(params)) { +- case 16: /* Nothing to do */ +- break; +- +- case 32: +- desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT | +- AIU_I2S_SOURCE_DESC_MODE_32BIT); +- break; +- +- default: +- return -EINVAL; +- } +- +- switch (params_channels(params)) { +- case 2: /* Nothing to do */ +- break; +- case 8: +- desc |= AIU_I2S_SOURCE_DESC_MODE_8CH; +- break; +- default: +- return -EINVAL; +- } +- +- snd_soc_component_update_bits(component, AIU_I2S_SOURCE_DESC, +- AIU_I2S_SOURCE_DESC_MODE_8CH | +- AIU_I2S_SOURCE_DESC_MODE_24BIT | +- AIU_I2S_SOURCE_DESC_MODE_32BIT | +- AIU_I2S_SOURCE_DESC_MODE_SPLIT, +- desc); +- +- return 0; +-} +- + static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component, + struct snd_pcm_hw_params *params, + unsigned int bs) +@@ -108,6 +62,32 @@ static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component, + return 0; + } + ++/* ++ * Return true if the given combination of channels and sample width requires ++ * the bs quirk. Return false otherwise. ++ */ ++static bool aiu_encoder_is_bs_quirk(unsigned int channels, int width) ++{ ++ return (channels == 8) && (width == 16); ++} ++ ++static int aiu_encoder_check_bs_quirk(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct gx_stream *other_stream = snd_soc_dai_dma_data_get(dai, !substream->stream); ++ ++ /* Nothing to do if the other stream doesn't exist or it's not configured yet. */ ++ if (!other_stream || !other_stream->channels) ++ return 0; ++ ++ if (aiu_encoder_is_bs_quirk(other_stream->channels, other_stream->width) != ++ aiu_encoder_is_bs_quirk(params_channels(params), params_width(params))) ++ return -EINVAL; ++ ++ return 0; ++} ++ + static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, + struct snd_pcm_hw_params *params, + unsigned int bs) +@@ -119,7 +99,7 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, + * increased by 50% to get the correct output rate. + * No idea why ! + */ +- if (params_width(params) == 16 && params_channels(params) == 8) { ++ if (aiu_encoder_is_bs_quirk(params_channels(params), params_width(params))) { + if (bs % 2) { + dev_err(component->dev, + "Cannot increase i2s divider by 50%%\n"); +@@ -141,25 +121,23 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, + return 0; + } + +-static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component, +- struct snd_pcm_hw_params *params) ++static int aiu_encoder_i2s_set_clocks(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) + { ++ struct snd_soc_component *component = dai->component; + struct aiu *aiu = snd_soc_component_get_drvdata(component); ++ struct gx_iface *iface = &aiu->i2s.iface; + unsigned int srate = params_rate(params); + unsigned int fs, bs; + int ret; + + /* Get the oversampling factor */ +- fs = DIV_ROUND_CLOSEST(clk_get_rate(aiu->i2s.clks[MCLK].clk), srate); ++ fs = DIV_ROUND_CLOSEST(iface->mclk_rate, srate); + +- if (fs % 64) ++ if ((fs % 64) || (fs == 0)) + return -EINVAL; + +- /* Send data MSB first */ +- snd_soc_component_update_bits(component, AIU_I2S_DAC_CFG, +- AIU_I2S_DAC_CFG_MSB_FIRST, +- AIU_I2S_DAC_CFG_MSB_FIRST); +- + /* Set bclk to lrlck ratio */ + snd_soc_component_update_bits(component, AIU_CODEC_DAC_LRCLK_CTRL, + AIU_CODEC_DAC_LRCLK_CTRL_DIV, +@@ -168,10 +146,22 @@ static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component, + + bs = fs / 64; + +- if (aiu->platform->has_clk_ctrl_more_i2s_div) ++ if (aiu->platform->has_clk_ctrl_more_i2s_div) { ++ /* ++ * The hw rules added in startup() make this unreachable in the ++ * sequential case, but both streams may be refined concurrently ++ * before either commits its config, since only ops->hw_params ++ * runs under the card's pcm_mutex. Re-check against the committed ++ * state of the other stream, which is stable under that mutex. ++ */ ++ if (aiu_encoder_check_bs_quirk(substream, params, dai)) { ++ dev_err(dai->dev, "bclk requirements incompatible with other stream\n"); ++ return -EINVAL; ++ } + ret = aiu_encoder_i2s_set_more_div(component, params, bs); +- else ++ } else { + ret = aiu_encoder_i2s_set_legacy_div(component, params, bs); ++ } + + if (ret) + return ret; +@@ -188,23 +178,37 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); ++ int ret; ++ ++ ret = aiu_encoder_i2s_set_clocks(substream, params, dai); ++ if (ret) { ++ dev_err(dai->dev, "setting i2s clocks failed: %d\n", ret); ++ return ret; ++ } ++ ++ ts->physical_width = params_physical_width(params); ++ ts->width = params_width(params); ++ ts->channels = params_channels(params); ++ ++ return 0; ++} ++ ++static int aiu_encoder_i2s_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct snd_soc_component *component = dai->component; + int ret; + +- /* Disable the clock while changing the settings */ +- aiu_encoder_i2s_divider_enable(component, false); ++ if (ts->clk_enabled) ++ return 0; + +- ret = aiu_encoder_i2s_setup_desc(component, params); +- if (ret) { +- dev_err(dai->dev, "setting i2s desc failed\n"); ++ ret = clk_prepare_enable(ts->iface->mclk); ++ if (ret) + return ret; +- } + +- ret = aiu_encoder_i2s_set_clocks(component, params); +- if (ret) { +- dev_err(dai->dev, "setting i2s clocks failed\n"); +- return ret; +- } ++ ts->clk_enabled = true; + + aiu_encoder_i2s_divider_enable(component, true); + +@@ -214,9 +218,24 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream, + static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct snd_soc_component *component = dai->component; + +- aiu_encoder_i2s_divider_enable(component, false); ++ /* ++ * If this is the last substream being closed then disable the i2s ++ * clock divider. ++ */ ++ if (snd_soc_dai_active(dai) <= 1) ++ aiu_encoder_i2s_divider_enable(component, 0); ++ ++ if (ts->clk_enabled) { ++ clk_disable_unprepare(ts->iface->mclk); ++ ts->clk_enabled = false; ++ } ++ ++ ts->channels = 0; ++ ts->width = 0; ++ ts->physical_width = 0; + + return 0; + } +@@ -224,6 +243,8 @@ static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream, + static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + { + struct snd_soc_component *component = dai->component; ++ struct aiu *aiu = snd_soc_component_get_drvdata(component); ++ struct gx_iface *iface = &aiu->i2s.iface; + unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK; + unsigned int val = 0; + unsigned int skew; +@@ -255,9 +276,12 @@ static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + skew = 0; + break; + default: ++ dev_err(dai->dev, "unsupported dai format\n"); + return -EINVAL; + } + ++ iface->fmt = fmt; ++ + val |= FIELD_PREP(AIU_CLK_CTRL_LRCLK_SKEW, skew); + snd_soc_component_update_bits(component, AIU_CLK_CTRL, + AIU_CLK_CTRL_LRCLK_INVERT | +@@ -272,6 +296,7 @@ static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) + { + struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); ++ struct gx_iface *iface = &aiu->i2s.iface; + int ret; + + if (WARN_ON(clk_id != 0)) +@@ -280,11 +305,15 @@ static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + if (dir == SND_SOC_CLOCK_IN) + return 0; + +- ret = clk_set_rate(aiu->i2s.clks[MCLK].clk, freq); +- if (ret) +- dev_err(dai->dev, "Failed to set sysclk to %uHz", freq); ++ ret = clk_set_rate(iface->mclk, freq); ++ if (ret) { ++ dev_err(dai->dev, "Failed to set sysclk to %uHz: %d", freq, ret); ++ return ret; ++ } + +- return ret; ++ iface->mclk_rate = freq; ++ ++ return 0; + } + + static const unsigned int hw_channels[] = {2, 8}; +@@ -294,10 +323,45 @@ static const struct snd_pcm_hw_constraint_list hw_channel_constraints = { + .mask = 0, + }; + ++static int aiu_encoder_i2s_pcm_hw_rule(struct snd_pcm_hw_params *params, ++ struct snd_pcm_hw_rule *rule) ++{ ++ struct gx_stream *other = rule->private; ++ struct snd_interval *ch = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); ++ /* ++ * The quirk is technically based on the significant bits whereas here ++ * we're using the physical width for simplicity. This works because ++ * S16_LE is the only format supported by this encoder that has: ++ * significant bits = physical width = 16-bits ++ */ ++ struct snd_interval *phys_width = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS); ++ struct snd_interval new_i; ++ ++ if (other->channels == 0) ++ return 0; ++ ++ snd_interval_any(&new_i); ++ ++ if (rule->var == SNDRV_PCM_HW_PARAM_CHANNELS) { ++ if (aiu_encoder_is_bs_quirk(other->channels, other->width)) ++ new_i.min = new_i.max = 8; ++ else if (snd_interval_single(phys_width) && phys_width->min == 16) ++ new_i.max = 2; /* Force 2ch */ ++ } else { /* SNDRV_PCM_HW_PARAM_SAMPLE_BITS */ ++ if (aiu_encoder_is_bs_quirk(other->channels, other->width)) ++ new_i.min = new_i.max = 16; ++ else if (snd_interval_single(ch) && ch->min == 8) ++ new_i.min = 17; /* Request physical width > 16 bits */ ++ } ++ ++ return snd_interval_refine(hw_param_interval(params, rule->var), &new_i); ++} ++ + static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { + struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); ++ struct gx_stream *other_stream = snd_soc_dai_dma_data_get(dai, !substream->stream); + int ret; + + /* Make sure the encoder gets either 2 or 8 channels */ +@@ -305,15 +369,79 @@ static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream, + SNDRV_PCM_HW_PARAM_CHANNELS, + &hw_channel_constraints); + if (ret) { +- dev_err(dai->dev, "adding channels constraints failed\n"); ++ dev_err(dai->dev, "adding channels constraints failed: %d\n", ret); + return ret; + } + +- ret = clk_bulk_prepare_enable(aiu->i2s.clk_num, aiu->i2s.clks); +- if (ret) +- dev_err(dai->dev, "failed to enable i2s clocks\n"); ++ /* ++ * If DAI supports both playback and capture streams ensure the bs-quirk is ++ * handled correctly. ++ * This is only valid for GX platforms (has_clk_ctrl_more_i2s_div=true). ++ */ ++ if (aiu->platform->has_clk_ctrl_more_i2s_div && other_stream) { ++ ret = snd_pcm_hw_rule_add(substream->runtime, 0, ++ SNDRV_PCM_HW_PARAM_CHANNELS, ++ aiu_encoder_i2s_pcm_hw_rule, ++ other_stream, ++ SNDRV_PCM_HW_PARAM_CHANNELS, ++ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); ++ if (ret) ++ return ret; + +- return ret; ++ ret = snd_pcm_hw_rule_add(substream->runtime, 0, ++ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, ++ aiu_encoder_i2s_pcm_hw_rule, ++ other_stream, ++ SNDRV_PCM_HW_PARAM_CHANNELS, ++ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); ++ if (ret) ++ return ret; ++ } ++ ++ /* ++ * Enable only clocks which are required for the interface internal ++ * logic. MCLK is enabled/disabled from the formatter and the I2S ++ * divider is enabled/disabled in "hw_params"/"hw_free", respectively. ++ */ ++ ret = clk_prepare_enable(aiu->i2s.clks[PCLK].clk); ++ if (ret) { ++ dev_err(dai->dev, "failed to enable PCLK: %d\n", ret); ++ return ret; ++ } ++ ret = clk_prepare_enable(aiu->i2s.clks[MIXER].clk); ++ if (ret) { ++ dev_err(dai->dev, "failed to enable MIXER: %d\n", ret); ++ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); ++ return ret; ++ } ++ ret = clk_prepare_enable(aiu->i2s.clks[AOCLK].clk); ++ if (ret) { ++ dev_err(dai->dev, "failed to enable AOCLK: %d\n", ret); ++ clk_disable_unprepare(aiu->i2s.clks[MIXER].clk); ++ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); ++ return ret; ++ } ++ ++ /* ++ * We're always operating in split mode for the playback stream. ++ * ++ * This setting arguably belong to the 'aiu-formatter', but it's kept ++ * here for backward compatibility reason. At reset the I2S encoder ++ * operates in normal mode which would only support 8ch, but by default ++ * only 2ch are enabled. If a playback stream is started without ++ * changing to split mode, then the I2S encoder doesn't consume audio ++ * samples and the playback fails. ++ * Moving this to 'aiu-formatter' would cause the split mode to be set ++ * only when the formatter is enabled, which doesn't happen at boot as ++ * the default value for "HDMI CTRL SRC" is "DISABLED". ++ */ ++ ret = snd_soc_component_update_bits(dai->component, AIU_I2S_SOURCE_DESC, ++ AIU_I2S_SOURCE_DESC_MODE_SPLIT, ++ AIU_I2S_SOURCE_DESC_MODE_SPLIT); ++ if (ret < 0) ++ dev_err(dai->dev, "failed to update AIU_I2S_SOURCE_DESC: %d", ret); ++ ++ return 0; + } + + static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream, +@@ -321,14 +449,89 @@ static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream, + { + struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); + +- clk_bulk_disable_unprepare(aiu->i2s.clk_num, aiu->i2s.clks); ++ clk_disable_unprepare(aiu->i2s.clks[AOCLK].clk); ++ clk_disable_unprepare(aiu->i2s.clks[MIXER].clk); ++ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); ++} ++ ++static int aiu_encoder_i2s_trigger(struct snd_pcm_substream *substream, ++ int cmd, ++ struct snd_soc_dai *dai) ++{ ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); ++ int ret; ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ ret = gx_stream_start(ts); ++ break; ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ case SNDRV_PCM_TRIGGER_STOP: ++ gx_stream_stop(ts); ++ ret = 0; ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int aiu_encoder_i2s_remove_dai(struct snd_soc_dai *dai) ++{ ++ int stream; ++ ++ for_each_pcm_streams(stream) { ++ struct gx_stream *ts; ++ ++ ts = snd_soc_dai_dma_data_get(dai, stream); ++ if (ts) ++ gx_stream_free(ts); ++ ++ snd_soc_dai_dma_data_set(dai, stream, NULL); ++ } ++ ++ return 0; ++} ++ ++static int aiu_encoder_i2s_probe_dai(struct snd_soc_dai *dai) ++{ ++ struct aiu *aiu = snd_soc_dai_get_drvdata(dai); ++ struct gx_iface *iface = &aiu->i2s.iface; ++ int stream; ++ ++ for_each_pcm_streams(stream) { ++ struct gx_stream *ts; ++ ++ if (!snd_soc_dai_get_widget(dai, stream)) ++ continue; ++ ++ ts = gx_stream_alloc(iface); ++ if (!ts) { ++ aiu_encoder_i2s_remove_dai(dai); ++ return -ENOMEM; ++ } ++ snd_soc_dai_dma_data_set(dai, stream, ts); ++ } ++ ++ iface->mclk = aiu->i2s.clks[MCLK].clk; ++ iface->mclk_rate = clk_get_rate(iface->mclk); ++ ++ return 0; + } + + const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops = { ++ .probe = aiu_encoder_i2s_probe_dai, ++ .remove = aiu_encoder_i2s_remove_dai, + .hw_params = aiu_encoder_i2s_hw_params, ++ .prepare = aiu_encoder_i2s_prepare, + .hw_free = aiu_encoder_i2s_hw_free, + .set_fmt = aiu_encoder_i2s_set_fmt, + .set_sysclk = aiu_encoder_i2s_set_sysclk, + .startup = aiu_encoder_i2s_startup, + .shutdown = aiu_encoder_i2s_shutdown, ++ .trigger = aiu_encoder_i2s_trigger, + }; +diff --git a/sound/soc/meson/aiu-formatter-i2s.c b/sound/soc/meson/aiu-formatter-i2s.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/aiu-formatter-i2s.c +@@ -0,0 +1,103 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// ++// Copyright (c) 2026 BayLibre, SAS. ++// Author: Valerio Setti ++ ++#include ++#include ++#include ++ ++#include "aiu.h" ++#include "gx-formatter.h" ++ ++#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0) ++#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5) ++#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9) ++ ++#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2) ++ ++static struct snd_soc_dai * ++aiu_formatter_i2s_get_be(struct snd_soc_dapm_widget *w) ++{ ++ struct snd_soc_dapm_path *p; ++ struct snd_soc_dai *be; ++ ++ snd_soc_dapm_widget_for_each_sink_path(w, p) { ++ if (!p->connect) ++ continue; ++ ++ if (p->sink->id == snd_soc_dapm_dai_in) ++ return (struct snd_soc_dai *)p->sink->priv; ++ ++ be = aiu_formatter_i2s_get_be(p->sink); ++ if (be) ++ return be; ++ } ++ ++ return NULL; ++} ++ ++static struct gx_stream * ++aiu_formatter_i2s_get_stream(struct snd_soc_dapm_widget *w) ++{ ++ struct snd_soc_dai *be = aiu_formatter_i2s_get_be(w); ++ ++ if (!be) ++ return NULL; ++ ++ return snd_soc_dai_dma_data_get_playback(be); ++} ++ ++static int aiu_formatter_i2s_prepare(struct regmap *map, ++ const struct gx_formatter_hw *quirks, ++ struct gx_stream *ts) ++{ ++ /* Always operate in split (classic interleaved) mode */ ++ unsigned int desc = 0; ++ ++ /* ++ * Pipeline reset is already implemented in aiu_fifo_i2s_trigger() at ++ * trigger time. ++ */ ++ ++ switch (ts->physical_width) { ++ case 16: /* Nothing to do */ ++ break; ++ ++ case 32: ++ desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT | ++ AIU_I2S_SOURCE_DESC_MODE_32BIT); ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ switch (ts->channels) { ++ case 2: /* Nothing to do */ ++ break; ++ case 8: ++ desc |= AIU_I2S_SOURCE_DESC_MODE_8CH; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ regmap_update_bits(map, AIU_I2S_SOURCE_DESC, ++ AIU_I2S_SOURCE_DESC_MODE_8CH | ++ AIU_I2S_SOURCE_DESC_MODE_24BIT | ++ AIU_I2S_SOURCE_DESC_MODE_32BIT, ++ desc); ++ ++ /* Send data MSB first */ ++ regmap_update_bits(map, AIU_I2S_DAC_CFG, ++ AIU_I2S_DAC_CFG_MSB_FIRST, ++ AIU_I2S_DAC_CFG_MSB_FIRST); ++ ++ return 0; ++} ++ ++const struct gx_formatter_ops aiu_formatter_i2s_ops = { ++ .get_stream = aiu_formatter_i2s_get_stream, ++ .prepare = aiu_formatter_i2s_prepare, ++}; +diff --git a/sound/soc/meson/aiu.c b/sound/soc/meson/aiu.c +--- a/sound/soc/meson/aiu.c ++++ b/sound/soc/meson/aiu.c +@@ -29,13 +29,22 @@ static SOC_ENUM_SINGLE_DECL(aiu_spdif_encode_sel_enum, AIU_I2S_MISC, + static const struct snd_kcontrol_new aiu_spdif_encode_mux = + SOC_DAPM_ENUM("SPDIF Buffer Src", aiu_spdif_encode_sel_enum); + +-static const struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = { +- SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0, +- &aiu_spdif_encode_mux), ++#define AIU_WIDGET_SPDIF_SRC_SEL 0 ++#define AIU_WIDGET_I2S_FORMATTER 1 ++ ++static struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = { ++ [AIU_WIDGET_SPDIF_SRC_SEL] = ++ SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0, ++ &aiu_spdif_encode_mux), ++ [AIU_WIDGET_I2S_FORMATTER] = ++ SND_SOC_DAPM_PGA_E("I2S Formatter", SND_SOC_NOPM, 0, 0, NULL, 0, ++ gx_formatter_event, ++ (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)), + }; + + static const struct snd_soc_dapm_route aiu_cpu_dapm_routes[] = { +- { "I2S Encoder Playback", NULL, "I2S FIFO Playback" }, ++ { "I2S Formatter", NULL, "I2S FIFO Playback" }, ++ { "I2S Encoder Playback", NULL, "I2S Formatter" }, + { "SPDIF SRC SEL", "SPDIF", "SPDIF FIFO Playback" }, + { "SPDIF SRC SEL", "I2S", "I2S FIFO Playback" }, + { "SPDIF Encoder Playback", NULL, "SPDIF SRC SEL" }, +@@ -145,6 +154,7 @@ static struct snd_soc_dai_driver aiu_cpu_dai_drv[] = { + .formats = AIU_FORMATS, + }, + .ops = &aiu_encoder_i2s_dai_ops, ++ .symmetric_rate = 1, + }, + [CPU_SPDIF_ENCODER] = { + .name = "SPDIF Encoder", +@@ -172,6 +182,11 @@ static const struct regmap_config aiu_regmap_cfg = { + .max_register = 0x2ac, + }; + ++static const struct gx_formatter_driver aiu_formatter_i2s_drv = { ++ .regmap_cfg = &aiu_regmap_cfg, ++ .ops = &aiu_formatter_i2s_ops, ++}; ++ + static int aiu_clk_bulk_get(struct device *dev, + const char * const *ids, + unsigned int num, +@@ -282,6 +297,14 @@ static int aiu_probe(struct platform_device *pdev) + if (ret) + return ret; + ++ /* Allocate the aiu-formatter into its widget */ ++ ret = gx_formatter_create(dev, &aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER], ++ &aiu_formatter_i2s_drv, map); ++ if (ret) { ++ dev_err(dev, "Failed to allocate aiu formatter\n"); ++ goto err; ++ } ++ + /* Register the cpu component of the aiu */ + ret = snd_soc_register_component(dev, &aiu_cpu_component, + aiu_cpu_dai_drv, +@@ -310,12 +333,14 @@ static int aiu_probe(struct platform_device *pdev) + + return 0; + err: ++ gx_formatter_free(&aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER]); + snd_soc_unregister_component(dev); + return ret; + } + + static void aiu_remove(struct platform_device *pdev) + { ++ gx_formatter_free(&aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER]); + snd_soc_unregister_component(&pdev->dev); + } + +diff --git a/sound/soc/meson/aiu.h b/sound/soc/meson/aiu.h +--- a/sound/soc/meson/aiu.h ++++ b/sound/soc/meson/aiu.h +@@ -7,6 +7,8 @@ + #ifndef _MESON_AIU_H + #define _MESON_AIU_H + ++#include "gx-formatter.h" ++ + struct clk; + struct clk_bulk_data; + struct device; +@@ -25,6 +27,7 @@ struct aiu_interface { + struct clk_bulk_data *clks; + unsigned int clk_num; + int irq; ++ struct gx_iface iface; + }; + + struct aiu_platform_data { +@@ -58,6 +61,7 @@ extern const struct snd_soc_dai_ops aiu_fifo_i2s_dai_ops; + extern const struct snd_soc_dai_ops aiu_fifo_spdif_dai_ops; + extern const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops; + extern const struct snd_soc_dai_ops aiu_encoder_spdif_dai_ops; ++extern const struct gx_formatter_ops aiu_formatter_i2s_ops; + + #define AIU_IEC958_BPF 0x000 + #define AIU_958_MISC 0x010 +diff --git a/sound/soc/meson/axg-card.c b/sound/soc/meson/axg-card.c +--- a/sound/soc/meson/axg-card.c ++++ b/sound/soc/meson/axg-card.c +@@ -107,6 +107,7 @@ static int axg_card_add_tdm_loopback(struct snd_soc_card *card, + struct snd_soc_dai_link *pad; + struct snd_soc_dai_link *lb; + struct snd_soc_dai_link_component *dlc; ++ struct device *dev = card->dev; + int ret; + + /* extend links */ +@@ -117,11 +118,11 @@ static int axg_card_add_tdm_loopback(struct snd_soc_card *card, + pad = &card->dai_link[*index]; + lb = &card->dai_link[*index + 1]; + +- lb->name = devm_kasprintf(card->dev, GFP_KERNEL, "%s-lb", pad->name); ++ lb->name = devm_kasprintf(dev, GFP_KERNEL, "%s-lb", pad->name); + if (!lb->name) + return -ENOMEM; + +- dlc = devm_kzalloc(card->dev, sizeof(*dlc), GFP_KERNEL); ++ dlc = devm_kzalloc(dev, sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + +@@ -158,13 +159,14 @@ static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + struct device_node *node, + struct axg_dai_link_tdm_data *be) + { ++ struct device *dev = card->dev; + char propname[32]; + u32 tx, rx; + int i; + +- be->tx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, ++ be->tx_mask = devm_kcalloc(dev, AXG_TDM_NUM_LANES, + sizeof(*be->tx_mask), GFP_KERNEL); +- be->rx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, ++ be->rx_mask = devm_kcalloc(dev, AXG_TDM_NUM_LANES, + sizeof(*be->rx_mask), GFP_KERNEL); + if (!be->tx_mask || !be->rx_mask) + return -ENOMEM; +@@ -191,7 +193,7 @@ static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + + /* ... but the interface should at least have one direction */ + if (!tx && !rx) { +- dev_err(card->dev, "tdm link has no cpu slots\n"); ++ dev_err(dev, "tdm link has no cpu slots\n"); + return -EINVAL; + } + +@@ -207,7 +209,7 @@ static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + * Error if the slots can't accommodate the largest mask or + * if it is just too big + */ +- dev_err(card->dev, "bad slot number\n"); ++ dev_err(dev, "bad slot number\n"); + return -EINVAL; + } + +@@ -222,8 +224,9 @@ static int axg_card_parse_codecs_masks(struct snd_soc_card *card, + struct axg_dai_link_tdm_data *be) + { + struct axg_dai_link_tdm_mask *codec_mask; ++ struct device *dev = card->dev; + +- codec_mask = devm_kcalloc(card->dev, link->num_codecs, ++ codec_mask = devm_kcalloc(dev, link->num_codecs, + sizeof(*codec_mask), GFP_KERNEL); + if (!codec_mask) + return -ENOMEM; +@@ -249,10 +252,11 @@ static int axg_card_parse_tdm(struct snd_soc_card *card, + struct meson_card *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link *link = &card->dai_link[*index]; + struct axg_dai_link_tdm_data *be; ++ struct device *dev = card->dev; + int ret; + + /* Allocate tdm link parameters */ +- be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); ++ be = devm_kzalloc(dev, sizeof(*be), GFP_KERNEL); + if (!be) + return -ENOMEM; + priv->link_data[*index] = be; +@@ -266,7 +270,7 @@ static int axg_card_parse_tdm(struct snd_soc_card *card, + + ret = axg_card_parse_cpu_tdm_slots(card, link, node, be); + if (ret) { +- dev_err(card->dev, "error parsing tdm link slots\n"); ++ dev_err(dev, "error parsing tdm link slots\n"); + return ret; + } + +@@ -310,9 +314,10 @@ static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np, + { + struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; + struct snd_soc_dai_link_component *cpu; ++ struct device *dev = card->dev; + int ret; + +- cpu = devm_kzalloc(card->dev, sizeof(*cpu), GFP_KERNEL); ++ cpu = devm_kzalloc(dev, sizeof(*cpu), GFP_KERNEL); + if (!cpu) + return -ENOMEM; + +diff --git a/sound/soc/meson/g12a-toacodec.c b/sound/soc/meson/g12a-toacodec.c +--- a/sound/soc/meson/g12a-toacodec.c ++++ b/sound/soc/meson/g12a-toacodec.c +@@ -312,7 +312,7 @@ static int g12a_toacodec_probe(struct platform_device *pdev) + + ret = device_reset(dev); + if (ret) +- return ret; ++ return dev_err_probe(dev, ret, "failed to reset device\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) +diff --git a/sound/soc/meson/g12a-tohdmitx.c b/sound/soc/meson/g12a-tohdmitx.c +--- a/sound/soc/meson/g12a-tohdmitx.c ++++ b/sound/soc/meson/g12a-tohdmitx.c +@@ -251,7 +251,7 @@ static int g12a_tohdmitx_probe(struct platform_device *pdev) + + ret = device_reset(dev); + if (ret) +- return ret; ++ return dev_err_probe(dev, ret, "failed to reset device\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) +diff --git a/sound/soc/meson/gx-card.c b/sound/soc/meson/gx-card.c +--- a/sound/soc/meson/gx-card.c ++++ b/sound/soc/meson/gx-card.c +@@ -48,9 +48,10 @@ static int gx_card_parse_i2s(struct snd_soc_card *card, + struct meson_card *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link *link = &card->dai_link[*index]; + struct gx_dai_link_i2s_data *be; ++ struct device *dev = card->dev; + + /* Allocate i2s link parameters */ +- be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); ++ be = devm_kzalloc(dev, sizeof(*be), GFP_KERNEL); + if (!be) + return -ENOMEM; + priv->link_data[*index] = be; +@@ -81,9 +82,10 @@ static int gx_card_add_link(struct snd_soc_card *card, struct device_node *np, + { + struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; + struct snd_soc_dai_link_component *cpu; ++ struct device *dev = card->dev; + int ret; + +- cpu = devm_kzalloc(card->dev, sizeof(*cpu), GFP_KERNEL); ++ cpu = devm_kzalloc(dev, sizeof(*cpu), GFP_KERNEL); + if (!cpu) + return -ENOMEM; + +diff --git a/sound/soc/meson/gx-formatter.c b/sound/soc/meson/gx-formatter.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/gx-formatter.c +@@ -0,0 +1,282 @@ ++// SPDX-License-Identifier: (GPL-2.0 OR MIT) ++// ++// Copyright (c) 2026 BayLibre, SAS. ++// Author: Valerio Setti ++ ++#include ++#include ++#include ++#include ++ ++#include "gx-formatter.h" ++ ++struct gx_formatter { ++ struct list_head list; ++ struct gx_stream *stream; ++ const struct gx_formatter_driver *drv; ++ bool enabled; ++ struct regmap *map; ++}; ++ ++static int gx_formatter_enable(struct gx_formatter *formatter) ++{ ++ int ret; ++ ++ /* Do nothing if the formatter is already enabled */ ++ if (formatter->enabled) ++ return 0; ++ ++ /* Setup the stream parameter in the formatter */ ++ if (formatter->drv->ops->prepare) { ++ ret = formatter->drv->ops->prepare(formatter->map, ++ formatter->drv->quirks, ++ formatter->stream); ++ if (ret) ++ return ret; ++ } ++ ++ /* Finally, actually enable the formatter */ ++ if (formatter->drv->ops->enable) ++ formatter->drv->ops->enable(formatter->map); ++ ++ formatter->enabled = true; ++ ++ return 0; ++} ++ ++static void gx_formatter_disable(struct gx_formatter *formatter) ++{ ++ /* Do nothing if the formatter is already disabled */ ++ if (!formatter->enabled) ++ return; ++ ++ if (formatter->drv->ops->disable) ++ formatter->drv->ops->disable(formatter->map); ++ ++ formatter->enabled = false; ++} ++ ++static int gx_formatter_attach(struct gx_formatter *formatter) ++{ ++ struct gx_stream *ts = formatter->stream; ++ int ret = 0; ++ ++ mutex_lock(&ts->lock); ++ ++ /* Catch up if the stream is already running when we attach */ ++ if (ts->ready) { ++ ret = gx_formatter_enable(formatter); ++ if (ret) { ++ pr_err("failed to enable formatter\n"); ++ goto out; ++ } ++ } ++ ++ list_add_tail(&formatter->list, &ts->formatter_list); ++out: ++ mutex_unlock(&ts->lock); ++ return ret; ++} ++ ++static void gx_formatter_detach(struct gx_formatter *formatter) ++{ ++ struct gx_stream *ts = formatter->stream; ++ ++ if (!ts) ++ return; ++ ++ mutex_lock(&ts->lock); ++ list_del(&formatter->list); ++ mutex_unlock(&ts->lock); ++ ++ gx_formatter_disable(formatter); ++} ++ ++static int gx_formatter_power_up(struct gx_formatter *formatter, ++ struct snd_soc_dapm_widget *w) ++{ ++ struct gx_stream *ts = formatter->drv->ops->get_stream(w); ++ int ret; ++ ++ /* ++ * If we don't get a stream at this stage, it would mean that the ++ * widget is powering up but is not attached to any backend DAI. ++ * It should not happen, ever ! ++ */ ++ if (WARN_ON(!ts)) ++ return -ENODEV; ++ ++ formatter->stream = ts; ++ INIT_LIST_HEAD(&formatter->list); ++ ret = gx_formatter_attach(formatter); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static void gx_formatter_power_down(struct gx_formatter *formatter) ++{ ++ gx_formatter_detach(formatter); ++ formatter->stream = NULL; ++} ++ ++int gx_formatter_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *control, ++ int event) ++{ ++ struct snd_soc_component *c; ++ struct gx_formatter *formatter; ++ int ret = 0; ++ ++ c = snd_soc_dapm_to_component(w->dapm); ++ ++ if (w->priv) ++ formatter = w->priv; ++ else ++ formatter = snd_soc_component_get_drvdata(c); ++ ++ switch (event) { ++ case SND_SOC_DAPM_PRE_PMU: ++ ret = gx_formatter_power_up(formatter, w); ++ break; ++ ++ case SND_SOC_DAPM_PRE_PMD: ++ gx_formatter_power_down(formatter); ++ break; ++ ++ default: ++ dev_err(c->dev, "Unexpected event %d\n", event); ++ return -EINVAL; ++ } ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(gx_formatter_event); ++ ++int gx_formatter_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ const struct gx_formatter_driver *drv; ++ struct gx_formatter *formatter; ++ void __iomem *regs; ++ ++ drv = of_device_get_match_data(dev); ++ if (!drv) { ++ dev_err(dev, "failed to match device\n"); ++ return -ENODEV; ++ } ++ ++ formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); ++ if (!formatter) ++ return -ENOMEM; ++ platform_set_drvdata(pdev, formatter); ++ formatter->drv = drv; ++ ++ regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); ++ ++ formatter->map = devm_regmap_init_mmio(dev, regs, drv->regmap_cfg); ++ if (IS_ERR(formatter->map)) { ++ dev_err(dev, "failed to init regmap: %ld\n", ++ PTR_ERR(formatter->map)); ++ return PTR_ERR(formatter->map); ++ } ++ ++ return devm_snd_soc_register_component(dev, drv->component_drv, ++ NULL, 0); ++} ++EXPORT_SYMBOL_GPL(gx_formatter_probe); ++ ++int gx_formatter_create(struct device *dev, ++ struct snd_soc_dapm_widget *w, ++ const struct gx_formatter_driver *drv, ++ struct regmap *regmap) ++{ ++ struct gx_formatter *formatter; ++ ++ formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); ++ if (!formatter) ++ return -ENOMEM; ++ ++ formatter->drv = drv; ++ formatter->map = regmap; ++ ++ w->priv = formatter; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(gx_formatter_create); ++ ++int gx_stream_start(struct gx_stream *ts) ++{ ++ struct gx_formatter *formatter; ++ int ret = 0; ++ ++ mutex_lock(&ts->lock); ++ ++ /* Start all the formatters attached to the stream */ ++ list_for_each_entry(formatter, &ts->formatter_list, list) { ++ ret = gx_formatter_enable(formatter); ++ if (ret) { ++ pr_err("failed to enable formatter\n"); ++ goto out; ++ } ++ } ++ ++ ts->ready = true; ++ ++out: ++ mutex_unlock(&ts->lock); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(gx_stream_start); ++ ++void gx_stream_stop(struct gx_stream *ts) ++{ ++ struct gx_formatter *formatter; ++ ++ mutex_lock(&ts->lock); ++ ts->ready = false; ++ ++ /* Stop all the formatters attached to the stream */ ++ list_for_each_entry(formatter, &ts->formatter_list, list) { ++ gx_formatter_disable(formatter); ++ } ++ ++ mutex_unlock(&ts->lock); ++} ++EXPORT_SYMBOL_GPL(gx_stream_stop); ++ ++struct gx_stream *gx_stream_alloc(struct gx_iface *iface) ++{ ++ struct gx_stream *ts; ++ ++ ts = kzalloc(sizeof(*ts), GFP_KERNEL); ++ if (ts) { ++ INIT_LIST_HEAD(&ts->formatter_list); ++ mutex_init(&ts->lock); ++ ts->iface = iface; ++ } ++ ++ return ts; ++} ++EXPORT_SYMBOL_GPL(gx_stream_alloc); ++ ++void gx_stream_free(struct gx_stream *ts) ++{ ++ /* ++ * If the list is not empty, it would mean that one of the formatter ++ * widget is still powered and attached to the interface while we ++ * are removing the TDM DAI. It should not be possible ++ */ ++ WARN_ON(!list_empty(&ts->formatter_list)); ++ mutex_destroy(&ts->lock); ++ kfree(ts); ++} ++EXPORT_SYMBOL_GPL(gx_stream_free); ++ ++MODULE_DESCRIPTION("Amlogic GX formatter driver"); ++MODULE_AUTHOR("Valerio Setti "); ++MODULE_LICENSE("GPL"); +diff --git a/sound/soc/meson/gx-formatter.h b/sound/soc/meson/gx-formatter.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/gx-formatter.h +@@ -0,0 +1,56 @@ ++/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ ++/* ++ * Copyright (c) 2026 Baylibre SAS. ++ * Author: Valerio Setti ++ */ ++ ++#ifndef _MESON_GX_FORMATTER_H ++#define _MESON_GX_FORMATTER_H ++ ++#include "gx-interface.h" ++ ++struct platform_device; ++struct regmap; ++struct snd_soc_dapm_widget; ++struct snd_kcontrol; ++ ++struct gx_formatter_hw { ++ unsigned int skew_offset; ++}; ++ ++struct gx_formatter_ops { ++ struct gx_stream *(*get_stream)(struct snd_soc_dapm_widget *w); ++ void (*enable)(struct regmap *map); ++ void (*disable)(struct regmap *map); ++ int (*prepare)(struct regmap *map, ++ const struct gx_formatter_hw *quirks, ++ struct gx_stream *ts); ++}; ++ ++struct gx_formatter_driver { ++ const struct snd_soc_component_driver *component_drv; ++ const struct regmap_config *regmap_cfg; ++ const struct gx_formatter_ops *ops; ++ const struct gx_formatter_hw *quirks; ++}; ++ ++int gx_formatter_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *control, ++ int event); ++int gx_formatter_probe(struct platform_device *pdev); ++ ++int gx_formatter_create(struct device *dev, ++ struct snd_soc_dapm_widget *w, ++ const struct gx_formatter_driver *drv, ++ struct regmap *regmap); ++ ++/* ++ * Formatter data is already freed when the associated device is removed, ++ * so we just need to remove the pointer from the widget. ++ */ ++static inline void gx_formatter_free(struct snd_soc_dapm_widget *w) ++{ ++ w->priv = NULL; ++} ++ ++#endif /* _MESON_GX_FORMATTER_H */ +diff --git a/sound/soc/meson/gx-interface.h b/sound/soc/meson/gx-interface.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/gx-interface.h +@@ -0,0 +1,42 @@ ++/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ ++/* ++ * Copyright (c) 2026 Baylibre SAS. ++ * Author: Valerio Setti ++ */ ++ ++#ifndef _MESON_GX_INTERFACE_H ++#define _MESON_GX_INTERFACE_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++struct gx_iface { ++ struct clk *mclk; ++ unsigned long mclk_rate; ++ ++ /* format is common to all the DAIs of the iface */ ++ unsigned int fmt; ++}; ++ ++struct gx_stream { ++ struct gx_iface *iface; ++ struct list_head formatter_list; ++ struct mutex lock; ++ unsigned int channels; ++ unsigned int width; ++ unsigned int physical_width; ++ bool ready; ++ ++ /* For continuous clock tracking */ ++ bool clk_enabled; ++}; ++ ++struct gx_stream *gx_stream_alloc(struct gx_iface *iface); ++void gx_stream_free(struct gx_stream *ts); ++int gx_stream_start(struct gx_stream *ts); ++void gx_stream_stop(struct gx_stream *ts); ++ ++#endif /* _MESON_GX_INTERFACE_H */ +diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-utils.c +--- a/sound/soc/meson/meson-card-utils.c ++++ b/sound/soc/meson/meson-card-utils.c +@@ -50,25 +50,20 @@ int meson_card_reallocate_links(struct snd_soc_card *card, + num_links * sizeof(*priv->card.dai_link), + GFP_KERNEL | __GFP_ZERO); + if (!links) +- goto err_links; ++ return -ENOMEM; ++ ++ priv->card.dai_link = links; ++ priv->card.num_links = num_links; + + ldata = krealloc(priv->link_data, + num_links * sizeof(*priv->link_data), + GFP_KERNEL | __GFP_ZERO); ++ /* meson_card_clean_references() will free the links on this error path */ + if (!ldata) +- goto err_ldata; ++ return -ENOMEM; + +- priv->card.dai_link = links; + priv->link_data = ldata; +- priv->card.num_links = num_links; + return 0; +- +-err_ldata: +- kfree(links); +-err_links: +- dev_err(priv->card.dev, "failed to allocate links\n"); +- return -ENOMEM; +- + } + EXPORT_SYMBOL_GPL(meson_card_reallocate_links); + +@@ -76,6 +71,7 @@ int meson_card_parse_dai(struct snd_soc_card *card, + struct device_node *node, + struct snd_soc_dai_link_component *dlc) + { ++ struct device *dev = card->dev; + int ret; + + if (!dlc || !node) +@@ -83,7 +79,7 @@ int meson_card_parse_dai(struct snd_soc_card *card, + + ret = snd_soc_of_get_dlc(node, NULL, dlc, 0); + if (ret) +- return dev_err_probe(card->dev, ret, "can't parse dai\n"); ++ return dev_err_probe(dev, ret, "can't parse dai\n"); + + return ret; + } +@@ -94,7 +90,8 @@ static int meson_card_set_link_name(struct snd_soc_card *card, + struct device_node *node, + const char *prefix) + { +- char *name = devm_kasprintf(card->dev, GFP_KERNEL, "%s.%s", ++ struct device *dev = card->dev; ++ char *name = devm_kasprintf(dev, GFP_KERNEL, "%s.%s", + prefix, node->full_name); + if (!name) + return -ENOMEM; +@@ -137,16 +134,17 @@ int meson_card_set_be_link(struct snd_soc_card *card, + struct device_node *node) + { + struct snd_soc_dai_link_component *codec; ++ struct device *dev = card->dev; + int ret, num_codecs; + + num_codecs = of_get_child_count(node); + if (!num_codecs) { +- dev_err(card->dev, "be link %s has no codec\n", ++ dev_err(dev, "be link %s has no codec\n", + node->full_name); + return -EINVAL; + } + +- codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec), GFP_KERNEL); ++ codec = devm_kcalloc(dev, num_codecs, sizeof(*codec), GFP_KERNEL); + if (!codec) + return -ENOMEM; + +@@ -163,7 +161,7 @@ int meson_card_set_be_link(struct snd_soc_card *card, + + ret = meson_card_set_link_name(card, link, node, "be"); + if (ret) +- dev_err(card->dev, "error setting %pOFn link name\n", node); ++ dev_err(dev, "error setting %pOFn link name\n", node); + + return ret; + } +@@ -194,12 +192,13 @@ EXPORT_SYMBOL_GPL(meson_card_set_fe_link); + static int meson_card_add_links(struct snd_soc_card *card) + { + struct meson_card *priv = snd_soc_card_get_drvdata(card); +- struct device_node *node = card->dev->of_node; ++ struct device *dev = card->dev; ++ struct device_node *node = dev->of_node; + int num, i, ret; + + num = of_get_child_count(node); + if (!num) { +- dev_err(card->dev, "card has no links\n"); ++ dev_err(dev, "card has no links\n"); + return -EINVAL; + } + +@@ -224,8 +223,10 @@ static int meson_card_parse_of_optional(struct snd_soc_card *card, + int (*func)(struct snd_soc_card *c, + const char *p)) + { ++ struct device *dev = card->dev; ++ + /* If property is not provided, don't fail ... */ +- if (!of_property_present(card->dev->of_node, propname)) ++ if (!of_property_present(dev->of_node, propname)) + return 0; + + /* ... but do fail if it is provided and the parsing fails */ +diff --git a/sound/soc/meson/meson-codec-glue.c b/sound/soc/meson/meson-codec-glue.c +--- a/sound/soc/meson/meson-codec-glue.c ++++ b/sound/soc/meson/meson-codec-glue.c +@@ -74,7 +74,7 @@ int meson_codec_glue_input_hw_params(struct snd_pcm_substream *substream, + data->params.rates = snd_pcm_rate_to_rate_bit(params_rate(params)); + data->params.rate_min = params_rate(params); + data->params.rate_max = params_rate(params); +- data->params.formats = 1ULL << (__force int) params_format(params); ++ data->params.formats = 1ULL << params_format(params); + data->params.channels_min = params_channels(params); + data->params.channels_max = params_channels(params); + data->params.sig_bits = dai->driver->playback.sig_bits; +diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c +--- a/sound/soc/meson/t9015.c ++++ b/sound/soc/meson/t9015.c +@@ -78,8 +78,14 @@ static int t9015_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + return 0; + } + ++static const u64 t9015_dai_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J; ++ + static const struct snd_soc_dai_ops t9015_dai_ops = { + .set_fmt = t9015_dai_set_fmt, ++ .auto_selectable_formats = &t9015_dai_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver t9015_dai = { +@@ -265,10 +271,8 @@ static int t9015_probe(struct platform_device *pdev) + return dev_err_probe(dev, PTR_ERR(priv->avdd), "failed to AVDD\n"); + + ret = device_reset(dev); +- if (ret) { +- dev_err(dev, "reset failed\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to reset device\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) { +diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c +--- a/sound/soc/mxs/mxs-saif.c ++++ b/sound/soc/mxs/mxs-saif.c +@@ -826,12 +826,9 @@ static int mxs_saif_probe(struct platform_device *pdev) + mxs_saif[saif->id] = saif; + + saif->clk = devm_clk_get(&pdev->dev, NULL); +- if (IS_ERR(saif->clk)) { +- ret = PTR_ERR(saif->clk); +- dev_err(&pdev->dev, "Cannot get the clock: %d\n", +- ret); +- return ret; +- } ++ if (IS_ERR(saif->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(saif->clk), ++ "Cannot get the clock\n"); + + saif->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(saif->base)) +@@ -844,10 +841,8 @@ static int mxs_saif_probe(struct platform_device *pdev) + saif->dev = &pdev->dev; + ret = devm_request_irq(&pdev->dev, irq, mxs_saif_irq, 0, + dev_name(&pdev->dev), saif); +- if (ret) { +- dev_err(&pdev->dev, "failed to request irq\n"); ++ if (ret) + return ret; +- } + + platform_set_drvdata(pdev, saif); + +@@ -860,16 +855,12 @@ static int mxs_saif_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, &mxs_saif_component, + &mxs_saif_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "register DAI failed\n"); ++ if (ret) + return ret; +- } + + ret = mxs_pcm_platform_register(&pdev->dev); +- if (ret) { +- dev_err(&pdev->dev, "register PCM failed: %d\n", ret); ++ if (ret) + return ret; +- } + + return 0; + } +diff --git a/sound/soc/mxs/mxs-sgtl5000.c b/sound/soc/mxs/mxs-sgtl5000.c +--- a/sound/soc/mxs/mxs-sgtl5000.c ++++ b/sound/soc/mxs/mxs-sgtl5000.c +@@ -155,8 +155,6 @@ static int mxs_sgtl5000_probe(struct platform_device *pdev) + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) { +- dev_err(&pdev->dev, "failed to parse audio-routing (%d)\n", +- ret); + mxs_saif_put_mclk(0); + return ret; + } +diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c +--- a/sound/soc/pxa/pxa-ssp.c ++++ b/sound/soc/pxa/pxa-ssp.c +@@ -769,13 +769,10 @@ static int pxa_ssp_probe(struct snd_soc_dai *dai) + goto err_priv; + } + +- priv->extclk = devm_clk_get(dev, "extclk"); ++ priv->extclk = devm_clk_get_optional(dev, "extclk"); + if (IS_ERR(priv->extclk)) { + ret = PTR_ERR(priv->extclk); +- if (ret == -EPROBE_DEFER) +- goto err_priv; +- +- priv->extclk = NULL; ++ goto err_priv; + } + } else { + priv->ssp = pxa_ssp_request(dai->id + 1, "SoC audio"); +diff --git a/sound/soc/pxa/pxa2xx-ac97-lib.c b/sound/soc/pxa/pxa2xx-ac97-lib.c +--- a/sound/soc/pxa/pxa2xx-ac97-lib.c ++++ b/sound/soc/pxa/pxa2xx-ac97-lib.c +@@ -316,10 +316,8 @@ int pxa2xx_ac97_hw_probe(struct platform_device *dev) + int irq; + + ac97_reg_base = devm_platform_ioremap_resource(dev, 0); +- if (IS_ERR(ac97_reg_base)) { +- dev_err(&dev->dev, "Missing MMIO resource\n"); ++ if (IS_ERR(ac97_reg_base)) + return PTR_ERR(ac97_reg_base); +- } + + if (cpu_is_pxa27x()) { + /* Assert reset using GPIOD_OUT_HIGH, because reset is GPIO_ACTIVE_LOW */ +diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c +--- a/sound/soc/qcom/common.c ++++ b/sound/soc/qcom/common.c +@@ -23,6 +23,161 @@ static const struct snd_soc_dapm_widget qcom_jack_snd_widgets[] = { + SND_SOC_DAPM_SPK("DP7 Jack", NULL), + }; + ++static struct device_node *qcom_snd_get_link_node(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_card *card = rtd->card; ++ struct of_phandle_args args; ++ int ret; ++ ++ if (!card->dev || !card->dev->of_node) ++ return NULL; ++ ++ for_each_available_child_of_node_scoped(card->dev->of_node, np) { ++ struct device_node *cpu_np __free(device_node) = ++ of_get_child_by_name(np, "cpu"); ++ ++ if (!cpu_np) ++ continue; ++ ++ ret = of_parse_phandle_with_args(cpu_np, "sound-dai", "#sound-dai-cells", 0, ++ &args); ++ if (ret) ++ continue; ++ ++ if (args.np == rtd->dai_link->cpus[0].of_node && ++ args.args_count == 1 && args.args[0] == cpu_dai->id) { ++ of_node_put(args.np); ++ return of_node_get(np); ++ } ++ ++ of_node_put(args.np); ++ } ++ ++ return NULL; ++} ++ ++static int qcom_snd_parse_tdm_slot(struct device_node *np, ++ struct qcom_snd_tdm_slot_cfg *cfg) ++{ ++ memset(cfg, 0, sizeof(*cfg)); ++ ++ return snd_soc_of_parse_tdm_slot(np, &cfg->tx_mask, &cfg->rx_mask, ++ &cfg->slots, &cfg->slot_width); ++} ++ ++static int qcom_snd_normalize_tdm_slots(struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ unsigned int slots; ++ unsigned int slot_width; ++ ++ if (cpu_cfg->slots && codec_cfg->slots && cpu_cfg->slots != codec_cfg->slots) ++ return -EINVAL; ++ ++ if (cpu_cfg->slot_width && codec_cfg->slot_width && ++ cpu_cfg->slot_width != codec_cfg->slot_width) ++ return -EINVAL; ++ ++ slots = cpu_cfg->slots ?: codec_cfg->slots; ++ if (!slots) ++ return 0; ++ ++ slot_width = cpu_cfg->slot_width ?: codec_cfg->slot_width; ++ if (!slot_width) ++ return -EINVAL; ++ ++ cpu_cfg->slots = slots; ++ codec_cfg->slots = slots; ++ cpu_cfg->slot_width = slot_width; ++ codec_cfg->slot_width = slot_width; ++ ++ return 0; ++} ++ ++static int qcom_snd_parse_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, ++ struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ struct device_node *link_np __free(device_node) = qcom_snd_get_link_node(rtd); ++ int ret; ++ ++ if (!link_np) ++ return -EINVAL; ++ ++ struct device_node *cpu_np __free(device_node) = ++ of_get_child_by_name(link_np, "cpu"); ++ struct device_node *codec_np __free(device_node) = ++ of_get_child_by_name(link_np, "codec"); ++ if (!cpu_np || !codec_np) ++ return -EINVAL; ++ ++ ret = qcom_snd_parse_tdm_slot(cpu_np, cpu_cfg); ++ if (ret) ++ return ret; ++ ++ return qcom_snd_parse_tdm_slot(codec_np, codec_cfg); ++} ++ ++int qcom_snd_get_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, ++ struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ int ret; ++ ++ ret = qcom_snd_parse_dai_tdm_slots(rtd, cpu_cfg, codec_cfg); ++ if (ret) ++ return ret; ++ ++ return qcom_snd_normalize_tdm_slots(cpu_cfg, codec_cfg); ++} ++EXPORT_SYMBOL_GPL(qcom_snd_get_dai_tdm_slots); ++ ++int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd, ++ const struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ const struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_dai *codec_dai; ++ int i; ++ int ret; ++ ++ if (!cpu_cfg->slots) ++ return 0; ++ ++ ret = snd_soc_dai_set_tdm_slot(cpu_dai, cpu_cfg->tx_mask, cpu_cfg->rx_mask, ++ cpu_cfg->slots, cpu_cfg->slot_width); ++ if (ret) ++ return ret; ++ ++ for_each_rtd_codec_dais(rtd, i, codec_dai) { ++ ret = snd_soc_dai_set_tdm_slot(codec_dai, ++ codec_cfg->tx_mask, ++ codec_cfg->rx_mask, ++ codec_cfg->slots, ++ codec_cfg->slot_width); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(qcom_snd_apply_dai_tdm_slots_cfg); ++ ++int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct qcom_snd_tdm_slot_cfg cpu_cfg; ++ struct qcom_snd_tdm_slot_cfg codec_cfg; ++ int ret; ++ ++ ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); ++ if (ret) ++ return ret == -EINVAL ? 0 : ret; ++ ++ return qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); ++} ++EXPORT_SYMBOL_GPL(qcom_snd_apply_dai_tdm_slots); ++ + int qcom_snd_parse_of(struct snd_soc_card *card) + { + struct device *dev = card->dev; +@@ -91,7 +246,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + + ret = of_property_read_string(np, "link-name", &link->name); + if (ret) { +- dev_err(card->dev, "error getting codec dai_link name\n"); ++ dev_err(dev, "error getting codec dai_link name\n"); + return ret; + } + +@@ -109,7 +264,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + + ret = snd_soc_of_get_dlc(cpu, &args, link->cpus, 0); + if (ret) { +- dev_err_probe(card->dev, ret, ++ dev_err_probe(dev, ret, + "%s: error getting cpu dai name\n", link->name); + return ret; + } +@@ -126,7 +281,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + "sound-dai", + 0); + if (!link->platforms->of_node) { +- dev_err(card->dev, "%s: platform dai not found\n", link->name); ++ dev_err(dev, "%s: platform dai not found\n", link->name); + return -EINVAL; + } + } else { +@@ -136,7 +291,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + if (codec) { + ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); + if (ret < 0) { +- dev_err_probe(card->dev, ret, ++ dev_err_probe(dev, ret, + "%s: codec dai not found\n", link->name); + return ret; + } +@@ -215,6 +370,7 @@ int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, + } + + switch (cpu_dai->id) { ++ case LPI_MI2S_RX_0: + case TX_CODEC_DMA_TX_0: + case TX_CODEC_DMA_TX_1: + case TX_CODEC_DMA_TX_2: +diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h +--- a/sound/soc/qcom/common.h ++++ b/sound/soc/qcom/common.h +@@ -7,9 +7,23 @@ + #include + #include + +-#define LPASS_MAX_PORT (SENARY_MI2S_TX + 1) ++#define LPASS_MAX_PORT (LPI_MI2S_TX_6 + 1) ++ ++struct qcom_snd_tdm_slot_cfg { ++ unsigned int tx_mask; ++ unsigned int rx_mask; ++ unsigned int slots; ++ unsigned int slot_width; ++}; + + int qcom_snd_parse_of(struct snd_soc_card *card); ++int qcom_snd_get_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, ++ struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg); ++int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd, ++ const struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ const struct qcom_snd_tdm_slot_cfg *codec_cfg); ++int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd); + int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, bool *jack_setup); + int qcom_snd_dp_jack_setup(struct snd_soc_pcm_runtime *rtd, +diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c +--- a/sound/soc/qcom/qdsp6/audioreach.c ++++ b/sound/soc/qcom/qdsp6/audioreach.c +@@ -152,6 +152,13 @@ struct apm_i2s_module_intf_cfg { + + #define APM_I2S_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_i2s_module_intf_cfg), 8) + ++struct apm_audio_if_module_intf_cfg { ++ struct apm_module_param_data param_data; ++ struct param_id_audio_if_intf_cfg cfg; ++} __packed; ++ ++#define APM_AUDIO_IF_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_audio_if_module_intf_cfg), 8) ++ + struct apm_module_hw_ep_mf_cfg { + struct apm_module_param_data param_data; + struct param_id_hw_ep_mf mf; +@@ -168,6 +175,13 @@ struct apm_module_frame_size_factor_cfg { + + #define APM_FS_CFG_PSIZE ALIGN(sizeof(struct apm_module_frame_size_factor_cfg), 8) + ++struct apm_module_hw_ep_frame_duration_cfg { ++ struct apm_module_param_data param_data; ++ struct param_id_hw_ep_frame_duration frame_duration; ++} __packed; ++ ++#define APM_HW_EP_FRAME_DURATION_PSIZE ALIGN(sizeof(struct apm_module_hw_ep_frame_duration_cfg), 8) ++ + struct apm_module_hw_ep_power_mode_cfg { + struct apm_module_param_data param_data; + struct param_id_hw_ep_power_mode_cfg power_mode; +@@ -703,6 +717,7 @@ static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph, + int pm_sz = APM_HW_EP_PMODE_CFG_PSIZE; + int size = ic_sz + ep_sz + fs_sz + pm_sz; + void *p; ++ int i; + + struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0); + if (IS_ERR(pkt)) +@@ -741,7 +756,12 @@ static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph, + + intf_cfg->cfg.lpaif_type = module->hw_interface_type; + intf_cfg->cfg.intf_index = module->hw_interface_idx; +- intf_cfg->cfg.active_channels_mask = (1 << cfg->num_channels) - 1; ++ intf_cfg->cfg.active_channels_mask = 0; ++ /* Convert the physical channel mapping into a bit field */ ++ for (i = 0; i < AR_PCM_MAX_NUM_CHANNEL; i++) ++ if (cfg->channel_map[i]) ++ intf_cfg->cfg.active_channels_mask |= BIT(i); ++ + p += ic_sz; + + pm_cfg = p; +@@ -840,7 +860,7 @@ static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, + uint32_t num_channels = cfg->num_channels; + int payload_size = APM_MFC_CFG_PSIZE(media_format, num_channels) + + APM_MODULE_PARAM_DATA_SIZE; +- int i; ++ int i, j; + void *p; + + struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); +@@ -860,8 +880,12 @@ static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, + media_format->sample_rate = cfg->sample_rate; + media_format->bit_width = cfg->bit_width; + media_format->num_channels = cfg->num_channels; +- for (i = 0; i < num_channels; i++) +- media_format->channel_mapping[i] = cfg->channel_map[i]; ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < cfg->num_channels; i++) { ++ if (!cfg->channel_map[i]) ++ continue; ++ media_format->channel_mapping[j++] = cfg->channel_map[i]; ++ } + + return q6apm_send_cmd_sync(graph->apm, pkt, 0); + } +@@ -1042,6 +1066,81 @@ static int audioreach_i2s_set_media_format(struct q6apm_graph *graph, + return q6apm_send_cmd_sync(graph->apm, pkt, 0); + } + ++static int audioreach_audio_if_set_media_format(struct q6apm_graph *graph, ++ const struct audioreach_module *module, ++ const struct audioreach_module_config *cfg) ++{ ++ struct apm_module_hw_ep_frame_duration_cfg *fd_cfg; ++ struct apm_module_param_data *param_data; ++ struct apm_audio_if_module_intf_cfg *intf_cfg; ++ struct apm_module_hw_ep_mf_cfg *hw_cfg; ++ int ic_sz = APM_AUDIO_IF_INTF_CFG_PSIZE; ++ int ep_sz = APM_HW_EP_CFG_PSIZE; ++ int fd_sz = APM_HW_EP_FRAME_DURATION_PSIZE; ++ int size = ic_sz + ep_sz + fd_sz; ++ u32 slot_mask = cfg->slot_mask ? cfg->slot_mask : module->slot_mask; ++ u16 nslots_per_frame = cfg->nslots_per_frame ? ++ (u16)cfg->nslots_per_frame : module->nslots_per_frame; ++ u16 slot_width = cfg->slot_width ? (u16)cfg->slot_width : module->slot_width; ++ void *p; ++ ++ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0); ++ if (IS_ERR(pkt)) ++ return PTR_ERR(pkt); ++ ++ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; ++ intf_cfg = p; ++ ++ param_data = &intf_cfg->param_data; ++ param_data->module_instance_id = module->instance_id; ++ param_data->error_code = 0; ++ param_data->param_id = PARAM_ID_AUDIO_IF_INTF_CFG; ++ param_data->param_size = ic_sz - APM_MODULE_PARAM_DATA_SIZE; ++ intf_cfg->cfg.qaif_type = module->qaif_type; ++ intf_cfg->cfg.intf_idx = (u16)module->hw_interface_idx; ++ intf_cfg->cfg.intf_mode = module->intf_mode; ++ intf_cfg->cfg.ctrl_data_out_enable = module->ctrl_data_out_enable; ++ intf_cfg->cfg.active_slot_mask = slot_mask; ++ intf_cfg->cfg.nslots_per_frame = nslots_per_frame; ++ intf_cfg->cfg.slot_width = slot_width; ++ intf_cfg->cfg.active_lane_mask = module->active_lane_mask; ++ intf_cfg->cfg.frame_sync_rate = module->frame_sync_rate; ++ intf_cfg->cfg.frame_sync_src = module->sync_src; ++ intf_cfg->cfg.frame_sync_mode = module->sync_mode; ++ intf_cfg->cfg.invert_frame_sync_pulse = module->ctrl_invert_sync_pulse; ++ intf_cfg->cfg.frame_sync_data_delay = module->ctrl_sync_data_delay; ++ intf_cfg->cfg.bit_clk_type = module->bit_clk_type; ++ intf_cfg->cfg.inv_int_bit_clk = module->inv_int_bit_clk; ++ intf_cfg->cfg.inv_ext_bit_clk = module->inv_ext_bit_clk; ++ ++ p += ic_sz; ++ hw_cfg = p; ++ param_data = &hw_cfg->param_data; ++ param_data->module_instance_id = module->instance_id; ++ param_data->error_code = 0; ++ param_data->param_id = PARAM_ID_HW_EP_MF_CFG; ++ param_data->param_size = ep_sz - APM_MODULE_PARAM_DATA_SIZE; ++ ++ hw_cfg->mf.sample_rate = cfg->sample_rate; ++ hw_cfg->mf.bit_width = cfg->bit_width; ++ hw_cfg->mf.num_channels = cfg->num_channels; ++ hw_cfg->mf.data_format = module->data_format; ++ ++ p += ep_sz; ++ fd_cfg = p; ++ param_data = &fd_cfg->param_data; ++ param_data->module_instance_id = module->instance_id; ++ param_data->error_code = 0; ++ param_data->param_id = PARAM_ID_HW_EP_FRAME_DURATION; ++ param_data->param_size = fd_sz - APM_MODULE_PARAM_DATA_SIZE; ++ fd_cfg->frame_duration.frame_duration_in_us = AUDIO_IF_FRAME_DURATION_US; ++ fd_cfg->frame_duration.allow_frame_duration_normalization = AUDIO_IF_FRAME_DURATION_NORMALIZATION_ENABLE; ++ fd_cfg->frame_duration.min_normalized_frame_dur_us = AUDIO_IF_FRAME_DURATION_MIN_US; ++ fd_cfg->frame_duration.max_normalized_frame_dur_us = AUDIO_IF_FRAME_DURATION_MAX_US; ++ ++ return q6apm_send_cmd_sync(graph->apm, pkt, 0); ++} ++ + static int audioreach_logging_set_media_format(struct q6apm_graph *graph, + const struct audioreach_module *module) + { +@@ -1080,6 +1179,7 @@ static int audioreach_pcm_set_media_format(struct q6apm_graph *graph, + struct apm_pcm_module_media_fmt_cmd *cfg; + struct apm_module_param_data *param_data; + int payload_size; ++ int i, j; + + if (num_channels > 4) { + dev_err(graph->dev, "Error: Invalid channels (%d)!\n", num_channels); +@@ -1113,7 +1213,12 @@ static int audioreach_pcm_set_media_format(struct q6apm_graph *graph, + media_cfg->num_channels = mcfg->num_channels; + media_cfg->q_factor = mcfg->bit_width - 1; + media_cfg->bits_per_sample = mcfg->bit_width; +- memcpy(media_cfg->channel_mapping, mcfg->channel_map, mcfg->num_channels); ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < mcfg->num_channels; i++) { ++ if (!mcfg->channel_map[i]) ++ continue; ++ media_cfg->channel_mapping[j++] = mcfg->channel_map[i]; ++ } + + return q6apm_send_cmd_sync(graph->apm, pkt, 0); + } +@@ -1163,6 +1268,7 @@ static int audioreach_shmem_set_media_format(struct q6apm_graph *graph, + struct payload_media_fmt_pcm *cfg; + struct media_format *header; + int rc, payload_size; ++ int i, j; + void *p; + + if (num_channels > 4) { +@@ -1202,7 +1308,12 @@ static int audioreach_shmem_set_media_format(struct q6apm_graph *graph, + cfg->q_factor = mcfg->bit_width - 1; + cfg->endianness = PCM_LITTLE_ENDIAN; + cfg->num_channels = mcfg->num_channels; +- memcpy(cfg->channel_mapping, mcfg->channel_map, mcfg->num_channels); ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < cfg->num_channels; i++) { ++ if (!mcfg->channel_map[i]) ++ continue; ++ cfg->channel_mapping[j++] = mcfg->channel_map[i]; ++ } + } else { + rc = audioreach_set_compr_media_format(header, p, mcfg); + if (rc) +@@ -1279,7 +1390,7 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph, + struct apm_module_sp_vi_ex_mode_cfg *ex_cfg; + int op_sz, cm_sz, ex_sz; + struct apm_module_param_data *param_data; +- int rc, i, payload_size; ++ int rc, i, payload_size, j; + struct gpr_pkt *pkt; + void *p; + +@@ -1320,14 +1431,19 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph, + param_data->param_size = cm_sz - APM_MODULE_PARAM_DATA_SIZE; + + cm_cfg->cfg.num_channels = num_channels * 2; +- for (i = 0; i < num_channels; i++) { ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < num_channels; i++) { ++ if (!mcfg->channel_map[i]) ++ continue; + /* + * Map speakers into Vsense and then Isense of each channel. + * E.g. for PCM_CHANNEL_FL and PCM_CHANNEL_FR to: + * [1, 2, 3, 4] + */ +- cm_cfg->cfg.channel_mapping[2 * i] = (mcfg->channel_map[i] - 1) * 2 + 1; +- cm_cfg->cfg.channel_mapping[2 * i + 1] = (mcfg->channel_map[i] - 1) * 2 + 2; ++ cm_cfg->cfg.channel_mapping[2 * j] = (mcfg->channel_map[i] - 1) * 2 + 1; ++ cm_cfg->cfg.channel_mapping[2 * j + 1] = (mcfg->channel_map[i] - 1) * 2 + 2; ++ ++ ++j; + } + + p += cm_sz; +@@ -1411,6 +1527,10 @@ int audioreach_set_media_format(struct q6apm_graph *graph, + if (!rc) + rc = audioreach_module_enable(graph, module, true); + break; ++ case MODULE_ID_AUDIO_IF_SOURCE: ++ case MODULE_ID_AUDIO_IF_SINK: ++ rc = audioreach_audio_if_set_media_format(graph, module, cfg); ++ break; + + default: + rc = 0; +diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h +--- a/sound/soc/qcom/qdsp6/audioreach.h ++++ b/sound/soc/qcom/qdsp6/audioreach.h +@@ -36,6 +36,8 @@ struct q6apm_graph; + #define MODULE_ID_SPEAKER_PROTECTION 0x070010E2 + #define MODULE_ID_SPEAKER_PROTECTION_VI 0x070010E3 + #define MODULE_ID_OPUS_DEC 0x07001174 ++#define MODULE_ID_AUDIO_IF_SINK 0x0700117C ++#define MODULE_ID_AUDIO_IF_SOURCE 0x0700117D + + #define APM_CMD_GET_SPF_STATE 0x01001021 + #define APM_CMD_RSP_GET_SPF_STATE 0x02001007 +@@ -544,6 +546,74 @@ struct param_id_i2s_intf_cfg { + #define PORT_ID_I2S_OUPUT 1 + #define I2S_STACK_SIZE 2048 + ++#define PARAM_ID_AUDIO_IF_INTF_CFG 0x08001B11 ++ ++/* ++ * struct param_id_audio_if_intf_cfg - Audio interface configuration ++ * @qaif_type: Audio interface type (e.g. QAIF, QAIF_VA) ++ * @intf_idx: Interface instance index ++ * @intf_mode: Interface operating mode (TDM/PCM/I2S) ++ * @ctrl_data_out_enable: Enable sharing of data-out signal with other masters ++ * @active_slot_mask: Bitmask indicating active slots ++ * @nslots_per_frame: Number of slots per audio frame ++ * @slot_width: Width of each slot in bits ++ * @active_lane_mask: Bitmask of active data lanes ++ * @frame_sync_rate: Frame sync rate in Hz ++ * @frame_sync_src: Frame sync source selection ++ * @frame_sync_mode: Frame sync mode configuration ++ * @invert_frame_sync_pulse: Invert frame sync polarity when set ++ * @frame_sync_data_delay: Data delay from frame sync in bit clocks ++ * @bit_clk_type: Bit clock type (internal / external) ++ * @inv_int_bit_clk: Invert internal bit clock when set ++ * @inv_ext_bit_clk: Invert external bit clock when set ++ * ++ * This structure defines configuration parameters for the Qualcomm ++ * Audio Interface (QAIF) block. It is used to program interface ++ * characteristics such as slot configuration, clocking and frame ++ * synchronization behaviour. ++ */ ++struct param_id_audio_if_intf_cfg { ++ uint16_t qaif_type; ++ uint16_t intf_idx; ++ uint16_t intf_mode; ++ uint16_t ctrl_data_out_enable; ++ uint32_t active_slot_mask; ++ uint16_t nslots_per_frame; ++ uint16_t slot_width; ++ uint32_t active_lane_mask; ++ uint32_t frame_sync_rate; ++ uint16_t frame_sync_src; ++ uint16_t frame_sync_mode; ++ uint16_t invert_frame_sync_pulse; ++ uint16_t frame_sync_data_delay; ++ uint16_t bit_clk_type; ++ uint8_t inv_int_bit_clk; ++ uint8_t inv_ext_bit_clk; ++} __packed; ++ ++#define PARAM_ID_HW_EP_FRAME_DURATION 0x08001B2F ++#define AUDIO_IF_FRAME_DURATION_US 1000 ++#define AUDIO_IF_FRAME_DURATION_NORMALIZATION_ENABLE 1 ++#define AUDIO_IF_FRAME_DURATION_MIN_US 1 ++#define AUDIO_IF_FRAME_DURATION_MAX_US 100000 ++ ++/** ++ * struct param_id_hw_ep_frame_duration - Hardware endpoint frame duration ++ * @frame_duration_in_us: Frame duration in microseconds. ++ * @allow_frame_duration_normalization: Permit SPF to normalize frame duration. ++ * @min_normalized_frame_dur_us: Minimum normalized frame duration in microseconds. ++ * @max_normalized_frame_dur_us: Maximum normalized frame duration in microseconds. ++ * ++ * This structure configures the frame duration for the Audio IF hardware ++ * endpoint and, when enabled, the allowed normalization range. ++ */ ++struct param_id_hw_ep_frame_duration { ++ uint32_t frame_duration_in_us; ++ uint32_t allow_frame_duration_normalization; ++ uint32_t min_normalized_frame_dur_us; ++ uint32_t max_normalized_frame_dur_us; ++} __packed; ++ + #define PARAM_ID_DISPLAY_PORT_INTF_CFG 0x08001154 + + struct param_id_display_port_intf_cfg { +@@ -877,6 +947,23 @@ struct audioreach_module { + uint32_t data_format; + uint32_t hw_interface_type; + ++ /* Audio IF module (TDM/PCM/I2S) */ ++ u32 slot_mask; ++ u32 active_lane_mask; ++ u32 frame_sync_rate; ++ u16 qaif_type; ++ u16 sync_src; ++ u16 ctrl_data_out_enable; ++ u16 nslots_per_frame; ++ u16 slot_width; ++ u16 intf_mode; ++ u16 sync_mode; ++ u16 ctrl_invert_sync_pulse; ++ u16 ctrl_sync_data_delay; ++ u16 bit_clk_type; ++ u8 inv_int_bit_clk; ++ u8 inv_ext_bit_clk; ++ + /* PCM module specific */ + uint32_t interleave_type; + +@@ -907,6 +994,9 @@ struct audioreach_module_config { + u32 channel_allocation; + u32 sd_line_mask; + int fmt; ++ u32 slot_mask; ++ u16 nslots_per_frame; ++ u16 slot_width; + struct snd_codec codec; + u8 channel_map[AR_PCM_MAX_NUM_CHANNEL]; + }; +diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c +--- a/sound/soc/qcom/qdsp6/q6afe-dai.c ++++ b/sound/soc/qcom/qdsp6/q6afe-dai.c +@@ -412,6 +412,8 @@ static int q6afe_dai_prepare(struct snd_pcm_substream *substream, + case SENARY_MI2S_RX ... SENARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + rc = q6afe_i2s_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].i2s_cfg); + if (rc < 0) { +@@ -665,6 +667,21 @@ static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { + + /* USB playback AFE port receives data for playback, hence use the RX port */ + {"USB Playback", NULL, "USB_RX"}, ++ ++ {"LPI RX0 MI2S Playback", NULL, "LPI_MI2S_RX_0"}, ++ {"LPI_MI2S_TX_0", NULL, "LPI TX0 MI2S Capture"}, ++ {"LPI RX1 MI2S Playback", NULL, "LPI_MI2S_RX_1"}, ++ {"LPI_MI2S_TX_1", NULL, "LPI TX1 MI2S Capture"}, ++ {"LPI RX2 MI2S Playback", NULL, "LPI_MI2S_RX_2"}, ++ {"LPI_MI2S_TX_2", NULL, "LPI TX2 MI2S Capture"}, ++ {"LPI RX3 MI2S Playback", NULL, "LPI_MI2S_RX_3"}, ++ {"LPI_MI2S_TX_3", NULL, "LPI TX3 MI2S Capture"}, ++ {"LPI RX4 MI2S Playback", NULL, "LPI_MI2S_RX_4"}, ++ {"LPI_MI2S_TX_4", NULL, "LPI TX4 MI2S Capture"}, ++ {"LPI RX5 MI2S Playback", NULL, "LPI_MI2S_RX_5"}, ++ {"LPI_MI2S_TX_5", NULL, "LPI TX5 MI2S Capture"}, ++ {"LPI RX6 MI2S Playback", NULL, "LPI_MI2S_RX_6"}, ++ {"LPI_MI2S_TX_6", NULL, "LPI TX6 MI2S Capture"}, + }; + + static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) +@@ -1011,6 +1028,35 @@ static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { + 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("USB_RX", NULL, 0, SND_SOC_NOPM, 0, 0), ++ ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_0", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_0", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_1", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_1", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_2", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_2", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_3", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_3", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_4", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_4", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_5", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_5", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_6", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_6", NULL, ++ 0, SND_SOC_NOPM, 0, 0), + }; + + static const struct snd_soc_component_driver q6afe_dai_component = { +@@ -1045,6 +1091,8 @@ static void of_q6afe_parse_dai_data(struct device *dev, + case SENARY_MI2S_RX ... SENARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + priv = &data->priv[id]; + ret = of_property_read_variable_u32_array(node, + "qcom,sd-lines", +diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c +--- a/sound/soc/qcom/qdsp6/q6afe.c ++++ b/sound/soc/qcom/qdsp6/q6afe.c +@@ -132,6 +132,20 @@ + #define AFE_PORT_ID_QUINARY_MI2S_TX 0x1017 + #define AFE_PORT_ID_SENARY_MI2S_RX 0x1018 + #define AFE_PORT_ID_SENARY_MI2S_TX 0x1019 ++#define AFE_PORT_ID_INT0_MI2S_RX 0x102e ++#define AFE_PORT_ID_INT0_MI2S_TX 0x102f ++#define AFE_PORT_ID_INT1_MI2S_RX 0x1030 ++#define AFE_PORT_ID_INT1_MI2S_TX 0x1031 ++#define AFE_PORT_ID_INT2_MI2S_RX 0x1032 ++#define AFE_PORT_ID_INT2_MI2S_TX 0x1033 ++#define AFE_PORT_ID_INT3_MI2S_RX 0x1034 ++#define AFE_PORT_ID_INT3_MI2S_TX 0x1035 ++#define AFE_PORT_ID_INT4_MI2S_RX 0x1036 ++#define AFE_PORT_ID_INT4_MI2S_TX 0x1037 ++#define AFE_PORT_ID_INT5_MI2S_RX 0x1038 ++#define AFE_PORT_ID_INT5_MI2S_TX 0x1039 ++#define AFE_PORT_ID_INT6_MI2S_RX 0x103a ++#define AFE_PORT_ID_INT6_MI2S_TX 0x103b + + /* Start of the range of port IDs for TDM devices. */ + #define AFE_PORT_ID_TDM_PORT_RANGE_START 0x9000 +@@ -931,6 +945,34 @@ static struct afe_port_map port_maps[AFE_PORT_MAX] = { + [RX_CODEC_DMA_RX_7] = { AFE_PORT_ID_RX_CODEC_DMA_RX_7, + RX_CODEC_DMA_RX_7, 1, 1}, + [USB_RX] = { AFE_PORT_ID_USB_RX, USB_RX, 1, 1}, ++ [LPI_MI2S_RX_0] = { AFE_PORT_ID_INT0_MI2S_RX, ++ LPI_MI2S_RX_0, 1, 1}, ++ [LPI_MI2S_TX_0] = { AFE_PORT_ID_INT0_MI2S_TX, ++ LPI_MI2S_TX_0, 0, 1}, ++ [LPI_MI2S_RX_1] = { AFE_PORT_ID_INT1_MI2S_RX, ++ LPI_MI2S_RX_1, 1, 1}, ++ [LPI_MI2S_TX_1] = { AFE_PORT_ID_INT1_MI2S_TX, ++ LPI_MI2S_TX_1, 0, 1}, ++ [LPI_MI2S_RX_2] = { AFE_PORT_ID_INT2_MI2S_RX, ++ LPI_MI2S_RX_2, 1, 1}, ++ [LPI_MI2S_TX_2] = { AFE_PORT_ID_INT2_MI2S_TX, ++ LPI_MI2S_TX_2, 0, 1}, ++ [LPI_MI2S_RX_3] = { AFE_PORT_ID_INT3_MI2S_RX, ++ LPI_MI2S_RX_3, 1, 1}, ++ [LPI_MI2S_TX_3] = { AFE_PORT_ID_INT3_MI2S_TX, ++ LPI_MI2S_TX_3, 0, 1}, ++ [LPI_MI2S_RX_4] = { AFE_PORT_ID_INT4_MI2S_RX, ++ LPI_MI2S_RX_4, 1, 1}, ++ [LPI_MI2S_TX_4] = { AFE_PORT_ID_INT4_MI2S_TX, ++ LPI_MI2S_TX_4, 0, 1}, ++ [LPI_MI2S_RX_5] = { AFE_PORT_ID_INT5_MI2S_RX, ++ LPI_MI2S_RX_5, 1, 1}, ++ [LPI_MI2S_TX_5] = { AFE_PORT_ID_INT5_MI2S_TX, ++ LPI_MI2S_TX_5, 0, 1}, ++ [LPI_MI2S_RX_6] = { AFE_PORT_ID_INT6_MI2S_RX, ++ LPI_MI2S_RX_6, 1, 1}, ++ [LPI_MI2S_TX_6] = { AFE_PORT_ID_INT6_MI2S_TX, ++ LPI_MI2S_TX_6, 0, 1}, + }; + + static void q6afe_port_free(struct kref *ref) +@@ -1785,6 +1827,20 @@ struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) + case AFE_PORT_ID_QUINARY_MI2S_TX: + case AFE_PORT_ID_SENARY_MI2S_RX: + case AFE_PORT_ID_SENARY_MI2S_TX: ++ case AFE_PORT_ID_INT0_MI2S_RX: ++ case AFE_PORT_ID_INT0_MI2S_TX: ++ case AFE_PORT_ID_INT1_MI2S_RX: ++ case AFE_PORT_ID_INT1_MI2S_TX: ++ case AFE_PORT_ID_INT2_MI2S_RX: ++ case AFE_PORT_ID_INT2_MI2S_TX: ++ case AFE_PORT_ID_INT3_MI2S_RX: ++ case AFE_PORT_ID_INT3_MI2S_TX: ++ case AFE_PORT_ID_INT4_MI2S_RX: ++ case AFE_PORT_ID_INT4_MI2S_TX: ++ case AFE_PORT_ID_INT5_MI2S_RX: ++ case AFE_PORT_ID_INT5_MI2S_TX: ++ case AFE_PORT_ID_INT6_MI2S_RX: ++ case AFE_PORT_ID_INT6_MI2S_TX: + cfg_type = AFE_PARAM_ID_I2S_CONFIG; + break; + case AFE_PORT_ID_PRIMARY_TDM_RX ... AFE_PORT_ID_QUINARY_TDM_TX_7: +diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +--- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c ++++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +@@ -2,10 +2,12 @@ + // Copyright (c) 2021, Linaro Limited + + #include ++#include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -15,15 +17,54 @@ + #include "q6dsp-common.h" + #include "audioreach.h" + #include "q6apm.h" ++#include "q6prm.h" + + #define AUDIOREACH_BE_PCM_BASE 16 + ++struct q6apm_dai_priv_data { ++ struct clk *mclk; ++ struct clk *bclk; ++ bool mclk_enabled, bclk_enabled; ++}; ++ + struct q6apm_lpass_dai_data { + struct q6apm_graph *graph[APM_PORT_MAX]; + bool is_port_started[APM_PORT_MAX]; + struct audioreach_module_config module_config[APM_PORT_MAX]; ++ struct q6apm_dai_priv_data priv[APM_PORT_MAX]; + }; + ++static void q6apm_lpass_dai_disable_clocks(struct q6apm_lpass_dai_data *dai_data, int id) ++{ ++ if (dai_data->priv[id].mclk_enabled) { ++ clk_disable_unprepare(dai_data->priv[id].mclk); ++ dai_data->priv[id].mclk_enabled = false; ++ } ++ ++ if (dai_data->priv[id].bclk_enabled) { ++ clk_disable_unprepare(dai_data->priv[id].bclk); ++ dai_data->priv[id].bclk_enabled = false; ++ } ++} ++ ++static void q6apm_lpass_dai_put_clocks(struct q6apm_lpass_dai_data *dai_data) ++{ ++ int i; ++ ++ for (i = 0; i < APM_PORT_MAX; i++) { ++ q6apm_lpass_dai_disable_clocks(dai_data, i); ++ ++ if (dai_data->priv[i].mclk) { ++ clk_put(dai_data->priv[i].mclk); ++ dai_data->priv[i].mclk = NULL; ++ } ++ if (dai_data->priv[i].bclk) { ++ clk_put(dai_data->priv[i].bclk); ++ dai_data->priv[i].bclk = NULL; ++ } ++ } ++} ++ + static int q6dma_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, + const unsigned int *tx_ch_mask, +@@ -251,6 +292,62 @@ static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, struct s + return 0; + } + ++static int q6i2s_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) ++{ ++ return q6apm_lpass_dai_startup(substream, dai); ++} ++ ++static void q6i2s_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) ++{ ++ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); ++ ++ q6apm_lpass_dai_shutdown(substream, dai); ++ q6apm_lpass_dai_disable_clocks(dai_data, dai->id); ++} ++ ++static int q6i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) ++{ ++ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); ++ struct clk *sysclk = NULL; ++ bool *enabled = NULL; ++ int ret = 0; ++ ++ switch (clk_id) { ++ case LPAIF_MI2S_MCLK: ++ sysclk = dai_data->priv[dai->id].mclk; ++ enabled = &dai_data->priv[dai->id].mclk_enabled; ++ break; ++ case LPAIF_MI2S_BCLK: ++ sysclk = dai_data->priv[dai->id].bclk; ++ enabled = &dai_data->priv[dai->id].bclk_enabled; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ if (sysclk) { ++ ret = clk_set_rate(sysclk, freq); ++ if (ret) { ++ dev_err(dai->dev, "Error, Unable to set rate (%d) for sysclk %d\n", ++ freq, clk_id); ++ return ret; ++ } ++ ++ if (*enabled) ++ return 0; ++ ++ ret = clk_prepare_enable(sysclk); ++ if (ret) { ++ dev_err(dai->dev, "Error, Unable to prepare (%d) sysclk\n", clk_id); ++ return ret; ++ } ++ ++ *enabled = true; ++ } ++ ++ return ret; ++} ++ + static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + { + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); +@@ -261,6 +358,53 @@ static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + return 0; + } + ++static int q6tdm_set_tdm_slot(struct snd_soc_dai *dai, ++ unsigned int tx_mask, ++ unsigned int rx_mask, ++ int slots, int slot_width) ++{ ++ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); ++ struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; ++ unsigned int cap_mask, slot_mask; ++ ++ if (slot_width != 16 && slot_width != 32) { ++ dev_err(dai->dev, "%s: invalid slot_width %d\n", __func__, slot_width); ++ return -EINVAL; ++ } ++ ++ switch (slots) { ++ case 2: ++ case 4: ++ case 8: ++ case 16: ++ cap_mask = GENMASK(slots - 1, 0); ++ break; ++ default: ++ dev_err(dai->dev, "%s: invalid slots %d\n", __func__, slots); ++ return -EINVAL; ++ } ++ ++ switch (dai->id) { ++ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: ++ slot_mask = (dai->id & 0x1) ? tx_mask : rx_mask; ++ if (slot_mask & ~cap_mask) { ++ dev_err(dai->dev, "%s: invalid slot mask 0x%x for %d slots\n", ++ __func__, slot_mask, slots); ++ return -EINVAL; ++ } ++ ++ cfg->nslots_per_frame = slots; ++ cfg->slot_width = slot_width; ++ cfg->slot_mask = slot_mask; ++ break; ++ default: ++ dev_err(dai->dev, "%s: invalid dai id 0x%x\n", __func__, dai->id); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ + static const struct snd_soc_dai_ops q6dma_ops = { + .prepare = q6apm_lpass_dai_prepare, + .startup = q6apm_lpass_dai_startup, +@@ -272,11 +416,12 @@ static const struct snd_soc_dai_ops q6dma_ops = { + + static const struct snd_soc_dai_ops q6i2s_ops = { + .prepare = q6apm_lpass_dai_prepare, +- .startup = q6apm_lpass_dai_startup, +- .shutdown = q6apm_lpass_dai_shutdown, ++ .startup = q6i2s_dai_startup, ++ .shutdown = q6i2s_lpass_dai_shutdown, + .set_channel_map = q6dma_set_channel_map, + .hw_params = q6dma_hw_params, + .set_fmt = q6i2s_set_fmt, ++ .set_sysclk = q6i2s_set_sysclk, + .trigger = q6apm_lpass_dai_trigger, + }; + +@@ -289,6 +434,17 @@ static const struct snd_soc_dai_ops q6hdmi_ops = { + .trigger = q6apm_lpass_dai_trigger, + }; + ++static const struct snd_soc_dai_ops q6tdm_ops = { ++ .prepare = q6apm_lpass_dai_prepare, ++ .startup = q6apm_lpass_dai_startup, ++ .shutdown = q6i2s_lpass_dai_shutdown, ++ .set_tdm_slot = q6tdm_set_tdm_slot, ++ .hw_params = q6dma_hw_params, ++ .set_fmt = q6i2s_set_fmt, ++ .set_sysclk = q6i2s_set_sysclk, ++ .trigger = q6apm_lpass_dai_trigger, ++}; ++ + static const struct snd_soc_component_driver q6apm_lpass_dai_component = { + .name = "q6apm-be-dai-component", + .of_xlate_dai_name = q6dsp_audio_ports_of_xlate_dai_name, +@@ -297,6 +453,65 @@ static const struct snd_soc_component_driver q6apm_lpass_dai_component = { + .remove_order = SND_SOC_COMP_ORDER_FIRST, + }; + ++static int of_q6apm_parse_dai_data(struct device *dev, ++ struct q6apm_lpass_dai_data *data) ++{ ++ int ret; ++ ++ for_each_child_of_node_scoped(dev->of_node, node) { ++ struct q6apm_dai_priv_data *priv; ++ int id; ++ ++ ret = of_property_read_u32(node, "reg", &id); ++ if (ret || id < 0 || id >= APM_PORT_MAX) { ++ dev_err(dev, "valid dai id not found:%d\n", ret); ++ continue; ++ } ++ ++ switch (id) { ++ /* MI2S specific properties */ ++ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: ++ case SENARY_MI2S_RX ... SENARY_MI2S_TX: ++ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: ++ priv = &data->priv[id]; ++ priv->mclk = of_clk_get_by_name(node, "mclk"); ++ if (IS_ERR(priv->mclk)) { ++ int err = PTR_ERR(priv->mclk); ++ ++ priv->mclk = NULL; ++ if (err == -EPROBE_DEFER) { ++ q6apm_lpass_dai_put_clocks(data); ++ return dev_err_probe(dev, err, ++ "unable to get mi2s mclk\n"); ++ } ++ } ++ ++ priv->bclk = of_clk_get_by_name(node, "bclk"); ++ if (IS_ERR(priv->bclk)) { ++ int err = PTR_ERR(priv->bclk); ++ ++ priv->bclk = NULL; ++ if (err == -EPROBE_DEFER) { ++ q6apm_lpass_dai_put_clocks(data); ++ return dev_err_probe(dev, err, ++ "unable to get mi2s bclk\n"); ++ } ++ } ++ break; ++ default: ++ break; ++ } ++ } ++ ++ return 0; ++} ++ ++static void q6apm_lpass_dai_clocks_action(void *data) ++{ ++ q6apm_lpass_dai_put_clocks(data); ++} ++ + static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) + { + struct q6dsp_audio_port_dai_driver_config cfg; +@@ -304,17 +519,26 @@ static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) + struct snd_soc_dai_driver *dais; + struct device *dev = &pdev->dev; + int num_dais; ++ int ret; + + dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); + if (!dai_data) + return -ENOMEM; + + dev_set_drvdata(dev, dai_data); ++ ret = of_q6apm_parse_dai_data(dev, dai_data); ++ if (ret) ++ return ret; ++ ++ ret = devm_add_action_or_reset(dev, q6apm_lpass_dai_clocks_action, dai_data); ++ if (ret) ++ return ret; + + memset(&cfg, 0, sizeof(cfg)); + cfg.q6i2s_ops = &q6i2s_ops; + cfg.q6dma_ops = &q6dma_ops; + cfg.q6hdmi_ops = &q6hdmi_ops; ++ cfg.q6tdm_ops = &q6tdm_ops; + dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); + + return devm_snd_soc_register_component(dev, &q6apm_lpass_dai_component, dais, num_dais); +diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c +--- a/sound/soc/qcom/qdsp6/q6apm.c ++++ b/sound/soc/qcom/qdsp6/q6apm.c +@@ -802,14 +802,17 @@ EXPORT_SYMBOL_GPL(q6apm_graph_prepare); + int q6apm_graph_start(struct q6apm_graph *graph) + { + struct audioreach_graph *ar_graph = graph->ar_graph; +- int ret = 0; ++ int ret; + +- if (ar_graph->start_count == 0) ++ if (ar_graph->start_count == 0) { + ret = audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_START); ++ if (ret) ++ return ret; ++ } + + ar_graph->start_count++; + +- return ret; ++ return 0; + } + EXPORT_SYMBOL_GPL(q6apm_graph_start); + +@@ -817,6 +820,9 @@ int q6apm_graph_stop(struct q6apm_graph *graph) + { + struct audioreach_graph *ar_graph = graph->ar_graph; + ++ if (ar_graph->start_count == 0) ++ return 0; ++ + if (--ar_graph->start_count > 0) + return 0; + +diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c +--- a/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c ++++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c +@@ -12,7 +12,7 @@ + #include + #include "q6dsp-lpass-clocks.h" + +-#define Q6DSP_MAX_CLK_ID 104 ++#define Q6DSP_MAX_CLK_ID 105 + #define Q6DSP_LPASS_CLK_ROOT_DEFAULT 0 + + +diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +--- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c ++++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +@@ -553,11 +553,15 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { + Q6AFE_MI2S_RX_DAI("LPI RX2", LPI_MI2S_RX_2), + Q6AFE_MI2S_RX_DAI("LPI RX3", LPI_MI2S_RX_3), + Q6AFE_MI2S_RX_DAI("LPI RX4", LPI_MI2S_RX_4), ++ Q6AFE_MI2S_RX_DAI("LPI RX5", LPI_MI2S_RX_5), ++ Q6AFE_MI2S_RX_DAI("LPI RX6", LPI_MI2S_RX_6), + Q6AFE_MI2S_TX_DAI("LPI TX0", LPI_MI2S_TX_0), + Q6AFE_MI2S_TX_DAI("LPI TX1", LPI_MI2S_TX_1), + Q6AFE_MI2S_TX_DAI("LPI TX2", LPI_MI2S_TX_2), + Q6AFE_MI2S_TX_DAI("LPI TX3", LPI_MI2S_TX_3), + Q6AFE_MI2S_TX_DAI("LPI TX4", LPI_MI2S_TX_4), ++ Q6AFE_MI2S_TX_DAI("LPI TX5", LPI_MI2S_TX_5), ++ Q6AFE_MI2S_TX_DAI("LPI TX6", LPI_MI2S_TX_6), + Q6AFE_TDM_PB_DAI("Primary", 0, PRIMARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Primary", 1, PRIMARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Primary", 2, PRIMARY_TDM_RX_2), +@@ -712,6 +716,7 @@ struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + q6dsp_audio_fe_dais[i].ops = cfg->q6i2s_ops; + break; + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: +diff --git a/sound/soc/qcom/qdsp6/q6prm-clocks.c b/sound/soc/qcom/qdsp6/q6prm-clocks.c +--- a/sound/soc/qcom/qdsp6/q6prm-clocks.c ++++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c +@@ -42,6 +42,11 @@ static const struct q6dsp_clk_init q6prm_clks[] = { + Q6PRM_CLK(LPASS_CLK_ID_INT5_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_INT6_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QUI_MI2S_OSR), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_1), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_2), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_3), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_4), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_5), + Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_NPL_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_VA_CORE_MCLK), +@@ -59,10 +64,40 @@ static const struct q6dsp_clk_init q6prm_clks[] = { + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_2X_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_EBIT), + Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6PRM_HW_CORE_ID_LPASS, + "LPASS_HW_MACRO"), + Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6PRM_HW_CORE_ID_DCODEC, + "LPASS_HW_DCODEC"), ++ Q6DSP_VOTE_CLK(LPASS_HW_LPR_VOTE, Q6PRM_HW_LPR_VOTE, ++ "LPASS_HW_LPR_VOTE"), + }; + + static const struct q6dsp_clk_desc q6dsp_clk_q6prm __maybe_unused = { +diff --git a/sound/soc/qcom/qdsp6/q6prm.c b/sound/soc/qcom/qdsp6/q6prm.c +--- a/sound/soc/qcom/qdsp6/q6prm.c ++++ b/sound/soc/qcom/qdsp6/q6prm.c +@@ -31,10 +31,16 @@ struct q6prm { + #define PARAM_ID_RSC_HW_CORE 0x08001032 + #define PARAM_ID_RSC_LPASS_CORE 0x0800102B + #define PARAM_ID_RSC_AUDIO_HW_CLK 0x0800102C ++#define PARAM_ID_RSC_CPU_LPR 0x08001A6E ++ ++#define LPR_CPU_SS_SLEEP_DISABLE 0x1 + + struct prm_cmd_request_hw_core { + struct apm_module_param_data param_data; +- uint32_t hw_clk_id; ++ union { ++ u32 hw_clk_id; ++ u32 lpr_state; ++ }; + } __packed; + + struct prm_cmd_request_rsc { +@@ -62,6 +68,7 @@ static int q6prm_set_hw_core_req(struct device *dev, uint32_t hw_block_id, bool + struct prm_cmd_request_hw_core *req; + gpr_device_t *gdev = prm->gdev; + uint32_t opcode, rsp_opcode; ++ bool lpr_req = (hw_block_id == Q6PRM_HW_LPR_VOTE); + + if (enable) { + opcode = PRM_CMD_REQUEST_HW_RSC; +@@ -82,10 +89,13 @@ static int q6prm_set_hw_core_req(struct device *dev, uint32_t hw_block_id, bool + + param_data->module_instance_id = GPR_PRM_MODULE_IID; + param_data->error_code = 0; +- param_data->param_id = PARAM_ID_RSC_HW_CORE; ++ param_data->param_id = lpr_req ? PARAM_ID_RSC_CPU_LPR : PARAM_ID_RSC_HW_CORE; + param_data->param_size = sizeof(*req) - APM_MODULE_PARAM_DATA_SIZE; + +- req->hw_clk_id = hw_block_id; ++ if (lpr_req) ++ req->lpr_state = LPR_CPU_SS_SLEEP_DISABLE; ++ else ++ req->hw_clk_id = hw_block_id; + + return q6prm_send_cmd_sync(prm, pkt, rsp_opcode); + } +diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h +--- a/sound/soc/qcom/qdsp6/q6prm.h ++++ b/sound/soc/qcom/qdsp6/q6prm.h +@@ -3,6 +3,9 @@ + #ifndef __Q6PRM_H__ + #define __Q6PRM_H__ + ++#define LPAIF_MI2S_MCLK 1 ++#define LPAIF_MI2S_BCLK 2 ++ + /* Clock ID for Primary I2S IBIT */ + #define Q6PRM_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100 + /* Clock ID for Primary I2S EBIT */ +@@ -52,6 +55,17 @@ + /* Clock ID for QUINARY MI2S OSR CLK */ + #define Q6PRM_LPASS_CLK_ID_QUI_MI2S_OSR 0x116 + ++/* Clock ID for MCLK1 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_1 0x300 ++/* Clock ID for MCLK2 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_2 0x301 ++/* Clock ID for MCLK3 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_3 0x302 ++/* Clock ID for MCLK4 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_4 0x303 ++/* Clock ID for MCLK5 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_5 0x304 ++ + #define Q6PRM_LPASS_CLK_ID_WSA_CORE_MCLK 0x305 + #define Q6PRM_LPASS_CLK_ID_WSA_CORE_NPL_MCLK 0x306 + +@@ -83,10 +97,40 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 0x318 + ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_IBIT 0x500 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_EBIT 0x501 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_IBIT 0x502 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_EBIT 0x503 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_IBIT 0x504 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_EBIT 0x505 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_IBIT 0x506 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_EBIT 0x507 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_IBIT 0x508 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_EBIT 0x509 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_IBIT 0x50A ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_EBIT 0x50B ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_IBIT 0x50C ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_EBIT 0x50D ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_IBIT 0x50E ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_EBIT 0x50F ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_IBIT 0x510 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_EBIT 0x511 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_IBIT 0x512 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_EBIT 0x513 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_IBIT 0x514 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_EBIT 0x515 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_IBIT 0x516 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_EBIT 0x517 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_IBIT 0x518 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_EBIT 0x519 ++#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 ++#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 ++ + #define Q6PRM_LPASS_CLK_SRC_INTERNAL 1 + #define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0 + #define Q6PRM_HW_CORE_ID_LPASS 1 + #define Q6PRM_HW_CORE_ID_DCODEC 2 ++#define Q6PRM_HW_LPR_VOTE 3 + + int q6prm_set_lpass_clock(struct device *dev, int clk_id, int clk_attr, + int clk_root, unsigned int freq); +diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c +--- a/sound/soc/qcom/qdsp6/q6routing.c ++++ b/sound/soc/qcom/qdsp6/q6routing.c +@@ -127,7 +127,14 @@ + { mix_name, "TX_CODEC_DMA_TX_2", "TX_CODEC_DMA_TX_2"}, \ + { mix_name, "TX_CODEC_DMA_TX_3", "TX_CODEC_DMA_TX_3"}, \ + { mix_name, "TX_CODEC_DMA_TX_4", "TX_CODEC_DMA_TX_4"}, \ +- { mix_name, "TX_CODEC_DMA_TX_5", "TX_CODEC_DMA_TX_5"} ++ { mix_name, "TX_CODEC_DMA_TX_5", "TX_CODEC_DMA_TX_5"}, \ ++ { mix_name, "LPI_MI2S_TX_0", "LPI_MI2S_TX_0" }, \ ++ { mix_name, "LPI_MI2S_TX_1", "LPI_MI2S_TX_1" }, \ ++ { mix_name, "LPI_MI2S_TX_2", "LPI_MI2S_TX_2" }, \ ++ { mix_name, "LPI_MI2S_TX_3", "LPI_MI2S_TX_3" }, \ ++ { mix_name, "LPI_MI2S_TX_4", "LPI_MI2S_TX_4" }, \ ++ { mix_name, "LPI_MI2S_TX_5", "LPI_MI2S_TX_5" }, \ ++ { mix_name, "LPI_MI2S_TX_6", "LPI_MI2S_TX_6" } + + #define Q6ROUTING_TX_MIXERS(id) \ + SOC_SINGLE_EXT("PRI_MI2S_TX", PRIMARY_MI2S_TX, \ +@@ -320,6 +327,27 @@ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TX_CODEC_DMA_TX_5", TX_CODEC_DMA_TX_5, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_0", LPI_MI2S_TX_0, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_1", LPI_MI2S_TX_1, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_2", LPI_MI2S_TX_2, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_3", LPI_MI2S_TX_3, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_4", LPI_MI2S_TX_4, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_5", LPI_MI2S_TX_5, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_6", LPI_MI2S_TX_6, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), + +@@ -709,6 +737,26 @@ static const struct snd_kcontrol_new rxcodec_dma_rx_6_mixer_controls[] = { + static const struct snd_kcontrol_new rx_codec_dma_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_7) }; + ++static const struct snd_kcontrol_new lpi_mi2s_rx_0_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_0) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_1_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_1) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_2_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_2) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_3_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_3) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_4_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_4) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_5_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_5) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_6_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_6) }; + + static const struct snd_kcontrol_new mmul1_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA1) }; +@@ -938,6 +986,27 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { + SND_SOC_DAPM_MIXER("USB_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + usb_rx_mixer_controls, + ARRAY_SIZE(usb_rx_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_0_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_0_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_1_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_1_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_2_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_2_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_3_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_3_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_4_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_4_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_5_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_5_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_6_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0, + mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0, +@@ -1031,6 +1100,13 @@ static const struct snd_soc_dapm_route intercon[] = { + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_6 Audio Mixer", "RX_CODEC_DMA_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_7 Audio Mixer", "RX_CODEC_DMA_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("USB_RX Audio Mixer", "USB_RX"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_0 Audio Mixer", "LPI_MI2S_RX_0"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_1 Audio Mixer", "LPI_MI2S_RX_1"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_2 Audio Mixer", "LPI_MI2S_RX_2"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_3 Audio Mixer", "LPI_MI2S_RX_3"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_4 Audio Mixer", "LPI_MI2S_RX_4"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_5 Audio Mixer", "LPI_MI2S_RX_5"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_6 Audio Mixer", "LPI_MI2S_RX_6"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia1 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia2 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia3 Mixer"), +diff --git a/sound/soc/qcom/qdsp6/topology.c b/sound/soc/qcom/qdsp6/topology.c +--- a/sound/soc/qcom/qdsp6/topology.c ++++ b/sound/soc/qcom/qdsp6/topology.c +@@ -753,6 +753,103 @@ static int audioreach_widget_i2s_module_load(struct audioreach_module *mod, + return 0; + } + ++static int audioreach_widget_audio_if_module_load(struct audioreach_module *mod, ++ const struct snd_soc_tplg_vendor_array *mod_array) ++{ ++ const struct snd_soc_tplg_vendor_value_elem *mod_elem; ++ int tkn_count = 0; ++ u32 val; ++ ++ mod_elem = mod_array->value; ++ ++ while (tkn_count < le32_to_cpu(mod_array->num_elems)) { ++ val = le32_to_cpu(mod_elem->value); ++ switch (le32_to_cpu(mod_elem->token)) { ++ case AR_TKN_U32_MODULE_HW_IF_IDX: ++ mod->hw_interface_idx = val; ++ break; ++ case AR_TKN_U32_MODULE_FMT_DATA: ++ mod->data_format = val; ++ break; ++ case AR_TKN_U16_MODULE_SYNC_SRC: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->sync_src = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->ctrl_data_out_enable = (u16)val; ++ break; ++ case AR_TKN_U32_MODULE_SLOT_MASK: ++ mod->slot_mask = val; ++ break; ++ case AR_TKN_U16_MODULE_NSLOTS_PER_FRAME: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->nslots_per_frame = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_SLOT_WIDTH: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->slot_width = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_INTF_MODE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->intf_mode = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_SYNC_MODE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->sync_mode = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->ctrl_invert_sync_pulse = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->ctrl_sync_data_delay = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_QAIF_TYPE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->qaif_type = (u16)val; ++ break; ++ case AR_TKN_U32_MODULE_ACTIVE_LANE_MASK: ++ mod->active_lane_mask = val; ++ break; ++ case AR_TKN_U32_MODULE_FRAME_SYNC_RATE: ++ mod->frame_sync_rate = val; ++ break; ++ case AR_TKN_U16_MODULE_BIT_CLK_TYPE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->bit_clk_type = (u16)val; ++ break; ++ case AR_TKN_U8_MODULE_INV_INT_BIT_CLK: ++ if (val > U8_MAX) ++ return -EINVAL; ++ mod->inv_int_bit_clk = (u8)val; ++ break; ++ case AR_TKN_U8_MODULE_INV_EXT_BIT_CLK: ++ if (val > U8_MAX) ++ return -EINVAL; ++ mod->inv_ext_bit_clk = (u8)val; ++ break; ++ default: ++ break; ++ } ++ tkn_count++; ++ mod_elem++; ++ } ++ ++ return 0; ++} ++ + static int audioreach_widget_dp_module_load(struct audioreach_module *mod, + const struct snd_soc_tplg_vendor_array *mod_array) + { +@@ -806,6 +903,12 @@ static int audioreach_widget_load_buffer(struct snd_soc_component *component, + case MODULE_ID_I2S_SOURCE: + audioreach_widget_i2s_module_load(mod, mod_array); + break; ++ case MODULE_ID_AUDIO_IF_SINK: ++ case MODULE_ID_AUDIO_IF_SOURCE: ++ ret = audioreach_widget_audio_if_module_load(mod, mod_array); ++ if (ret) ++ return ret; ++ break; + case MODULE_ID_DISPLAY_PORT_SINK: + audioreach_widget_dp_module_load(mod, mod_array); + break; +@@ -1313,7 +1416,6 @@ int audioreach_tplg_init(struct snd_soc_component *component) + { + struct snd_soc_card *card = component->card; + struct device *dev = component->dev; +- const struct firmware *fw; + int ret; + + /* Inline with Qualcomm UCM configs and linux-firmware path */ +@@ -1323,6 +1425,7 @@ int audioreach_tplg_init(struct snd_soc_component *component) + if (!tplg_fw_name) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, tplg_fw_name, dev); + if (ret < 0) { + dev_err(dev, "tplg firmware loading %s failed %d\n", tplg_fw_name, ret); +@@ -1335,8 +1438,6 @@ int audioreach_tplg_init(struct snd_soc_component *component) + dev_err(dev, "tplg component load failed: %d\n", ret); + } + +- release_firmware(fw); +- + return ret; + } + EXPORT_SYMBOL_GPL(audioreach_tplg_init); +diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c +--- a/sound/soc/qcom/sc8280xp.c ++++ b/sound/soc/qcom/sc8280xp.c +@@ -12,17 +12,157 @@ + #include + #include + #include "qdsp6/q6afe.h" ++#include "qdsp6/q6apm.h" ++#include "qdsp6/q6prm.h" ++#include "qdsp6/q6dsp-common.h" + #include "common.h" + #include "sdw.h" + ++#define I2S_MCLKFS 256 ++ ++#define I2S_MCLK_RATE(rate) \ ++ ((rate) * (I2S_MCLKFS)) ++#define I2S_BIT_RATE(rate, channels, format) \ ++ ((rate) * (channels) * (format)) ++ ++static struct snd_soc_dapm_widget sc8280xp_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone Jack", NULL), ++ SND_SOC_DAPM_MIC("Mic Jack", NULL), ++ SND_SOC_DAPM_SPK("DP0 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP1 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP2 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP3 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP4 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP5 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP6 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP7 Jack", NULL), ++}; ++ ++static const struct snd_kcontrol_new max98090_controls[] = { ++ SOC_DAPM_PIN_SWITCH("Headset Mic12"), ++ SOC_DAPM_PIN_SWITCH("Headphone"), ++ SOC_DAPM_PIN_SWITCH("Headset Mic56"), ++ SOC_DAPM_PIN_SWITCH("Speaker"), ++ SOC_DAPM_PIN_SWITCH("Receiver"), ++ SOC_DAPM_PIN_SWITCH("Int Mic"), ++}; ++ ++static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone Jack", NULL), ++ SND_SOC_DAPM_MIC("Mic Jack", NULL), ++ SND_SOC_DAPM_HP("Headphone", NULL), ++ SND_SOC_DAPM_MIC("Headset Mic12", NULL), ++ SND_SOC_DAPM_MIC("Headset Mic56", NULL), ++ SND_SOC_DAPM_MIC("Int Mic", NULL), ++ SND_SOC_DAPM_SPK("Receiver", NULL), ++ SND_SOC_DAPM_SPK("Speaker", NULL), ++}; ++ ++struct qcom_snd_soc_common { ++ const char *driver_name; ++ const struct snd_soc_dapm_widget *dapm_widgets; ++ int num_dapm_widgets; ++ const struct snd_soc_dapm_route *dapm_routes; ++ int num_dapm_routes; ++ const struct snd_kcontrol_new *controls; ++ int num_controls; ++ unsigned int codec_dai_fmt; ++ bool codec_sysclk_set; ++ bool mi2s_mclk_enable; ++ bool mi2s_bclk_enable; ++ bool wcd_jack; ++ int (*snd_prepare)(struct snd_pcm_substream *substream); ++}; ++ + struct sc8280xp_snd_data { + bool stream_prepared[AFE_PORT_MAX]; + struct snd_soc_card *card; + struct snd_soc_jack jack; + struct snd_soc_jack dp_jack[8]; ++ const struct qcom_snd_soc_common *priv; + bool jack_setup; + }; + ++static inline int sc8280xp_get_mclk_freq(struct snd_pcm_hw_params *params) ++{ ++ int rate = params_rate(params); ++ ++ switch (rate) { ++ case 11025: ++ case 44100: ++ case 88200: ++ return I2S_MCLK_RATE(44100); ++ default: ++ break; ++ } ++ ++ return I2S_MCLK_RATE(rate); ++} ++ ++static inline int sc8280xp_get_bclk_freq(struct snd_pcm_hw_params *params) ++{ ++ return I2S_BIT_RATE(params_rate(params), ++ params_channels(params), ++ snd_pcm_format_width(params_format(params))); ++} ++ ++static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_dai *codec_dai; ++ struct qcom_snd_tdm_slot_cfg cpu_cfg; ++ struct qcom_snd_tdm_slot_cfg codec_cfg; ++ unsigned int bclk_freq; ++ int ret; ++ int i; ++ ++ ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); ++ if (ret) ++ return ret == -EINVAL ? 0 : ret; ++ ++ if (!cpu_cfg.slots) ++ return 0; ++ ++ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); ++ if (ret) ++ return ret; ++ ++ ret = qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); ++ if (ret) ++ return ret; ++ ++ bclk_freq = snd_soc_tdm_params_to_bclk(params, cpu_cfg.slot_width, cpu_cfg.slots, 1); ++ if (!bclk_freq) ++ return -EINVAL; ++ ++ if (data->priv->mi2s_bclk_enable) { ++ ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, ++ SND_SOC_CLOCK_IN); ++ if (ret) { ++ dev_err(rtd->dev, "%s: failed to set cpu sysclk: %d\n", ++ __func__, ret); ++ return ret; ++ } ++ } ++ ++ if (data->priv->codec_sysclk_set) { ++ for_each_rtd_codec_dais(rtd, i, codec_dai) { ++ ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq, ++ SND_SOC_CLOCK_IN); ++ if (ret) { ++ dev_err(rtd->dev, "%s: failed to set codec sysclk on %s: %d\n", ++ __func__, codec_dai->name, ret); ++ return ret; ++ } ++ } ++ } ++ ++ return 0; ++} ++ + static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) + { + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); +@@ -32,10 +172,6 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) + int dp_pcm_id = 0; + + switch (cpu_dai->id) { +- case PRIMARY_MI2S_RX...QUATERNARY_MI2S_TX: +- case QUINARY_MI2S_RX...QUINARY_MI2S_TX: +- snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); +- break; + case WSA_CODEC_DMA_RX_0: + case WSA_CODEC_DMA_RX_1: + /* +@@ -64,7 +200,10 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) + if (dp_jack) + return qcom_snd_dp_jack_setup(rtd, dp_jack, dp_pcm_id); + +- return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); ++ if (data->priv->wcd_jack) ++ return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); ++ ++ return 0; + } + + static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, +@@ -96,12 +235,118 @@ static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + return 0; + } + ++static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); ++ int mclk_freq = sc8280xp_get_mclk_freq(params); ++ int bclk_freq = sc8280xp_get_bclk_freq(params); ++ int ret; ++ ++ switch (cpu_dai->id) { ++ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: ++ case SENARY_MI2S_RX ... SENARY_MI2S_TX: ++ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ ++ if (data->priv->codec_dai_fmt) { ++ ret = snd_soc_dai_set_fmt(codec_dai, ++ data->priv->codec_dai_fmt); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ } ++ ++ if (data->priv->mi2s_mclk_enable) { ++ ret = snd_soc_dai_set_sysclk(cpu_dai, ++ LPAIF_MI2S_MCLK, mclk_freq, ++ SND_SOC_CLOCK_OUT); ++ if (ret) ++ return ret; ++ } ++ ++ if (data->priv->mi2s_bclk_enable) { ++ ret = snd_soc_dai_set_sysclk(cpu_dai, ++ LPAIF_MI2S_BCLK, bclk_freq, ++ SND_SOC_CLOCK_OUT); ++ if (ret) ++ return ret; ++ } ++ ++ if (data->priv->codec_sysclk_set) { ++ ret = snd_soc_dai_set_sysclk(codec_dai, ++ 0, mclk_freq, ++ SND_SOC_CLOCK_IN); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ } ++ break; ++ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: ++ return sc8280xp_tdm_hw_params(substream, params); ++ default: ++ break; ++ } ++ ++ return 0; ++} ++ ++/* ++ * WSA and WSA2 are handled as a single interface with the ++ * following channels mask: ++ * __________________________________________________ ++ * | Bits | 3 | 2 | 1 | 0 | ++ * --------------------------------------------------- ++ * | Line | WSA2 Ch2 | WSA2 Ch1 | WSA Ch2 | WSA Ch1 | ++ * --------------------------------------------------- ++ * ++ * The Ayaneo Pocket S2 speakers are connected only to ++ * the WSA2 interface and the WSA interface is not enabled. ++ * ++ * Set the channel mapping on the WSA2 channels only. ++ */ ++static const unsigned int ayaneo_ps2_channels_mapping[] = { ++ 0, /* WSA Ch1 */ ++ 0, /* WSA Ch2 */ ++ PCM_CHANNEL_FL, /* WSA2 Ch1 */ ++ PCM_CHANNEL_FR /* WSA2 Ch2 */ ++}; ++ ++static int ayaneo_ps2_snd_prepare(struct snd_pcm_substream *substream) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ unsigned int channels = substream->runtime->channels; ++ ++ if (cpu_dai->id != WSA_CODEC_DMA_RX_0) ++ return 0; ++ ++ if (channels != 2) ++ return -EINVAL; ++ ++ return snd_soc_dai_set_channel_map(cpu_dai, 0, NULL, ++ ARRAY_SIZE(ayaneo_ps2_channels_mapping), ++ ayaneo_ps2_channels_mapping); ++} ++ + static int sc8280xp_snd_prepare(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + ++ if (data->priv->snd_prepare) { ++ int ret; ++ ++ ret = data->priv->snd_prepare(substream); ++ if (ret) ++ return ret; ++ } ++ + return qcom_snd_sdw_prepare(substream, &data->stream_prepared[cpu_dai->id]); + } + +@@ -117,6 +362,7 @@ static int sc8280xp_snd_hw_free(struct snd_pcm_substream *substream) + static const struct snd_soc_ops sc8280xp_be_ops = { + .startup = qcom_snd_sdw_startup, + .shutdown = qcom_snd_sdw_shutdown, ++ .hw_params = sc8280xp_snd_hw_params, + .hw_free = sc8280xp_snd_hw_free, + .prepare = sc8280xp_snd_prepare, + }; +@@ -145,38 +391,182 @@ static int sc8280xp_platform_probe(struct platform_device *pdev) + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; +- card->owner = THIS_MODULE; ++ + /* Allocate the private data */ + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + ++ data->priv = of_device_get_match_data(dev); ++ if (!data->priv) ++ return -ENODEV; ++ ++ card->owner = THIS_MODULE; + card->dev = dev; + dev_set_drvdata(dev, card); + snd_soc_card_set_drvdata(card, data); ++ card->dapm_widgets = data->priv->dapm_widgets; ++ card->num_dapm_widgets = data->priv->num_dapm_widgets; ++ card->dapm_routes = data->priv->dapm_routes; ++ card->num_dapm_routes = data->priv->num_dapm_routes; ++ card->controls = data->priv->controls; ++ card->num_controls = data->priv->num_controls; ++ + ret = qcom_snd_parse_of(card); + if (ret) + return ret; + +- card->driver_name = of_device_get_match_data(dev); ++ card->driver_name = data->priv->driver_name; + sc8280xp_add_be_ops(card); + return devm_snd_soc_register_card(dev, card); + } + ++static struct qcom_snd_soc_common ayaneo_ps2_priv_data = { ++ .driver_name = "ayaneo-ps2", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .snd_prepare = ayaneo_ps2_snd_prepare, ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common eliza_priv_data = { ++ .driver_name = "eliza", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common hawi_priv_data = { ++ .driver_name = "hawi", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .codec_sysclk_set = true, ++ .mi2s_bclk_enable = true, ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common kaanapali_priv_data = { ++ .driver_name = "kaanapali", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common qcs9100_priv_data = { ++ .driver_name = "sa8775p", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++}; ++ ++static const struct qcom_snd_soc_common qcs615_priv_data = { ++ .driver_name = "qcs615", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .codec_sysclk_set = true, ++}; ++ ++static const struct qcom_snd_soc_common qcm6490_priv_data = { ++ .driver_name = "qcm6490", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common qcs6490_priv_data = { ++ .driver_name = "qcs6490", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common qcs8275_priv_data = { ++ .driver_name = "qcs8300", ++ .dapm_widgets = max98090_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(max98090_dapm_widgets), ++ .controls = max98090_controls, ++ .num_controls = ARRAY_SIZE(max98090_controls), ++ .codec_sysclk_set = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_BC_FC, ++}; ++ ++static const struct qcom_snd_soc_common sc8280xp_priv_data = { ++ .driver_name = "sc8280xp", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common sm8450_priv_data = { ++ .driver_name = "sm8450", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++ /* I2S Connected to HDMI */ ++ .mi2s_mclk_enable = true, ++ .mi2s_bclk_enable = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | ++ SND_SOC_DAIFMT_NB_NF | ++ SND_SOC_DAIFMT_I2S, ++}; ++ ++static const struct qcom_snd_soc_common sm8475_priv_data = { ++ .driver_name = "sm8475", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common sm8550_priv_data = { ++ .driver_name = "sm8550", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++ /* I2S Connected to HDMI */ ++ .mi2s_mclk_enable = true, ++ .mi2s_bclk_enable = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | ++ SND_SOC_DAIFMT_NB_NF | ++ SND_SOC_DAIFMT_I2S, ++}; ++ ++static const struct qcom_snd_soc_common sm8650_priv_data = { ++ .driver_name = "sm8650", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++ /* I2S Connected to HDMI */ ++ .mi2s_mclk_enable = true, ++ .mi2s_bclk_enable = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | ++ SND_SOC_DAIFMT_NB_NF | ++ SND_SOC_DAIFMT_I2S, ++}; ++ ++static const struct qcom_snd_soc_common sm8750_priv_data = { ++ .driver_name = "sm8750", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ + static const struct of_device_id snd_sc8280xp_dt_match[] = { +- {.compatible = "qcom,eliza-sndcard", "eliza"}, +- {.compatible = "qcom,kaanapali-sndcard", "kaanapali"}, +- {.compatible = "qcom,qcm6490-idp-sndcard", "qcm6490"}, +- {.compatible = "qcom,qcs615-sndcard", "qcs615"}, +- {.compatible = "qcom,qcs6490-rb3gen2-sndcard", "qcs6490"}, +- {.compatible = "qcom,qcs8275-sndcard", "qcs8300"}, +- {.compatible = "qcom,qcs9075-sndcard", "sa8775p"}, +- {.compatible = "qcom,qcs9100-sndcard", "sa8775p"}, +- {.compatible = "qcom,sc8280xp-sndcard", "sc8280xp"}, +- {.compatible = "qcom,sm8450-sndcard", "sm8450"}, +- {.compatible = "qcom,sm8550-sndcard", "sm8550"}, +- {.compatible = "qcom,sm8650-sndcard", "sm8650"}, +- {.compatible = "qcom,sm8750-sndcard", "sm8750"}, ++ { .compatible = "ayaneo,pocket-s2-sndcard", .data = &ayaneo_ps2_priv_data }, ++ { .compatible = "qcom,eliza-sndcard", .data = &eliza_priv_data }, ++ { .compatible = "qcom,hawi-sndcard", .data = &hawi_priv_data }, ++ { .compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data }, ++ { .compatible = "qcom,maili-sndcard", .data = &hawi_priv_data }, ++ { .compatible = "qcom,qcm6490-idp-sndcard", .data = &qcm6490_priv_data }, ++ { .compatible = "qcom,qcs615-sndcard", .data = &qcs615_priv_data }, ++ { .compatible = "qcom,qcs6490-rb3gen2-sndcard", .data = &qcs6490_priv_data }, ++ { .compatible = "qcom,qcs8275-sndcard", .data = &qcs8275_priv_data }, ++ { .compatible = "qcom,qcs9075-sndcard", .data = &qcs9100_priv_data }, ++ { .compatible = "qcom,qcs9100-sndcard", .data = &qcs9100_priv_data }, ++ { .compatible = "qcom,sc8280xp-sndcard", .data = &sc8280xp_priv_data }, ++ { .compatible = "qcom,sm8450-sndcard", .data = &sm8450_priv_data }, ++ { .compatible = "qcom,sm8475-sndcard", .data = &sm8475_priv_data }, ++ { .compatible = "qcom,sm8550-sndcard", .data = &sm8550_priv_data }, ++ { .compatible = "qcom,sm8650-sndcard", .data = &sm8650_priv_data }, ++ { .compatible = "qcom,sm8750-sndcard", .data = &sm8750_priv_data }, + {} + }; + +diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c +--- a/sound/soc/qcom/sdm845.c ++++ b/sound/soc/qcom/sdm845.c +@@ -36,7 +36,6 @@ struct sdm845_snd_data { + bool jack_setup; + bool slim_port_setup; + bool stream_prepared[AFE_PORT_MAX]; +- struct snd_soc_card *card; + uint32_t pri_mi2s_clk_count; + uint32_t sec_mi2s_clk_count; + uint32_t quat_tdm_clk_count; +@@ -564,7 +563,6 @@ static int sdm845_snd_platform_probe(struct platform_device *pdev) + if (ret) + return ret; + +- data->card = card; + snd_soc_card_set_drvdata(card, data); + + sdm845_add_ops(card); +diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c +--- a/sound/soc/qcom/sm8250.c ++++ b/sound/soc/qcom/sm8250.c +@@ -112,6 +112,22 @@ static int sm8250_snd_startup(struct snd_pcm_substream *substream) + snd_soc_dai_set_fmt(cpu_dai, fmt); + snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); + break; ++ case LPI_MI2S_RX_0: ++ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; ++ snd_soc_dai_set_sysclk(cpu_dai, ++ Q6AFE_LPASS_CLK_ID_INT0_MI2S_IBIT, ++ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); ++ snd_soc_dai_set_fmt(cpu_dai, fmt); ++ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); ++ break; ++ case LPI_MI2S_TX_3: ++ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; ++ snd_soc_dai_set_sysclk(cpu_dai, ++ Q6AFE_LPASS_CLK_ID_INT3_MI2S_IBIT, ++ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_CAPTURE); ++ snd_soc_dai_set_fmt(cpu_dai, fmt); ++ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); ++ break; + default: + break; + } +@@ -194,6 +210,7 @@ static const struct of_device_id snd_sm8250_dt_match[] = { + { .compatible = "qcom,qrb2210-sndcard", .data = "qcm2290" }, + { .compatible = "qcom,qrb4210-rb2-sndcard", .data = "sm4250" }, + { .compatible = "qcom,qrb5165-rb5-sndcard", .data = "sm8250" }, ++ { .compatible = "qcom,sdm660-sndcard", .data = "sdm660" }, + { .compatible = "qcom,sm8250-sndcard", .data = "sm8250" }, + {} + }; +diff --git a/sound/soc/qcom/storm.c b/sound/soc/qcom/storm.c +--- a/sound/soc/qcom/storm.c ++++ b/sound/soc/qcom/storm.c +@@ -64,21 +64,21 @@ static struct snd_soc_dai_link storm_dai_link = { + SND_SOC_DAILINK_REG(hifi), + }; + +-static int storm_parse_of(struct snd_soc_card *card) ++static int storm_parse_of(struct device *dev) + { +- struct snd_soc_dai_link *dai_link = card->dai_link; +- struct device_node *np = card->dev->of_node; ++ struct snd_soc_dai_link *dai_link = &storm_dai_link; ++ struct device_node *np = dev->of_node; + + dai_link->cpus->of_node = of_parse_phandle(np, "cpu", 0); + if (!dai_link->cpus->of_node) { +- dev_err(card->dev, "error getting cpu phandle\n"); ++ dev_err(dev, "error getting cpu phandle\n"); + return -EINVAL; + } + dai_link->platforms->of_node = dai_link->cpus->of_node; + + dai_link->codecs->of_node = of_parse_phandle(np, "codec", 0); + if (!dai_link->codecs->of_node) { +- dev_err(card->dev, "error getting codec phandle\n"); ++ dev_err(dev, "error getting codec phandle\n"); + return -EINVAL; + } + +@@ -106,7 +106,7 @@ static int storm_platform_probe(struct platform_device *pdev) + card->dai_link = &storm_dai_link; + card->num_links = 1; + +- ret = storm_parse_of(card); ++ ret = storm_parse_of(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "error resolving dai links: %d\n", ret); + return ret; +diff --git a/sound/soc/renesas/fsi.c b/sound/soc/renesas/fsi.c +--- a/sound/soc/renesas/fsi.c ++++ b/sound/soc/renesas/fsi.c +@@ -1979,8 +1979,8 @@ static const struct of_device_id fsi_of_match[] = { + MODULE_DEVICE_TABLE(of, fsi_of_match); + + static const struct platform_device_id fsi_id_table[] = { +- { "sh_fsi", (kernel_ulong_t)&fsi1_core }, +- {}, ++ /* an array with no valid entry prevents matching on driver name */ ++ { } + }; + MODULE_DEVICE_TABLE(platform, fsi_id_table); + +@@ -1992,7 +1992,7 @@ static int fsi_probe(struct platform_device *pdev) + const struct fsi_core *core; + struct fsi_priv *fsi; + struct resource *res; +- unsigned int irq; ++ int irq; + int ret; + + memset(&info, 0, sizeof(info)); +@@ -2007,12 +2007,15 @@ static int fsi_probe(struct platform_device *pdev) + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- irq = platform_get_irq(pdev, 0); +- if (!res || (int)irq <= 0) { ++ if (!res) { + dev_err(&pdev->dev, "Not enough FSI platform resources.\n"); + return -ENODEV; + } + ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return irq; ++ + master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL); + if (!master) + return -ENOMEM; +diff --git a/sound/soc/renesas/rcar/adg.c b/sound/soc/renesas/rcar/adg.c +--- a/sound/soc/renesas/rcar/adg.c ++++ b/sound/soc/renesas/rcar/adg.c +@@ -565,7 +565,7 @@ static struct clk *rsnd_adg_create_null_clk(struct rsnd_priv *priv, + struct clk *clk; + + clk = clk_register_fixed_rate(dev, name, parent, 0, 0); +- if (IS_ERR_OR_NULL(clk)) { ++ if (IS_ERR(clk)) { + dev_err(dev, "create null clk error\n"); + return ERR_CAST(clk); + } +@@ -618,7 +618,7 @@ static int rsnd_adg_get_clkin(struct rsnd_priv *priv) + * No "adg" is not error + */ + clk = devm_clk_get(dev, "adg"); +- if (IS_ERR_OR_NULL(clk)) ++ if (IS_ERR(clk)) + clk = rsnd_adg_null_clk_get(priv); + adg->adg = clk; + +@@ -626,9 +626,9 @@ static int rsnd_adg_get_clkin(struct rsnd_priv *priv) + for (i = 0; i < clkin_size; i++) { + clk = devm_clk_get(dev, clkin_name[i]); + +- if (IS_ERR_OR_NULL(clk)) ++ if (IS_ERR(clk)) + clk = rsnd_adg_null_clk_get(priv); +- if (IS_ERR_OR_NULL(clk)) ++ if (IS_ERR(clk)) + goto err; + + adg->clkin[i] = clk; +diff --git a/sound/soc/renesas/siu_dai.c b/sound/soc/renesas/siu_dai.c +--- a/sound/soc/renesas/siu_dai.c ++++ b/sound/soc/renesas/siu_dai.c +@@ -715,7 +715,6 @@ static struct snd_soc_dai_driver siu_i2s_dai = { + + static int siu_probe(struct platform_device *pdev) + { +- const struct firmware *fw_entry; + struct resource *res, *region; + struct siu_info *info; + int ret; +@@ -726,6 +725,7 @@ static int siu_probe(struct platform_device *pdev) + siu_i2s_data = info; + info->dev = &pdev->dev; + ++ const struct firmware *fw_entry __free(firmware) = NULL; + ret = request_firmware(&fw_entry, "siu_spb.bin", &pdev->dev); + if (ret) + return ret; +@@ -736,8 +736,6 @@ static int siu_probe(struct platform_device *pdev) + */ + memcpy(&info->fw, fw_entry->data, fw_entry->size); + +- release_firmware(fw_entry); +- + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; +diff --git a/sound/soc/rockchip/rk3288_hdmi_analog.c b/sound/soc/rockchip/rk3288_hdmi_analog.c +--- a/sound/soc/rockchip/rk3288_hdmi_analog.c ++++ b/sound/soc/rockchip/rk3288_hdmi_analog.c +@@ -185,10 +185,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + gpiod_set_consumer_name(machine->gpio_hp_en, "hp_en"); + + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); +- if (ret) { +- dev_err(card->dev, "SoC parse card name failed %d\n", ret); ++ if (ret) + return ret; +- } + + rk_dailink.codecs[0].of_node = of_parse_phandle(np, + "rockchip,audio-codec", +@@ -207,10 +205,9 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + } + + ret = snd_soc_get_dai_name(&args, &rk_dailink.codecs[0].dai_name); +- if (ret) { +- dev_err(&pdev->dev, "Unable to get codec_dai_name\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "Unable to get codec_dai_name\n"); + + rk_dailink.cpus->of_node = of_parse_phandle(np, "rockchip,i2s-controller", + 0); +@@ -223,11 +220,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + rk_dailink.platforms->of_node = rk_dailink.cpus->of_node; + + ret = snd_soc_of_parse_audio_routing(card, "rockchip,routing"); +- if (ret) { +- dev_err(&pdev->dev, +- "Unable to parse 'rockchip,routing' property\n"); ++ if (ret) + return ret; +- } + + snd_soc_card_set_drvdata(card, machine); + +diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c +--- a/sound/soc/rockchip/rockchip_i2s.c ++++ b/sound/soc/rockchip/rockchip_i2s.c +@@ -767,16 +767,14 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + + /* try to prepare related clocks */ + i2s->hclk = devm_clk_get_enabled(&pdev->dev, "i2s_hclk"); +- if (IS_ERR(i2s->hclk)) { +- dev_err(&pdev->dev, "Can't retrieve i2s bus clock\n"); +- return PTR_ERR(i2s->hclk); +- } ++ if (IS_ERR(i2s->hclk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->hclk), ++ "Can't retrieve i2s bus clock\n"); + + i2s->mclk = devm_clk_get(&pdev->dev, "i2s_clk"); +- if (IS_ERR(i2s->mclk)) { +- dev_err(&pdev->dev, "Can't retrieve i2s master clock\n"); +- return PTR_ERR(i2s->mclk); +- } ++ if (IS_ERR(i2s->mclk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->mclk), ++ "Can't retrieve i2s master clock\n"); + + regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(regs)) +@@ -792,7 +790,11 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + + i2s->bclk_ratio = 64; + i2s->pinctrl = devm_pinctrl_get(&pdev->dev); +- if (!IS_ERR(i2s->pinctrl)) { ++ if (IS_ERR(i2s->pinctrl)) { ++ if (PTR_ERR(i2s->pinctrl) == -EPROBE_DEFER) ++ return -EPROBE_DEFER; ++ dev_dbg(&pdev->dev, "failed to find i2s pinctrl\n"); ++ } else { + i2s->bclk_on = pinctrl_lookup_state(i2s->pinctrl, "bclk_on"); + if (!IS_ERR_OR_NULL(i2s->bclk_on)) { + i2s->bclk_off = pinctrl_lookup_state(i2s->pinctrl, "bclk_off"); +@@ -801,8 +803,6 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + return -EINVAL; + } + } +- } else { +- dev_dbg(&pdev->dev, "failed to find i2s pinctrl\n"); + } + + i2s_pinctrl_select_bclk_off(i2s); +@@ -831,16 +831,12 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + &rockchip_i2s_component, + dai, 1); + +- if (ret) { +- dev_err(&pdev->dev, "Could not register DAI\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Could not register PCM\n"); ++ if (ret) + return ret; +- } + + return 0; + } +diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c +--- a/sound/soc/rockchip/rockchip_i2s_tdm.c ++++ b/sound/soc/rockchip/rockchip_i2s_tdm.c +@@ -1263,16 +1263,14 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) + i2s_tdm->tx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, + "tx-m"); + if (IS_ERR(i2s_tdm->tx_reset)) { +- ret = PTR_ERR(i2s_tdm->tx_reset); +- return dev_err_probe(i2s_tdm->dev, ret, ++ return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->tx_reset), + "Error in tx-m reset control\n"); + } + + i2s_tdm->rx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, + "rx-m"); + if (IS_ERR(i2s_tdm->rx_reset)) { +- ret = PTR_ERR(i2s_tdm->rx_reset); +- return dev_err_probe(i2s_tdm->dev, ret, ++ return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->rx_reset), + "Error in rx-m reset control\n"); + } + +@@ -1363,17 +1361,12 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) + ret = devm_snd_soc_register_component(&pdev->dev, + &rockchip_i2s_tdm_component, + i2s_tdm->dai, 1); +- +- if (ret) { +- dev_err(&pdev->dev, "Could not register DAI\n"); ++ if (ret) + goto err_suspend; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Could not register PCM\n"); ++ if (ret) + goto err_suspend; +- } + + return 0; + +diff --git a/sound/soc/rockchip/rockchip_max98090.c b/sound/soc/rockchip/rockchip_max98090.c +--- a/sound/soc/rockchip/rockchip_max98090.c ++++ b/sound/soc/rockchip/rockchip_max98090.c +@@ -428,11 +428,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + + /* Parse card name. */ + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); +- if (ret) { +- dev_err(&pdev->dev, +- "Soc parse card name failed %d\n", ret); ++ if (ret) + return ret; +- } + + /* register the soc card */ + ret = devm_snd_soc_register_card(&pdev->dev, card); +diff --git a/sound/soc/rockchip/rockchip_pdm.c b/sound/soc/rockchip/rockchip_pdm.c +--- a/sound/soc/rockchip/rockchip_pdm.c ++++ b/sound/soc/rockchip/rockchip_pdm.c +@@ -630,10 +630,8 @@ static int rockchip_pdm_probe(struct platform_device *pdev) + &rockchip_pdm_component, + &rockchip_pdm_dai, 1); + +- if (ret) { +- dev_err(&pdev->dev, "could not register dai: %d\n", ret); ++ if (ret) + goto err_suspend; +- } + + rockchip_pdm_rxctrl(pdm, 0); + +@@ -642,10 +640,8 @@ static int rockchip_pdm_probe(struct platform_device *pdev) + goto err_suspend; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "could not register pcm: %d\n", ret); ++ if (ret) + goto err_suspend; +- } + + return 0; + +diff --git a/sound/soc/rockchip/rockchip_rt5645.c b/sound/soc/rockchip/rockchip_rt5645.c +--- a/sound/soc/rockchip/rockchip_rt5645.c ++++ b/sound/soc/rockchip/rockchip_rt5645.c +@@ -191,11 +191,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + rk_dailink.platforms->of_node = rk_dailink.cpus->of_node; + + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); +- if (ret) { +- dev_err(&pdev->dev, +- "Soc parse card name failed %d\n", ret); ++ if (ret) + goto put_cpu_of_node; +- } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { +diff --git a/sound/soc/rockchip/rockchip_sai.c b/sound/soc/rockchip/rockchip_sai.c +--- a/sound/soc/rockchip/rockchip_sai.c ++++ b/sound/soc/rockchip/rockchip_sai.c +@@ -1428,12 +1428,13 @@ static int rockchip_sai_probe(struct platform_device *pdev) + "Failed to initialize regmap\n"); + + irq = platform_get_irq_optional(pdev, 0); ++ if (irq == -EPROBE_DEFER) ++ return irq; + if (irq > 0) { + ret = devm_request_irq(&pdev->dev, irq, rockchip_sai_isr, + IRQF_SHARED, node->name, sai); + if (ret) +- return dev_err_probe(&pdev->dev, ret, +- "Failed to request irq %d\n", irq); ++ return ret; + } else { + dev_dbg(&pdev->dev, "Asked for an IRQ but got %d\n", irq); + } +@@ -1456,7 +1457,7 @@ static int rockchip_sai_probe(struct platform_device *pdev) + + ret = rockchip_sai_parse_paths(sai, node); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "Failed to parse paths\n"); ++ return ret; + + /* + * From here on, all register accesses need to be wrapped in +@@ -1471,18 +1472,14 @@ static int rockchip_sai_probe(struct platform_device *pdev) + return dev_err_probe(&pdev->dev, ret, "Failed to resume device\n"); + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register PCM: %d\n", ret); ++ if (ret) + goto err_runtime_suspend; +- } + + ret = devm_snd_soc_register_component(&pdev->dev, + &rockchip_sai_component, + dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register component: %d\n", ret); ++ if (ret) + goto err_runtime_suspend; +- } + + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_put(&pdev->dev); +diff --git a/sound/soc/rockchip/rockchip_spdif.c b/sound/soc/rockchip/rockchip_spdif.c +--- a/sound/soc/rockchip/rockchip_spdif.c ++++ b/sound/soc/rockchip/rockchip_spdif.c +@@ -396,13 +396,13 @@ static int rk_spdif_probe(struct platform_device *pdev) + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "Could not register PCM\n"); ++ return ret; + + ret = devm_snd_soc_register_component(&pdev->dev, + &rk_spdif_component, + &rk_spdif_dai, 1); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "Could not register DAI\n"); ++ return ret; + + return 0; + } +diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c +--- a/sound/soc/samsung/i2s.c ++++ b/sound/soc/samsung/i2s.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -512,7 +513,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + u32 mod, mask, val = 0; + int ret = 0; + +- pm_runtime_get_sync(dai->dev); ++ guard(pm_runtime_active)(dai->dev); + + scoped_guard(spinlock_irqsave, &priv->lock) + mod = readl(priv->addr + I2SMOD); +@@ -537,8 +538,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + && (mod & cdcon_mask))))) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); +- ret = -EAGAIN; +- goto err; ++ return -EAGAIN; + } + + if (dir == SND_SOC_CLOCK_IN) +@@ -566,7 +566,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + } else { + priv->rclk_srcrate = + clk_get_rate(priv->op_clk); +- goto done; ++ return 0; + } + } + +@@ -580,14 +580,14 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + if (WARN_ON(IS_ERR(priv->op_clk))) { + ret = PTR_ERR(priv->op_clk); + priv->op_clk = NULL; +- goto err; ++ return ret; + } + + ret = clk_prepare_enable(priv->op_clk); + if (ret) { + clk_put(priv->op_clk); + priv->op_clk = NULL; +- goto err; ++ return ret; + } + priv->rclk_srcrate = clk_get_rate(priv->op_clk); + +@@ -595,11 +595,10 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + || (clk_id && !(mod & rsrc_mask))) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); +- ret = -EAGAIN; +- goto err; ++ return -EAGAIN; + } else { + /* Call can't be on the active DAI */ +- goto done; ++ return 0; + } + + if (clk_id == 1) +@@ -607,8 +606,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + break; + default: + dev_err(&i2s->pdev->dev, "We don't serve that!\n"); +- ret = -EINVAL; +- goto err; ++ return -EINVAL; + } + + scoped_guard(spinlock_irqsave, &priv->lock) { +@@ -616,13 +614,8 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + mod = (mod & ~mask) | val; + writel(mod, priv->addr + I2SMOD); + } +-done: +- pm_runtime_put(dai->dev); + + return 0; +-err: +- pm_runtime_put(dai->dev); +- return ret; + } + + static int i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +@@ -1123,15 +1116,31 @@ static const struct snd_soc_dapm_widget samsung_i2s_widgets[] = { + + static const struct snd_soc_dapm_route samsung_i2s_dapm_routes[] = { + { "Playback Mixer", NULL, "Primary Playback" }, +- { "Playback Mixer", NULL, "Secondary Playback" }, +- + { "Mixer DAI TX", NULL, "Playback Mixer" }, + { "Primary Capture", NULL, "Mixer DAI RX" }, + }; + ++static const struct snd_soc_dapm_route samsung_i2s_dapm_routes_sec_play[] = { ++ { "Playback Mixer", NULL, "Secondary Playback" }, ++}; ++ ++static int samsung_i2s_component_probe(struct snd_soc_component *component) ++{ ++ struct samsung_i2s_priv *priv = snd_soc_component_get_drvdata(component); ++ ++ if (priv->quirks & QUIRK_SEC_DAI) ++ snd_soc_dapm_add_routes(snd_soc_component_to_dapm(component), ++ samsung_i2s_dapm_routes_sec_play, ++ ARRAY_SIZE(samsung_i2s_dapm_routes_sec_play)); ++ ++ return 0; ++} ++ + static const struct snd_soc_component_driver samsung_i2s_component = { + .name = "samsung-i2s", + ++ .probe = samsung_i2s_component_probe, ++ + .dapm_widgets = samsung_i2s_widgets, + .num_dapm_widgets = ARRAY_SIZE(samsung_i2s_widgets), + +@@ -1448,10 +1457,10 @@ static int samsung_i2s_probe(struct platform_device *pdev) + regs_base = res->start; + + priv->clk = devm_clk_get(&pdev->dev, "iis"); +- if (IS_ERR(priv->clk)) { +- dev_err(&pdev->dev, "Failed to get iis clock\n"); +- return PTR_ERR(priv->clk); +- } ++ if (IS_ERR(priv->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk), ++ "Failed to get iis clock\n"); ++ + + ret = clk_prepare_enable(priv->clk); + if (ret != 0) { +@@ -1657,8 +1666,7 @@ static const struct samsung_i2s_dai_data i2sv6_dai_type __maybe_unused = { + }; + + static const struct samsung_i2s_dai_data i2sv7_dai_type __maybe_unused = { +- .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | +- QUIRK_SUPPORTS_TDM, ++ .quirks = QUIRK_PRI_6CHAN | QUIRK_NEED_RSTCLR | QUIRK_SUPPORTS_TDM, + .pcm_rates = SNDRV_PCM_RATE_8000_192000, + .i2s_variant_regs = &i2sv7_regs, + }; +@@ -1682,7 +1690,7 @@ static const struct platform_device_id samsung_i2s_driver_ids[] = { + .name = "samsung-i2s", + .driver_data = (kernel_ulong_t)&i2sv3_dai_type, + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, samsung_i2s_driver_ids); + +diff --git a/sound/soc/samsung/smdk_spdif.c b/sound/soc/samsung/smdk_spdif.c +--- a/sound/soc/samsung/smdk_spdif.c ++++ b/sound/soc/samsung/smdk_spdif.c +@@ -130,12 +130,8 @@ static int smdk_hw_params(struct snd_pcm_substream *substream, + return ret; + + /* Set S/PDIF uses internal source clock */ +- ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK, +- rclk_rate, SND_SOC_CLOCK_IN); +- if (ret < 0) +- return ret; +- +- return ret; ++ return snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK, ++ rclk_rate, SND_SOC_CLOCK_IN); + } + + static const struct snd_soc_ops smdk_spdif_ops = { +diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c +--- a/sound/soc/samsung/spdif.c ++++ b/sound/soc/samsung/spdif.c +@@ -380,8 +380,8 @@ static int spdif_probe(struct platform_device *pdev) + + spdif->pclk = devm_clk_get(&pdev->dev, "spdif"); + if (IS_ERR(spdif->pclk)) { +- dev_err(&pdev->dev, "failed to get peri-clock\n"); +- ret = -ENOENT; ++ ret = dev_err_probe(&pdev->dev, PTR_ERR(spdif->pclk), ++ "failed to get peri-clock\n"); + goto err0; + } + ret = clk_prepare_enable(spdif->pclk); +@@ -390,8 +390,8 @@ static int spdif_probe(struct platform_device *pdev) + + spdif->sclk = devm_clk_get(&pdev->dev, "sclk_spdif"); + if (IS_ERR(spdif->sclk)) { +- dev_err(&pdev->dev, "failed to get internal source clock\n"); +- ret = -ENOENT; ++ ret = dev_err_probe(&pdev->dev, PTR_ERR(spdif->sclk), ++ "failed to get internal source clock\n"); + goto err1; + } + ret = clk_prepare_enable(spdif->sclk); +diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c +--- a/sound/soc/sdca/sdca_asoc.c ++++ b/sound/soc/sdca/sdca_asoc.c +@@ -364,7 +364,6 @@ static int entity_parse_ot(struct device *dev, + + /** + * sdca_asoc_pde_poll_actual_ps - Verify PDE power state reached target state +- * @dev: Pointer to the device for error logging. + * @regmap: Register map for reading ACTUAL_PS register. + * @function_id: SDCA function identifier. + * @entity_id: SDCA entity identifier for the power domain. +@@ -389,11 +388,11 @@ static int entity_parse_ot(struct device *dev, + * polling times out before reaching the target state, or a negative error code if + * a register read fails. + */ +-int sdca_asoc_pde_poll_actual_ps(struct device *dev, struct regmap *regmap, ++int sdca_asoc_pde_poll_actual_ps(struct regmap *regmap, + int function_id, int entity_id, +- int from_ps, int to_ps, +- const struct sdca_pde_delay *pde_delays, +- int num_delays) ++ int from_ps, int to_ps, ++ const struct sdca_pde_delay *pde_delays, ++ int num_delays) + { + static const int polls = 100; + static const int default_poll_us = 1000; +@@ -451,14 +450,14 @@ static int entity_pde_event(struct snd_soc_dapm_widget *widget, + return 0; + } + +- ret = sdca_asoc_pde_poll_actual_ps(component->dev, component->regmap, ++ ret = sdca_asoc_pde_poll_actual_ps(component->regmap, + SDW_SDCA_CTL_FUNC(widget->reg), + SDW_SDCA_CTL_ENT(widget->reg), + from, to, + entity->pde.max_delay, + entity->pde.num_max_delay); + if (ret) +- dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n", ++ dev_err(component->dev, "%s: pde transition %x -> %x failed: %d\n", + entity->label, from, to, ret); + + return ret; +@@ -1231,7 +1230,7 @@ static u64 width_find_mask(unsigned int bits) + } + } + +-static int populate_rate_format(struct device *dev, ++int sdca_asoc_populate_rate_format(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct snd_soc_pcm_stream *stream) +@@ -1292,6 +1291,7 @@ static int populate_rate_format(struct device *dev, + + return 0; + } ++EXPORT_SYMBOL_NS(sdca_asoc_populate_rate_format, "SND_SOC_SDCA"); + + /** + * sdca_asoc_populate_dais - fill in an array of DAI drivers for a Function +@@ -1344,7 +1344,7 @@ int sdca_asoc_populate_dais(struct device *dev, struct sdca_function_data *funct + stream->channels_min = 1; + stream->channels_max = SDCA_MAX_CHANNEL_COUNT; + +- ret = populate_rate_format(dev, function, entity, stream); ++ ret = sdca_asoc_populate_rate_format(dev, function, entity, stream); + if (ret) + return ret; + +diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c +--- a/sound/soc/sdca/sdca_class_function.c ++++ b/sound/soc/sdca/sdca_class_function.c +@@ -329,7 +329,7 @@ static int class_function_probe(struct auxiliary_device *auxdev, + drv->core = core; + drv->function = &sdev->function; + +- ret = sdca_parse_function(dev, core->sdw, drv->function); ++ ret = sdca_parse_function(dev, drv->function); + if (ret) + return ret; + +diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c +--- a/sound/soc/sdca/sdca_fdl.c ++++ b/sound/soc/sdca/sdca_fdl.c +@@ -195,7 +195,6 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + { + struct device *dev = interrupt->dev; + struct sdca_fdl_data *fdl_data = &interrupt->function->fdl_data; +- const struct firmware *firmware = NULL; + struct acpi_sw_file *swf = NULL, *tmp; + struct sdca_fdl_file *fdl_file; + char *disk_filename; +@@ -230,6 +229,7 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + + dev_dbg(dev, "FDL disk filename: %s\n", disk_filename); + ++ const struct firmware *firmware __free(firmware) = NULL; + ret = firmware_request_nowarn(&firmware, disk_filename, dev); + kfree(disk_filename); + if (ret) { +@@ -258,8 +258,7 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + + if (!swf) { + dev_err(dev, "failed to locate SWF\n"); +- ret = -ENOENT; +- goto error; ++ return -ENOENT; + } + + dev_info(dev, "loading SWF: %x-%x-%x\n", +@@ -271,9 +270,6 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + SDCA_CTL_XU_FDL_MESSAGEOFFSET, fdl_file->fdl_offset, + SDCA_CTL_XU_FDL_MESSAGELENGTH, swf->data, + swf->file_length - offsetof(struct acpi_sw_file, data)); +- +-error: +- release_firmware(firmware); + return ret; + } + +diff --git a/sound/soc/sdca/sdca_function_device.c b/sound/soc/sdca/sdca_function_device.c +--- a/sound/soc/sdca/sdca_function_device.c ++++ b/sound/soc/sdca/sdca_function_device.c +@@ -32,7 +32,8 @@ static void sdca_dev_release(struct device *dev) + + /* alloc, init and add link devices */ + static struct sdca_dev *sdca_dev_register(struct device *parent, +- struct sdca_function_desc *function_desc) ++ struct sdca_function_desc *function_desc, ++ struct acpi_table_swft *swft) + { + struct sdca_dev *sdev; + struct auxiliary_device *auxdev; +@@ -50,6 +51,7 @@ static struct sdca_dev *sdca_dev_register(struct device *parent, + auxdev->dev.release = sdca_dev_release; + + sdev->function.desc = function_desc; ++ sdev->function.fdl_data.swft = swft; + + rc = ida_alloc(&sdca_function_ida, GFP_KERNEL); + if (rc < 0) { +@@ -99,7 +101,8 @@ int sdca_dev_register_functions(struct sdw_slave *slave) + struct sdca_dev *func_dev; + + func_dev = sdca_dev_register(&slave->dev, +- &sdca_data->function[i]); ++ &sdca_data->function[i], ++ sdca_data->swft); + if (IS_ERR(func_dev)) { + ret = PTR_ERR(func_dev); + /* +diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c +--- a/sound/soc/sdca/sdca_functions.c ++++ b/sound/soc/sdca/sdca_functions.c +@@ -7,6 +7,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -18,7 +19,6 @@ + #include + #include + #include +-#include + + /* + * Should be long enough to encompass all the MIPI DisCo properties. +@@ -1365,90 +1365,76 @@ static int find_sdca_entity_ge(struct device *dev, + return -EINVAL; + } + +-static int +-find_sdca_entity_hide(struct device *dev, struct sdw_slave *sdw, +- struct fwnode_handle *function_node, +- struct fwnode_handle *entity_node, struct sdca_entity *entity) ++static int find_sdca_entity_hide(struct device *dev, ++ struct fwnode_handle *entity_node, ++ struct sdca_entity *entity) + { + struct sdca_entity_hide *hide = &entity->hide; +- unsigned int delay, *af_list = hide->af_number_list; +- int nval, ret; +- unsigned char *report_desc = NULL; ++ int num_reports, ret; ++ unsigned int delay; + + ret = fwnode_property_read_u32(entity_node, +- "mipi-sdca-RxUMP-ownership-transition-max-delay", &delay); ++ "mipi-sdca-RxUMP-ownership-transition-max-delay", ++ &delay); + if (!ret) + hide->max_delay = delay; + +- nval = fwnode_property_count_u32(entity_node, "mipi-sdca-HIDTx-supported-report-ids"); +- if (nval > 0) { +- hide->num_hidtx_ids = nval; ++ num_reports = fwnode_property_count_u32(entity_node, ++ "mipi-sdca-HIDTx-supported-report-ids"); ++ if (num_reports < 0 && num_reports != -EINVAL) { ++ dev_err(dev, "%pfwP: failed to read hid tx ids: %d\n", ++ entity_node, num_reports); ++ return num_reports; ++ } else if (num_reports > 0) { ++ hide->num_hidtx_ids = num_reports; + hide->hidtx_ids = devm_kcalloc(dev, hide->num_hidtx_ids, + sizeof(*hide->hidtx_ids), GFP_KERNEL); + if (!hide->hidtx_ids) + return -ENOMEM; + +- ret = fwnode_property_read_u32_array(entity_node, +- "mipi-sdca-HIDTx-supported-report-ids", +- hide->hidtx_ids, +- hide->num_hidtx_ids); +- if (ret < 0) +- return ret; ++ fwnode_property_read_u32_array(entity_node, ++ "mipi-sdca-HIDTx-supported-report-ids", ++ hide->hidtx_ids, hide->num_hidtx_ids); + } + +- nval = fwnode_property_count_u32(entity_node, "mipi-sdca-HIDRx-supported-report-ids"); +- if (nval > 0) { +- hide->num_hidrx_ids = nval; ++ num_reports = fwnode_property_count_u32(entity_node, ++ "mipi-sdca-HIDRx-supported-report-ids"); ++ if (num_reports < 0 && num_reports != -EINVAL) { ++ dev_err(dev, "%pfwP: failed to read hid rx ids: %d\n", ++ entity_node, num_reports); ++ return num_reports; ++ } else if (num_reports > 0) { ++ hide->num_hidrx_ids = num_reports; + hide->hidrx_ids = devm_kcalloc(dev, hide->num_hidrx_ids, + sizeof(*hide->hidrx_ids), GFP_KERNEL); + if (!hide->hidrx_ids) + return -ENOMEM; + +- ret = fwnode_property_read_u32_array(entity_node, +- "mipi-sdca-HIDRx-supported-report-ids", +- hide->hidrx_ids, +- hide->num_hidrx_ids); +- if (ret < 0) +- return ret; ++ fwnode_property_read_u32_array(entity_node, ++ "mipi-sdca-HIDRx-supported-report-ids", ++ hide->hidrx_ids, hide->num_hidrx_ids); + } + +- nval = fwnode_property_count_u32(entity_node, "mipi-sdca-hide-related-audio-function-list"); +- if (nval <= 0) { ++ /* ++ * FIXME: This should probably link to the actual sdca_function_data pointer, ++ * but updating to do so should probably wait until we have a user. ++ */ ++ num_reports = fwnode_property_count_u32(entity_node, ++ "mipi-sdca-hide-related-audio-function-list"); ++ if (num_reports <= 0) { + dev_err(dev, "%pfwP: audio function numbers list missing: %d\n", +- entity_node, nval); ++ entity_node, num_reports); + return -EINVAL; +- } else if (nval > SDCA_MAX_FUNCTION_COUNT) { +- dev_err(dev, "%pfwP: maximum number of audio function exceeded\n", entity_node); ++ } else if (num_reports > ARRAY_SIZE(hide->af_number_list)) { ++ dev_err(dev, "%pfwP: maximum number of audio function exceeded\n", ++ entity_node); + return -EINVAL; + } + +- hide->hide_reside_function_num = nval; ++ hide->hide_reside_function_num = num_reports; + fwnode_property_read_u32_array(entity_node, +- "mipi-sdca-hide-related-audio-function-list", af_list, nval); +- +- nval = fwnode_property_count_u8(function_node, "mipi-sdca-hid-descriptor"); +- if (nval) +- fwnode_property_read_u8_array(function_node, "mipi-sdca-hid-descriptor", +- (u8 *)&hide->hid_desc, nval); +- +- if (hide->hid_desc.bNumDescriptors) { +- nval = fwnode_property_count_u8(function_node, "mipi-sdca-report-descriptor"); +- if (nval) { +- report_desc = devm_kzalloc(dev, nval, GFP_KERNEL); +- if (!report_desc) +- return -ENOMEM; +- hide->hid_report_desc = report_desc; +- fwnode_property_read_u8_array(function_node, "mipi-sdca-report-descriptor", +- report_desc, nval); +- +- /* add HID device */ +- ret = sdca_add_hid_device(dev, sdw, entity); +- if (ret) { +- dev_err(dev, "%pfwP: failed to add HID device: %d\n", entity_node, ret); +- return ret; +- } +- } +- } ++ "mipi-sdca-hide-related-audio-function-list", ++ hide->af_number_list, num_reports); + + return 0; + } +@@ -1475,8 +1461,7 @@ static int find_sdca_entity_xu(struct device *dev, + return 0; + } + +-static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, +- struct sdca_function_data *function, ++static int find_sdca_entity(struct device *dev, struct sdca_function_data *function, + struct fwnode_handle *function_node, + struct fwnode_handle *entity_node, + struct sdca_entity *entity) +@@ -1528,8 +1513,7 @@ static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, + ret = find_sdca_entity_ge(dev, entity_node, entity); + break; + case SDCA_ENTITY_TYPE_HIDE: +- ret = find_sdca_entity_hide(dev, sdw, function_node, +- entity_node, entity); ++ ret = find_sdca_entity_hide(dev, entity_node, entity); + break; + default: + break; +@@ -1544,8 +1528,7 @@ static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, + return 0; + } + +-static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, +- struct fwnode_handle *function_node, ++static int find_sdca_entities(struct device *dev, struct fwnode_handle *function_node, + struct sdca_function_data *function) + { + struct sdca_entity *entities; +@@ -1596,7 +1579,7 @@ static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, + return -EINVAL; + } + +- ret = find_sdca_entity(dev, sdw, function, function_node, ++ ret = find_sdca_entity(dev, function, function_node, + entity_node, &entities[i]); + fwnode_handle_put(entity_node); + if (ret) +@@ -1619,7 +1602,7 @@ static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, + return 0; + } + +-static struct sdca_entity *find_sdca_entity_by_label(struct sdca_function_data *function, ++struct sdca_entity *sdca_find_entity_by_label(struct sdca_function_data *function, + const char *entity_label) + { + struct sdca_entity *entity = NULL; +@@ -1648,6 +1631,7 @@ static struct sdca_entity *find_sdca_entity_by_label(struct sdca_function_data * + + return NULL; + } ++EXPORT_SYMBOL_NS(sdca_find_entity_by_label, "SND_SOC_SDCA"); + + static struct sdca_entity *find_sdca_entity_by_id(struct sdca_function_data *function, + const int id) +@@ -1688,7 +1672,7 @@ static int find_sdca_entity_connection_iot(struct device *dev, + return ret; + } + +- clock_entity = find_sdca_entity_by_label(function, clock_label); ++ clock_entity = sdca_find_entity_by_label(function, clock_label); + if (!clock_entity) { + dev_err(dev, "%s: failed to find clock with label %s\n", + entity->label, clock_label); +@@ -1873,7 +1857,7 @@ static int find_sdca_entity_connection(struct device *dev, + return ret; + } + +- connected_entity = find_sdca_entity_by_label(function, connected_label); ++ connected_entity = sdca_find_entity_by_label(function, connected_label); + if (!connected_entity) { + dev_err(dev, "%s: failed to find entity with label %s\n", + entity->label, connected_label); +@@ -2088,8 +2072,7 @@ static int find_sdca_clusters(struct device *dev, + return 0; + } + +-static int find_sdca_filesets(struct device *dev, struct sdw_slave *sdw, +- struct fwnode_handle *function_node, ++static int find_sdca_filesets(struct device *dev, struct fwnode_handle *function_node, + struct sdca_function_data *function) + { + static const int mult_fileset = 3; +@@ -2171,23 +2154,65 @@ static int find_sdca_filesets(struct device *dev, struct sdw_slave *sdw, + set->files = files; + } + +- function->fdl_data.swft = sdw->sdca_data.swft; + function->fdl_data.num_sets = num_sets; + function->fdl_data.sets = sets; + + return 0; + } + ++static int find_sdca_hid(struct device *dev, struct fwnode_handle *function_node, ++ struct sdca_function_data *function) ++{ ++ int num_desc; ++ ++ num_desc = fwnode_property_count_u8(function_node, "mipi-sdca-hid-descriptor"); ++ if (!num_desc) { ++ return 0; ++ } else if (num_desc < 0) { ++ dev_err(dev, "%pfwP: failed to read hid descriptor: %d\n", ++ function_node, num_desc); ++ return num_desc; ++ } else if (num_desc > sizeof(function->hid.desc)) { ++ dev_err(dev, "%pfwP: hid descriptor too large: %d\n", ++ function_node, num_desc); ++ return -EINVAL; ++ } ++ ++ fwnode_property_read_u8_array(function_node, "mipi-sdca-hid-descriptor", ++ (u8 *)&function->hid.desc, num_desc); ++ ++ if (!function->hid.desc.bNumDescriptors) ++ return 0; ++ ++ num_desc = fwnode_property_count_u8(function_node, "mipi-sdca-report-descriptor"); ++ if (num_desc <= 0) { ++ dev_err(dev, "%pfwP: failed to read report descriptor: %d\n", ++ function_node, num_desc); ++ ++ if (!num_desc) ++ return -EINVAL; ++ ++ return num_desc; ++ } ++ ++ function->hid.report_desc = devm_kzalloc(dev, num_desc, GFP_KERNEL); ++ if (!function->hid.report_desc) ++ return -ENOMEM; ++ ++ fwnode_property_read_u8_array(function_node, "mipi-sdca-report-descriptor", ++ function->hid.report_desc, num_desc); ++ ++ return 0; ++} ++ + /** + * sdca_parse_function - parse ACPI DisCo for a Function + * @dev: Pointer to device against which function data will be allocated. +- * @sdw: SoundWire slave device to be processed. + * @function: Pointer to the Function information, to be populated. + * + * Return: Returns 0 for success. + */ +-int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, +- struct sdca_function_data *function) ++int sdca_parse_function(struct device *dev, struct sdca_function_data *function) + { + struct fwnode_handle *node = function->desc->node; + u32 tmp; +@@ -2213,7 +2238,7 @@ int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, + if (ret) + return ret; + +- ret = find_sdca_entities(dev, sdw, node, function); ++ ret = find_sdca_entities(dev, node, function); + if (ret) + return ret; + +@@ -2225,10 +2250,20 @@ int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, + if (ret < 0) + return ret; + +- ret = find_sdca_filesets(dev, sdw, node, function); ++ ret = find_sdca_filesets(dev, node, function); + if (ret) + return ret; + ++ switch (function->desc->type) { ++ case SDCA_FUNCTION_TYPE_HID: ++ ret = find_sdca_hid(dev, node, function); ++ if (ret) ++ return ret; ++ break; ++ default: ++ break; ++ } ++ + return 0; + } + EXPORT_SYMBOL_NS(sdca_parse_function, "SND_SOC_SDCA"); +diff --git a/sound/soc/sdca/sdca_hid.c b/sound/soc/sdca/sdca_hid.c +--- a/sound/soc/sdca/sdca_hid.c ++++ b/sound/soc/sdca/sdca_hid.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -23,18 +24,18 @@ + + static int sdwhid_parse(struct hid_device *hid) + { +- struct sdca_entity *entity = hid->driver_data; ++ struct sdca_function_data *function = hid->driver_data; + unsigned int rsize; + int ret; + +- rsize = le16_to_cpu(entity->hide.hid_desc.rpt_desc.wDescriptorLength); ++ rsize = le16_to_cpu(function->hid.desc.rpt_desc.wDescriptorLength); + + if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) { + dev_err(&hid->dev, "invalid size of report descriptor (%u)\n", rsize); + return -EINVAL; + } + +- ret = hid_parse_report(hid, entity->hide.hid_report_desc, rsize); ++ ret = hid_parse_report(hid, function->hid.report_desc, rsize); + + if (!ret) + return 0; +@@ -85,10 +86,16 @@ static const struct hid_ll_driver sdw_hid_driver = { + .raw_request = sdwhid_raw_request, + }; + +-int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, +- struct sdca_entity *entity) ++/** ++ * sdca_add_hid_device - create a new SDCA HID device ++ * @interrupt: Pointer to the SDCA interrupt information structure. ++ * ++ * Return: Zero on success, and a negative error code on failure. ++ */ ++int sdca_add_hid_device(struct sdca_interrupt *interrupt) + { +- struct sdw_bus *bus = sdw->bus; ++ struct device *dev = interrupt->dev; ++ struct sdca_function_data *function = interrupt->function; + struct hid_device *hid; + int ret; + +@@ -100,16 +107,13 @@ int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, + + hid->dev.parent = dev; + hid->bus = BUS_SDW; +- hid->version = le16_to_cpu(entity->hide.hid_desc.bcdHID); ++ hid->version = le16_to_cpu(function->hid.desc.bcdHID); + +- snprintf(hid->name, sizeof(hid->name), +- "HID sdw:%01x:%01x:%04x:%04x:%02x", +- bus->controller_id, bus->link_id, sdw->id.mfg_id, +- sdw->id.part_id, sdw->id.class_id); ++ strscpy(hid->phys, dev_name(dev)); ++ snprintf(hid->name, sizeof(hid->name), "SDCA %s:%02x", ++ function->desc->name, function->desc->adr); + +- snprintf(hid->phys, sizeof(hid->phys), "%s", dev->bus->name); +- +- hid->driver_data = entity; ++ hid->driver_data = function; + + ret = hid_add_device(hid); + if (ret && ret != -ENODEV) { +@@ -118,12 +122,24 @@ int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, + return ret; + } + +- entity->hide.hid = hid; ++ interrupt->priv = hid; + + return 0; + } + EXPORT_SYMBOL_NS(sdca_add_hid_device, "SND_SOC_SDCA"); + ++/** ++ * sdca_destroy_hid_device - destroy the HID device ++ * @interrupt: Pointer to the SDCA interrupt information structure. ++ */ ++void sdca_destroy_hid_device(struct sdca_interrupt *interrupt) ++{ ++ struct hid_device *hid = interrupt->priv; ++ ++ hid_destroy_device(hid); ++} ++EXPORT_SYMBOL_NS(sdca_destroy_hid_device, "SND_SOC_SDCA"); ++ + /** + * sdca_hid_process_report - read a HID event from the device and report + * @interrupt: Pointer to the SDCA interrupt information structure. +@@ -133,7 +149,7 @@ EXPORT_SYMBOL_NS(sdca_add_hid_device, "SND_SOC_SDCA"); + int sdca_hid_process_report(struct sdca_interrupt *interrupt) + { + struct device *dev = interrupt->dev; +- struct hid_device *hid = interrupt->entity->hide.hid; ++ struct hid_device *hid = interrupt->priv; + void *val __free(kfree) = NULL; + int len, ret; + +diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c +--- a/sound/soc/sdca/sdca_interrupts.c ++++ b/sound/soc/sdca/sdca_interrupts.c +@@ -487,6 +487,12 @@ int sdca_irq_populate_early(struct device *dev, struct regmap *regmap, + } + break; + case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER): ++ interrupt->free_priv = sdca_destroy_hid_device; ++ ++ ret = sdca_add_hid_device(interrupt); ++ if (ret) ++ return ret; ++ + interrupt->handler = hid_handler; + break; + default: +diff --git a/sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c b/sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c +--- a/sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c ++++ b/sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c +@@ -99,11 +99,9 @@ static const struct snd_soc_dai_link bridge_dai_template = { + SND_SOC_DAILINK_REG(asoc_sdw_bridge_dai), + }; + +-int asoc_sdw_bridge_cs35l56_count_sidecar(struct snd_soc_card *card, ++int asoc_sdw_bridge_cs35l56_count_sidecar(struct asoc_sdw_mc_private *ctx, + int *num_dais, int *num_devs) + { +- struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); +- + if (ctx->mc_quirk & SOC_SDW_SIDECAR_AMPS) { + (*num_dais)++; + (*num_devs) += ARRAY_SIZE(bridge_cs35l56_name_prefixes); +diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c +--- a/sound/soc/sdw_utils/soc_sdw_utils.c ++++ b/sound/soc/sdw_utils/soc_sdw_utils.c +@@ -834,6 +834,35 @@ struct asoc_sdw_codec_info codec_info_list[] = { + }, + .dai_num = 2, + }, ++ { ++ .vendor_id = 0x01fa, ++ .part_id = 0x3562, ++ .name_prefix = "AMP", ++ .is_amp = true, ++ .dais = { ++ { ++ .direction = {true, false}, ++ .dai_name = "cs35l56-sdw1", ++ .component_name = "cs35l56", ++ .dai_type = SOC_SDW_DAI_TYPE_AMP, ++ .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, ++ .init = asoc_sdw_cs_amp_init, ++ .rtd_init = asoc_sdw_cs_spk_rtd_init, ++ .controls = generic_spk_controls, ++ .num_controls = ARRAY_SIZE(generic_spk_controls), ++ .widgets = generic_spk_widgets, ++ .num_widgets = ARRAY_SIZE(generic_spk_widgets), ++ }, ++ { ++ .direction = {false, true}, ++ .dai_name = "cs35l56-sdw1c", ++ .dai_type = SOC_SDW_DAI_TYPE_AMP, ++ .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, ++ .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, ++ }, ++ }, ++ .dai_num = 2, ++ }, + { + .vendor_id = 0x01fa, + .part_id = 0x3563, +@@ -1789,9 +1818,7 @@ static int is_sdca_aux_dev_present(struct device *dev, + int adr_index) + { + struct sdw_slave *slave; +- struct device *sdw_dev; + const char *sdw_codec_name; +- int ret = 0; + int i; + + if (!aux_codec_name) +@@ -1801,7 +1828,8 @@ static int is_sdca_aux_dev_present(struct device *dev, + if (!sdw_codec_name) + return -ENOMEM; + +- sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); ++ struct device *sdw_dev __free(put_device) = ++ bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); + if (!sdw_dev) { + dev_err(dev, "codec %s not found\n", sdw_codec_name); + return -EINVAL; +@@ -1811,24 +1839,19 @@ static int is_sdca_aux_dev_present(struct device *dev, + + if (!slave->sdca_data.interface_revision) { + dev_warn(dev, "No SDCA properties, assuming aux '%s' present\n", aux_codec_name); +- ret = 1; +- goto put_dev; ++ return 1; + } + + for (i = 0; i < slave->sdca_data.num_functions; i++) { + const char *fname = slave->sdca_data.function[i].name; + +- if (fname && strstr(aux_codec_name, fname)) { +- ret = 1; +- goto put_dev; +- } ++ if (fname && strstr(aux_codec_name, fname)) ++ return 1; + } + + dev_dbg(dev, "SDCA function for aux '%s' NOT FOUND on slave, skipping\n", aux_codec_name); + +-put_dev: +- put_device(sdw_dev); +- return ret; ++ return 0; + } + + int asoc_sdw_count_sdw_endpoints(struct snd_soc_card *card, +@@ -1928,9 +1951,8 @@ static int is_sdca_endpoint_present(struct device *dev, + const struct snd_soc_acpi_endpoint *adr_end; + const struct asoc_sdw_dai_info *dai_info; + struct sdw_slave *slave; +- struct device *sdw_dev; + const char *sdw_codec_name; +- int ret, i; ++ int i; + + adr_end = &adr_dev->endpoints[end_index]; + dai_info = &codec_info->dais[adr_end->num]; +@@ -1939,7 +1961,8 @@ static int is_sdca_endpoint_present(struct device *dev, + if (!sdw_codec_name) + return -ENOMEM; + +- sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); ++ struct device *sdw_dev __free(put_device) = ++ bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); + if (!sdw_dev) { + dev_err(dev, "codec %s not found\n", sdw_codec_name); + return -EINVAL; +@@ -1950,8 +1973,7 @@ static int is_sdca_endpoint_present(struct device *dev, + /* Make sure BIOS provides SDCA properties */ + if (!slave->sdca_data.interface_revision) { + dev_warn(&slave->dev, "SDCA properties not found in the BIOS\n"); +- ret = 1; +- goto put_device; ++ return 1; + } + + for (i = 0; i < slave->sdca_data.num_functions; i++) { +@@ -1960,8 +1982,7 @@ static int is_sdca_endpoint_present(struct device *dev, + if (dai_type == dai_info->dai_type) { + dev_dbg(&slave->dev, "DAI type %d sdca function %s found\n", + dai_type, slave->sdca_data.function[i].name); +- ret = 1; +- goto put_device; ++ return 1; + } + } + +@@ -1969,21 +1990,16 @@ static int is_sdca_endpoint_present(struct device *dev, + "SDCA device function for DAI type %d not supported, skip endpoint\n", + dai_info->dai_type); + +- ret = 0; +- +-put_device: +- put_device(sdw_dev); +- return ret; ++ return 0; + } + +-int asoc_sdw_parse_sdw_endpoints(struct snd_soc_card *card, ++int asoc_sdw_parse_sdw_endpoints(struct device *dev, ++ struct asoc_sdw_mc_private *ctx, + struct snd_soc_aux_dev *soc_aux, + struct asoc_sdw_dailink *soc_dais, + struct asoc_sdw_endpoint *soc_ends, + int *num_devs) + { +- struct device *dev = card->dev; +- struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_acpi_mach *mach = dev_get_platdata(dev); + struct snd_soc_acpi_mach_params *mach_params = &mach->mach_params; + const struct snd_soc_acpi_link_adr *adr_link; +@@ -2045,7 +2061,7 @@ int asoc_sdw_parse_sdw_endpoints(struct snd_soc_card *card, + ctx->ignore_internal_dmic |= codec_info->ignore_internal_dmic; + + if (codec_info->count_sidecar && codec_info->add_sidecar) { +- ret = codec_info->count_sidecar(card, &num_dais, num_devs); ++ ret = codec_info->count_sidecar(ctx, &num_dais, num_devs); + if (ret) + return ret; + +diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c +--- a/sound/soc/soc-component.c ++++ b/sound/soc/soc-component.c +@@ -29,17 +29,42 @@ static inline int _soc_component_ret_reg_rw(struct snd_soc_component *component, + func, component->name, reg); + } + +-static inline int soc_component_field_shift(struct snd_soc_component *component, +- unsigned int mask) ++struct snd_soc_component *snd_soc_component_alloc(struct device *dev) + { +- if (!mask) { +- dev_err(component->dev, "ASoC: error field mask is zero for %s\n", +- component->name); +- return 0; +- } ++ struct snd_soc_component *component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); + +- return (ffs(mask) - 1); ++ if (!component) ++ return NULL; ++ ++ component->dev = dev; ++ ++ return component; + } ++EXPORT_SYMBOL_GPL(snd_soc_component_alloc); ++ ++void snd_soc_component_set_name(struct snd_soc_component *component, const char *name) ++{ ++ component->name = name; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_set_name); ++ ++const char *snd_soc_component_name(struct snd_soc_component *component) ++{ ++ return component->name; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_name); ++ ++void snd_soc_component_set_priv(struct snd_soc_component *component, void *priv) ++{ ++ component->priv = priv; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_set_priv); ++ ++void *snd_soc_component_to_priv(struct snd_soc_component *component) ++{ ++ return component->priv; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_to_priv); + + /* + * We might want to check substream by using list. +@@ -820,6 +845,18 @@ int snd_soc_component_update_bits_async(struct snd_soc_component *component, + } + EXPORT_SYMBOL_GPL(snd_soc_component_update_bits_async); + ++static inline int soc_component_field_shift(struct snd_soc_component *component, ++ unsigned int mask) ++{ ++ if (!mask) { ++ dev_err(component->dev, "ASoC: error field mask is zero for %s\n", ++ component->name); ++ return 0; ++ } ++ ++ return (ffs(mask) - 1); ++} ++ + /** + * snd_soc_component_read_field() - Read register field value + * @component: Component to read from +diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c +--- a/sound/soc/soc-core.c ++++ b/sound/soc/soc-core.c +@@ -2707,10 +2707,11 @@ static void snd_soc_del_component_unlocked(struct snd_soc_component *component) + list_del(&component->list); + } + +-int snd_soc_component_initialize(struct snd_soc_component *component, +- const struct snd_soc_component_driver *driver, +- struct device *dev) ++static int soc_component_initialize(struct snd_soc_component *component, ++ const struct snd_soc_component_driver *driver) + { ++ struct device *dev = component->dev; ++ + component->dapm = snd_soc_dapm_alloc(dev); + if (!component->dapm) + return -ENOMEM; +@@ -2730,16 +2731,14 @@ int snd_soc_component_initialize(struct snd_soc_component *component, + } + } + +- component->dev = dev; + component->driver = driver; + + return 0; + } +-EXPORT_SYMBOL_GPL(snd_soc_component_initialize); + +-int snd_soc_add_component(struct snd_soc_component *component, +- struct snd_soc_dai_driver *dai_drv, +- int num_dai) ++static int soc_component_add(struct snd_soc_component *component, ++ struct snd_soc_dai_driver *dai_drv, ++ int num_dai) + { + struct snd_soc_card *card, *c; + int ret; +@@ -2778,27 +2777,36 @@ int snd_soc_add_component(struct snd_soc_component *component, + + return ret; + } +-EXPORT_SYMBOL_GPL(snd_soc_add_component); + +-int snd_soc_register_component(struct device *dev, ++int snd_soc_register_component_c(struct snd_soc_component *component, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, + int num_dai) + { +- struct snd_soc_component *component; + int ret; + +- component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); +- if (!component) +- return -ENOMEM; +- +- ret = snd_soc_component_initialize(component, component_driver, dev); ++ ret = soc_component_initialize(component, component_driver); + if (ret < 0) + return ret; + +- return snd_soc_add_component(component, dai_drv, num_dai); ++ return soc_component_add(component, dai_drv, num_dai); + } +-EXPORT_SYMBOL_GPL(snd_soc_register_component); ++EXPORT_SYMBOL_GPL(snd_soc_register_component_c); ++ ++int snd_soc_register_component_d(struct device *dev, ++ const struct snd_soc_component_driver *component_driver, ++ struct snd_soc_dai_driver *dai_drv, ++ int num_dai) ++{ ++ struct snd_soc_component *component; ++ ++ component = snd_soc_component_alloc(dev); ++ if (!component) ++ return -ENOMEM; ++ ++ return snd_soc_register_component_c(component, component_driver, dai_drv, num_dai); ++} ++EXPORT_SYMBOL_GPL(snd_soc_register_component_d); + + /** + * snd_soc_unregister_component_by_driver - Unregister component using a given driver +diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c +--- a/sound/soc/soc-dapm.c ++++ b/sound/soc/soc-dapm.c +@@ -1203,6 +1203,8 @@ static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w, + case snd_soc_dapm_pga: + case snd_soc_dapm_effect: + case snd_soc_dapm_out_drv: ++ case snd_soc_dapm_encoder: ++ case snd_soc_dapm_decoder: + wname_in_long_name = true; + kcname_in_long_name = true; + break; +@@ -2785,6 +2787,8 @@ static ssize_t dapm_widget_show_component(struct snd_soc_component *component, + case snd_soc_dapm_pga: + case snd_soc_dapm_effect: + case snd_soc_dapm_out_drv: ++ case snd_soc_dapm_encoder: ++ case snd_soc_dapm_decoder: + case snd_soc_dapm_mixer: + case snd_soc_dapm_mixer_named_ctl: + case snd_soc_dapm_supply: +@@ -3367,6 +3371,8 @@ int snd_soc_dapm_new_widgets(struct snd_soc_card *card) + case snd_soc_dapm_pga: + case snd_soc_dapm_effect: + case snd_soc_dapm_out_drv: ++ case snd_soc_dapm_encoder: ++ case snd_soc_dapm_decoder: + dapm_new_pga(w); + break; + case snd_soc_dapm_dai_link: +diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c +--- a/sound/soc/soc-generic-dmaengine-pcm.c ++++ b/sound/soc/soc-generic-dmaengine-pcm.c +@@ -3,6 +3,7 @@ + // Copyright (C) 2013, Analog Devices Inc. + // Author: Lars-Peter Clausen + ++#include + #include + #include + #include +@@ -77,7 +78,7 @@ static int dmaengine_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream); + struct dma_slave_config slave_config; + int ret; +@@ -99,7 +100,7 @@ dmaengine_pcm_set_runtime_hwparams(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct device *dma_dev = dmaengine_dma_dev(pcm, substream); + struct dma_chan *chan = pcm->chan[substream->stream]; + struct snd_dmaengine_dai_dma_data *dma_data; +@@ -148,7 +149,7 @@ dmaengine_pcm_set_runtime_hwparams(struct snd_soc_component *component, + static int dmaengine_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct dma_chan *chan = pcm->chan[substream->stream]; + int ret; + +@@ -176,7 +177,7 @@ static struct dma_chan *dmaengine_pcm_compat_request_channel( + struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct snd_dmaengine_dai_dma_data *dma_data; + + if (rtd->dai_link->num_cpus > 1) { +@@ -219,7 +220,7 @@ static bool dmaengine_pcm_can_report_residue(struct device *dev, + static int dmaengine_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + const struct snd_dmaengine_pcm_config *config = pcm->config; + struct device *dev = component->dev; + size_t prealloc_buffer_size; +@@ -279,7 +280,7 @@ static snd_pcm_uframes_t dmaengine_pcm_pointer( + struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE) + return snd_dmaengine_pcm_pointer_no_residue(substream); +@@ -293,7 +294,7 @@ static int dmaengine_copy(struct snd_soc_component *component, + struct iov_iter *iter, unsigned long bytes) + { + struct snd_pcm_runtime *runtime = substream->runtime; +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + int (*process)(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + unsigned long bytes) = pcm->config->process; +@@ -395,6 +396,27 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + */ + if (PTR_ERR(chan) == -EPROBE_DEFER) + return -EPROBE_DEFER; ++ ++ bool has_fw_node = dev->of_node || is_acpi_device_node(dev->fwnode); ++ bool name_exists_in_fw = false; ++ ++ if (has_fw_node) ++ name_exists_in_fw = device_property_match_string(dev, ++ "dma-names", ++ name) >= 0; ++ ++ if (has_fw_node && name_exists_in_fw) ++ dev_warn(dev, "DTS/ACPI DMA channel '%s' request failed (%ld)\n", ++ name, PTR_ERR(chan)); ++ ++ if (has_fw_node && !name_exists_in_fw) ++ dev_warn(dev, "DTS/ACPI name '%s' not found, legacy failed (%ld)\n", ++ name, PTR_ERR(chan)); ++ ++ if (!has_fw_node) ++ dev_warn(dev, "Legacy DMA channel '%s' request failed (%ld)\n", ++ name, PTR_ERR(chan)); ++ + pcm->chan[i] = NULL; + } else { + pcm->chan[i] = chan; +@@ -406,6 +428,12 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) + pcm->chan[1] = pcm->chan[0]; + ++ if (!pcm->chan[0] && ++ !pcm->chan[1]) { ++ dev_err(dev, "no DMA channel found for either playback or capture\n"); ++ return -ENODEV; ++ } ++ + return 0; + } + +@@ -435,10 +463,15 @@ static const struct snd_dmaengine_pcm_config snd_dmaengine_pcm_default_config = + int snd_dmaengine_pcm_register(struct device *dev, + const struct snd_dmaengine_pcm_config *config, unsigned int flags) + { ++ struct snd_soc_component *component; + const struct snd_soc_component_driver *driver; + struct dmaengine_pcm *pcm; + int ret; + ++ component = snd_soc_component_alloc(dev); ++ if (!component) ++ return -ENOMEM; ++ + pcm = kzalloc_obj(*pcm); + if (!pcm) + return -ENOMEM; +@@ -449,7 +482,8 @@ int snd_dmaengine_pcm_register(struct device *dev, + pcm->flags = flags; + + if (config->name) +- pcm->component.name = config->name; ++ snd_soc_component_set_name(component, config->name); ++ snd_soc_component_set_priv(component, pcm); + + ret = dmaengine_pcm_request_chan_of(pcm, dev, config); + if (ret) +@@ -460,11 +494,7 @@ int snd_dmaengine_pcm_register(struct device *dev, + else + driver = &dmaengine_pcm_component; + +- ret = snd_soc_component_initialize(&pcm->component, driver, dev); +- if (ret) +- goto err_free_dma; +- +- ret = snd_soc_add_component(&pcm->component, NULL, 0); ++ ret = snd_soc_register_component(component, driver, NULL, 0); + if (ret) + goto err_free_dma; + +@@ -493,7 +523,7 @@ void snd_dmaengine_pcm_unregister(struct device *dev) + if (!component) + return; + +- pcm = soc_component_to_pcm(component); ++ pcm = snd_soc_component_to_priv(component); + + snd_soc_unregister_component_by_driver(dev, component->driver); + dmaengine_pcm_release_chan(pcm); +diff --git a/sound/soc/soc-topology-test.c b/sound/soc/soc-topology-test.c +--- a/sound/soc/soc-topology-test.c ++++ b/sound/soc/soc-topology-test.c +@@ -45,15 +45,13 @@ static void snd_soc_tplg_test_exit(struct kunit *test) + struct kunit_soc_component { + struct kunit *kunit; + int expect; /* what result we expect when loading topology */ +- struct snd_soc_component comp; + struct snd_soc_card card; + struct firmware fw; + }; + + static int d_probe(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + ret = snd_soc_tplg_component_load(component, NULL, &kunit_comp->fw); +@@ -65,8 +63,7 @@ static int d_probe(struct snd_soc_component *component) + + static void d_remove(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + ret = snd_soc_tplg_component_remove(component); +@@ -214,8 +211,7 @@ static struct tplg_tmpl_002 tplg_tmpl_with_pcm = { + */ + static int d_probe_null_comp(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + /* instead of passing component pointer as first argument, pass NULL here */ +@@ -234,6 +230,7 @@ static const struct snd_soc_component_driver test_component_null_comp = { + static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + int ret; + + /* prepare */ +@@ -249,15 +246,17 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component_null_comp, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component_null_comp, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -276,6 +275,7 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) + static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + int ret; + + /* prepare */ +@@ -291,15 +291,17 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -318,8 +320,7 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) + */ + static int d_probe_null_fw(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + /* instead of passing fw pointer as third argument, pass NULL here */ +@@ -338,6 +339,7 @@ static const struct snd_soc_component_driver test_component_null_fw = { + static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + int ret; + + /* prepare */ +@@ -353,15 +355,17 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component_null_fw, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component_null_fw, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -375,6 +379,7 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -401,15 +406,17 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -425,6 +432,7 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -456,15 +464,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -480,6 +490,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -511,15 +522,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -535,6 +548,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -566,15 +580,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -590,6 +606,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -622,15 +639,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -644,6 +663,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes + static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + u8 *data; + int size; + int ret; +@@ -670,15 +690,17 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + snd_soc_unregister_component(test_dev); +@@ -693,6 +715,7 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) + static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + u8 *data; + int size; + int ret; +@@ -720,16 +743,19 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + + for (i = 0; i < 100; i++) { +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); + +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + snd_soc_unregister_component(test_dev); +@@ -745,6 +771,7 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) + static void snd_soc_tplg_test_load_pcm_tplg_reload_card(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + u8 *data; + int size; + int ret; +@@ -772,11 +799,13 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_card(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + +- /* run test */ +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); + +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ snd_soc_component_set_priv(component, kunit_comp); ++ ++ /* run test */ ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + for (i = 0; i < 100; i++) { +diff --git a/sound/soc/sof/amd/Kconfig b/sound/soc/sof/amd/Kconfig +--- a/sound/soc/sof/amd/Kconfig ++++ b/sound/soc/sof/amd/Kconfig +@@ -104,4 +104,15 @@ config SND_SOC_SOF_AMD_ACP70 + AMD ACP7.0/ACP7.1 version based platforms. + Say Y if you want to enable SOF on ACP7.0/ACP7.1 based platforms. + ++config SND_SOC_SOF_AMD_ACP7X ++ tristate "SOF support for ACP7.B/7.F platforms" ++ depends on SND_SOC_SOF_PCI ++ depends on AMD_NODE ++ select SND_SOC_SOF_AMD_COMMON ++ help ++ Select this option for SOF support on AMD ACP7.B and ACP7.F PCI ++ revision based platforms. ++ Say Y if you want to enable SOF on ACP7.B/7.F based platforms. ++ If unsure select "N". ++ + endif +diff --git a/sound/soc/sof/amd/Makefile b/sound/soc/sof/amd/Makefile +--- a/sound/soc/sof/amd/Makefile ++++ b/sound/soc/sof/amd/Makefile +@@ -11,6 +11,7 @@ snd-sof-amd-rembrandt-y := pci-rmb.o rembrandt.o + snd-sof-amd-vangogh-y := pci-vangogh.o vangogh.o + snd-sof-amd-acp63-y := pci-acp63.o acp63.o + snd-sof-amd-acp70-y := pci-acp70.o acp70.o ++snd-sof-amd-acp7x-y := pci-acp7x.o acp7x.o + + obj-$(CONFIG_SND_SOC_SOF_AMD_COMMON) += snd-sof-amd-acp.o + obj-$(CONFIG_SND_SOC_SOF_AMD_RENOIR) += snd-sof-amd-renoir.o +@@ -18,3 +19,4 @@ obj-$(CONFIG_SND_SOC_SOF_AMD_REMBRANDT) += snd-sof-amd-rembrandt.o + obj-$(CONFIG_SND_SOC_SOF_AMD_VANGOGH) += snd-sof-amd-vangogh.o + obj-$(CONFIG_SND_SOC_SOF_AMD_ACP63) += snd-sof-amd-acp63.o + obj-$(CONFIG_SND_SOC_SOF_AMD_ACP70) += snd-sof-amd-acp70.o ++obj-$(CONFIG_SND_SOC_SOF_AMD_ACP7X) += snd-sof-amd-acp7x.o +diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h +--- a/sound/soc/sof/amd/acp-dsp-offset.h ++++ b/sound/soc/sof/amd/acp-dsp-offset.h +@@ -68,12 +68,14 @@ + #define ACP5X_PGFSM_BASE 0x1424 + #define ACP6X_PGFSM_BASE 0x1024 + #define ACP70_PGFSM_BASE ACP6X_PGFSM_BASE ++#define ACP7X_PGFSM_BASE ACP6X_PGFSM_BASE + #define PGFSM_CONTROL_OFFSET 0x0 + #define PGFSM_STATUS_OFFSET 0x4 + #define ACP3X_CLKMUX_SEL 0x1424 + #define ACP5X_CLKMUX_SEL 0x142C + #define ACP6X_CLKMUX_SEL 0x102C + #define ACP70_CLKMUX_SEL ACP6X_CLKMUX_SEL ++#define ACP7X_CLKMUX_SEL ACP6X_CLKMUX_SEL + + /* Registers from ACP_INTR block */ + #define ACP3X_EXT_INTR_STAT 0x1808 +@@ -86,21 +88,31 @@ + #define ACP70_EXTERNAL_INTR_CNTL ACP6X_EXTERNAL_INTR_CNTL + #define ACP70_EXT_INTR_STAT ACP6X_EXT_INTR_STAT + #define ACP70_EXT_INTR_STAT1 ACP6X_EXT_INTR_STAT1 ++#define ACP7X_EXTERNAL_INTR_ENB ACP6X_EXTERNAL_INTR_ENB ++#define ACP7X_EXTERNAL_INTR_CNTL 0x1A04 ++#define ACP7X_EXT_INTR_STAT 0x1A1C ++#define ACP7X_EXTERNAL_INTR_CNTL1 0x1A08 ++#define ACP7X_EXT_INTR_STAT1 0x1A20 + + #define ACP3X_DSP_SW_INTR_BASE 0x1814 + #define ACP5X_DSP_SW_INTR_BASE 0x1814 + #define ACP6X_DSP_SW_INTR_BASE 0x1808 + #define ACP70_DSP_SW_INTR_BASE ACP6X_DSP_SW_INTR_BASE ++#define ACP7X_DSP_SW_INTR_BASE 0x1860 + #define DSP_SW_INTR_CNTL_OFFSET 0x0 + #define DSP_SW_INTR_STAT_OFFSET 0x4 ++#define ACP7X_DSP_SW_INTR_STAT (ACP7X_DSP_SW_INTR_BASE + DSP_SW_INTR_STAT_OFFSET) + #define DSP_SW_INTR_TRIG_OFFSET 0x8 ++#define ACP7X_DSP_SW_INTR_TRIG_OFFSET 0x30 + #define ACP3X_ERROR_STATUS 0x18C4 + #define ACP6X_ERROR_STATUS 0x1A4C + #define ACP70_ERROR_STATUS ACP6X_ERROR_STATUS ++#define ACP7X_ERROR_STATUS 0x1A88 + #define ACP3X_AXI2DAGB_SEM_0 0x1880 + #define ACP5X_AXI2DAGB_SEM_0 0x1884 + #define ACP6X_AXI2DAGB_SEM_0 0x1874 + #define ACP70_AXI2DAGB_SEM_0 ACP6X_AXI2DAGB_SEM_0 ++#define ACP7X_AXI2DAGB_SEM_0 0x18F4 + + /* ACP common registers to report errors related to I2S & SoundWire interfaces */ + #define ACP3X_SW_I2S_ERROR_REASON 0x18C8 +@@ -123,6 +135,7 @@ + #define ACP_SCRATCH_REG_0 0x10000 + #define ACP6X_DSP_FUSION_RUNSTALL 0x0644 + #define ACP70_DSP_FUSION_RUNSTALL ACP6X_DSP_FUSION_RUNSTALL ++#define ACP7X_DSP_FUSION_RUNSTALL ACP6X_DSP_FUSION_RUNSTALL + + /* Cache window registers */ + #define ACP_DSP0_CACHE_OFFSET0 0x0420 +@@ -139,5 +152,9 @@ + #define ACP70_SDW1_HOST_WAKE_STAT BIT(25) + #define ACP70_SDW0_PME_STAT BIT(26) + #define ACP70_SDW1_PME_STAT BIT(27) ++#define ACP7X_DSP0_IDMA_ERROR_MASK 0x4B0 ++#define ACP7X_IDMA_ERROR_MASK 0x1FF9FF ++#define ACP7X_ZSC_DSP_CTRL 0x001014 ++#define ACP7X_PME_EN ACP70_PME_EN + + #endif +diff --git a/sound/soc/sof/amd/acp-ipc.c b/sound/soc/sof/amd/acp-ipc.c +--- a/sound/soc/sof/amd/acp-ipc.c ++++ b/sound/soc/sof/amd/acp-ipc.c +@@ -32,11 +32,20 @@ static void acpbus_trigger_host_to_dsp_swintr(struct acp_dev_data *adata) + struct snd_sof_dev *sdev = adata->dev; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + u32 swintr_trigger; ++ unsigned int swintr_trigger_reg_offset; + ++ switch (adata->pci_rev) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ swintr_trigger_reg_offset = ACP7X_DSP_SW_INTR_TRIG_OFFSET; ++ break; ++ default: ++ swintr_trigger_reg_offset = DSP_SW_INTR_TRIG_OFFSET; ++ } + swintr_trigger = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->dsp_intr_base + +- DSP_SW_INTR_TRIG_OFFSET); ++ swintr_trigger_reg_offset); + swintr_trigger |= 0x01; +- snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->dsp_intr_base + DSP_SW_INTR_TRIG_OFFSET, ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->dsp_intr_base + swintr_trigger_reg_offset, + swintr_trigger); + } + +diff --git a/sound/soc/sof/amd/acp-loader.c b/sound/soc/sof/amd/acp-loader.c +--- a/sound/soc/sof/amd/acp-loader.c ++++ b/sound/soc/sof/amd/acp-loader.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + #include "../ops.h" + #include "acp-dsp-offset.h" +@@ -173,10 +174,31 @@ int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev) + + adata = sdev->pdata->hw_pdata; + +- if (adata->quirks && adata->quirks->signed_fw_image) ++ if (adata->pci_rev >= ACP7B_PCI_ID) { ++ if (adata->acp_sof_signed_firmware_image) { ++ if (adata->fw_bin_size <= ACP_IMAGE_HEADER_SIZE) { ++ dev_err(sdev->dev, "Invalid signed firmware size %u\n", ++ adata->fw_bin_size); ++ return -EINVAL; ++ } ++ size_fw = get_unaligned_le32(adata->bin_buf + ++ ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF); ++ if (!size_fw || ++ size_fw > adata->fw_bin_size - ACP_IMAGE_HEADER_SIZE) { ++ dev_err(sdev->dev, ++ "Invalid signed firmware payload size %u (max %u)\n", ++ size_fw, adata->fw_bin_size - ACP_IMAGE_HEADER_SIZE); ++ return -EINVAL; ++ } ++ size_fw += ACP_IMAGE_HEADER_SIZE; ++ } else { ++ size_fw = adata->fw_bin_size; ++ } ++ } else if (adata->quirks && adata->quirks->signed_fw_image) { + size_fw = adata->fw_bin_size - ACP_FIRMWARE_SIGNATURE; +- else ++ } else { + size_fw = adata->fw_bin_size; ++ } + + page_count = PAGE_ALIGN(size_fw) >> PAGE_SHIFT; + adata->fw_bin_page_count = page_count; +@@ -312,9 +334,14 @@ int acp_sof_load_signed_firmware(struct snd_sof_dev *sdev) + } + kfree(fw_filename); + +- ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_DRAM, 0, +- (void *)adata->fw_dbin->data, +- adata->fw_dbin->size); ++ if (adata->pci_rev >= ACP7B_PCI_ID) ++ ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_SRAM, 0, ++ (void *)adata->fw_dbin->data, ++ adata->fw_dbin->size); ++ else ++ ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_DRAM, 0, ++ (void *)adata->fw_dbin->data, ++ adata->fw_dbin->size); + return ret; + } + EXPORT_SYMBOL_NS(acp_sof_load_signed_firmware, "SND_SOC_SOF_AMD_COMMON"); +diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c +--- a/sound/soc/sof/amd/acp.c ++++ b/sound/soc/sof/amd/acp.c +@@ -12,6 +12,7 @@ + * Hardware interface for generic AMD ACP processor + */ + ++#include + #include + #include + #include +@@ -60,6 +61,8 @@ static void init_dma_descriptor(struct acp_dev_data *adata) + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: + acp_dma_desc_base_addr = ACP70_DMA_DESC_BASE_ADDR; + acp_dma_desc_max_num_dscr = ACP70_DMA_DESC_MAX_NUM_DSCR; + break; +@@ -101,6 +104,8 @@ static int config_dma_channel(struct acp_dev_data *adata, unsigned int ch, + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: + acp_dma_cntl_0 = ACP70_DMA_CNTL_0; + acp_dma_ch_rst_sts = ACP70_DMA_CH_RST_STS; + acp_dma_dscr_err_sts_0 = ACP70_DMA_ERR_STS_0; +@@ -283,7 +288,8 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, + } + } + +- if (adata->quirks && adata->quirks->signed_fw_image) ++ if ((adata->quirks && adata->quirks->signed_fw_image) || ++ adata->acp_sof_signed_firmware_image) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_INCLUDE_HDR, ACP_SHA_HEADER); + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_STRT_ADDR, start_addr); +@@ -342,6 +348,8 @@ int acp_dma_status(struct acp_dev_data *adata, unsigned char ch) + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: + acp_dma_ch_sts = ACP70_DMA_CH_STS; + break; + default: +@@ -534,8 +542,17 @@ static irqreturn_t acp_irq_handler(int irq, void *dev_id) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_ERROR_IRQ_MASK); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_sw0_i2s_err_reason, 0); + /* ACP_SW1_I2S_ERROR_REASON is newly added register from rmb platform onwards */ +- if (adata->pci_rev >= ACP_RMB_PCI_ID) ++ switch (adata->pci_rev) { ++ case ACP_RMB_PCI_ID: ++ case ACP63_PCI_ID: ++ case ACP70_PCI_ID: ++ case ACP71_PCI_ID: ++ case ACP72_PCI_ID: + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SW1_I2S_ERROR_REASON, 0); ++ break; ++ default: ++ break; ++ } + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_error_stat, 0); + irq_flag = 1; + } +@@ -564,6 +581,35 @@ static irqreturn_t acp_irq_handler(int irq, void *dev_id) + return IRQ_NONE; + } + ++static irqreturn_t acp7x_irq_handler(int irq, void *dev_id) ++{ ++ struct snd_sof_dev *sdev = dev_id; ++ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); ++ unsigned int base = desc->dsp_intr_base; ++ unsigned int val; ++ unsigned int ext_intr_stat; ++ int irq_flag = 0; ++ ++ val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET); ++ if (val & ACP_DSP_TO_HOST_IRQ) { ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET, ++ ACP_DSP_TO_HOST_IRQ); ++ return IRQ_WAKE_THREAD; ++ } ++ ++ ext_intr_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat); ++ if (ext_intr_stat & ACP_ERROR_IRQ_MASK) { ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_ERROR_IRQ_MASK); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_error_stat, 0); ++ irq_flag = 1; ++ } ++ ++ if (irq_flag) ++ return IRQ_HANDLED; ++ ++ return IRQ_NONE; ++} ++ + static int acp_power_on(struct snd_sof_dev *sdev) + { + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); +@@ -571,6 +617,7 @@ static int acp_power_on(struct snd_sof_dev *sdev) + unsigned int base = desc->pgfsm_base; + unsigned int val; + unsigned int acp_pgfsm_status_mask, acp_pgfsm_cntl_mask; ++ bool use_masked_status = false; + int ret; + + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + PGFSM_STATUS_OFFSET); +@@ -595,6 +642,12 @@ static int acp_power_on(struct snd_sof_dev *sdev) + acp_pgfsm_status_mask = ACP70_PGFSM_STATUS_MASK; + acp_pgfsm_cntl_mask = ACP70_PGFSM_CNTL_POWER_ON_MASK; + break; ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ acp_pgfsm_status_mask = ACP7X_PGFSM_STATUS_MASK; ++ acp_pgfsm_cntl_mask = ACP7X_PGFSM_CNTL_POWER_ON_MASK; ++ use_masked_status = true; ++ break; + default: + return -EINVAL; + } +@@ -603,8 +656,17 @@ static int acp_power_on(struct snd_sof_dev *sdev) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + PGFSM_CONTROL_OFFSET, + acp_pgfsm_cntl_mask); + +- ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, base + PGFSM_STATUS_OFFSET, val, +- !val, ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US); ++ if (use_masked_status) ++ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ++ base + PGFSM_STATUS_OFFSET, val, ++ !(val & acp_pgfsm_status_mask), ++ ACP_REG_POLL_INTERVAL, ++ ACP_REG_POLL_TIMEOUT_US); ++ else ++ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ++ base + PGFSM_STATUS_OFFSET, val, ++ !val, ACP_REG_POLL_INTERVAL, ++ ACP_REG_POLL_TIMEOUT_US); + if (ret < 0) + dev_err(sdev->dev, "timeout in ACP_PGFSM_STATUS read\n"); + +@@ -703,6 +765,13 @@ static int acp_init(struct snd_sof_dev *sdev) + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP70_PME_EN, 1); + break; ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 0); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_DSP0_IDMA_ERROR_MASK, ++ ACP7X_IDMA_ERROR_MASK); ++ break; + } + return 0; + } +@@ -749,6 +818,8 @@ int amd_sof_acp_suspend(struct snd_sof_dev *sdev, u32 target_state) + case ACP72_PCI_ID: + enable = true; + break; ++ default: ++ break; + } + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); + +@@ -990,6 +1061,194 @@ void amd_sof_acp_remove(struct snd_sof_dev *sdev) + } + EXPORT_SYMBOL_NS(amd_sof_acp_remove, "SND_SOC_SOF_AMD_COMMON"); + ++int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) ++{ ++ struct pci_dev *pci = to_pci_dev(sdev->dev); ++ struct acp_dev_data *adata; ++ const struct sof_amd_acp_desc *chip; ++ const union acpi_object *obj; ++ struct acpi_device *adev; ++ unsigned int addr; ++ unsigned int irqflags; ++ int ret; ++ ++ chip = get_chip_info(sdev->pdata); ++ if (!chip) { ++ dev_err(sdev->dev, "no such device supported, chip id:%x\n", pci->device); ++ return -EIO; ++ } ++ adata = devm_kzalloc(sdev->dev, sizeof(struct acp_dev_data), GFP_KERNEL); ++ if (!adata) ++ return -ENOMEM; ++ ++ adata->dev = sdev; ++ adata->dmic_dev = platform_device_register_data(sdev->dev, "dmic-codec", ++ PLATFORM_DEVID_NONE, NULL, 0); ++ if (IS_ERR(adata->dmic_dev)) { ++ dev_err(sdev->dev, "failed to register platform for dmic codec\n"); ++ return PTR_ERR(adata->dmic_dev); ++ } ++ ++ addr = pci_resource_start(pci, ACP_DSP_BAR); ++ sdev->bar[ACP_DSP_BAR] = devm_ioremap(sdev->dev, addr, pci_resource_len(pci, ACP_DSP_BAR)); ++ if (!sdev->bar[ACP_DSP_BAR]) { ++ dev_err(sdev->dev, "ioremap error\n"); ++ ret = -ENXIO; ++ goto unregister_dev; ++ } ++ ++ pci_set_master(pci); ++ adata->addr = addr; ++ adata->reg_range = chip->reg_end_addr - chip->reg_start_addr; ++ adata->pci_rev = pci->revision; ++ mutex_init(&adata->acp_lock); ++ sdev->pdata->hw_pdata = adata; ++ ++ ret = acp_init(sdev); ++ if (ret < 0) ++ goto unregister_dev; ++ ++ adev = ACPI_COMPANION(&pci->dev); ++ ++ sdev->ipc_irq = pci->irq; ++ irqflags = IRQF_SHARED; ++ ++ ret = request_threaded_irq(pci->irq, acp7x_irq_handler, acp_irq_thread, ++ irqflags, "AudioDSP", sdev); ++ if (ret < 0) { ++ dev_err(sdev->dev, "failed to register IRQ %d\n", sdev->ipc_irq); ++ goto unregister_dev; ++ } ++ ++ if (adev) { ++ if (!acpi_dev_get_property(adev, "acp-sof-signed-firmware-image", ++ ACPI_TYPE_INTEGER, &obj)) ++ adata->acp_sof_signed_firmware_image = obj->integer.value; ++ } ++ ++ sdev->dsp_box.offset = 0; ++ sdev->dsp_box.size = BOX_SIZE_512; ++ ++ sdev->host_box.offset = sdev->dsp_box.offset + sdev->dsp_box.size; ++ sdev->host_box.size = BOX_SIZE_512; ++ ++ sdev->debug_box.offset = sdev->host_box.offset + sdev->host_box.size; ++ sdev->debug_box.size = BOX_SIZE_1024; ++ ++ if (adata->acp_sof_signed_firmware_image) { ++ adata->fw_code_bin = devm_kasprintf(sdev->dev, GFP_KERNEL, ++ "sof-%s-code.bin", chip->name); ++ if (!adata->fw_code_bin) { ++ ret = -ENOMEM; ++ goto free_ipc_irq; ++ } ++ adata->fw_data_bin = devm_kasprintf(sdev->dev, GFP_KERNEL, ++ "sof-%s-data.bin", chip->name); ++ if (!adata->fw_data_bin) { ++ ret = -ENOMEM; ++ goto free_ipc_irq; ++ } ++ } ++ ++ adata->enable_fw_debug = enable_fw_debug; ++ acp_memory_init(sdev); ++ acp_dsp_stream_init(sdev); ++ ++ return 0; ++ ++free_ipc_irq: ++ free_irq(sdev->ipc_irq, sdev); ++unregister_dev: ++ platform_device_unregister(adata->dmic_dev); ++ return ret; ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_probe, "SND_SOC_SOF_AMD_COMMON"); ++ ++void amd_sof_acp7x_remove(struct snd_sof_dev *sdev) ++{ ++ struct acp_dev_data *adata = sdev->pdata->hw_pdata; ++ ++ if (sdev->ipc_irq) ++ free_irq(sdev->ipc_irq, sdev); ++ ++ if (adata->dmic_dev) ++ platform_device_unregister(adata->dmic_dev); ++ ++ acp_reset(sdev); ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_remove, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) ++{ ++ struct acp_dev_data *acp_data; ++ int ret; ++ bool enable = false; ++ ++ acp_data = sdev->pdata->hw_pdata; ++ ++ ret = acp_reset(sdev); ++ if (ret) { ++ dev_err(sdev->dev, "ACP Reset failed\n"); ++ return ret; ++ } ++ switch (acp_data->pci_rev) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ enable = true; ++ break; ++ default: ++ break; ++ } ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) ++{ ++ struct acp_dev_data *acp_data; ++ int ret; ++ ++ acp_data = sdev->pdata->hw_pdata; ++ ++ ret = acp_init(sdev); ++ if (ret) { ++ dev_err(sdev->dev, "ACP Init failed\n"); ++ return ret; ++ } ++ ret = acp_memory_init(sdev); ++ if (ret) { ++ dev_err(sdev->dev, "ACP Memory init failed\n"); ++ return ret; ++ } ++ ++ switch (acp_data->pci_rev) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); ++ break; ++ default: ++ break; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_resume, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev) ++{ ++ return amd_sof_acp7x_suspend(sdev, 0); ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend_runtime, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev) ++{ ++ return amd_sof_acp7x_resume(sdev); ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_resume_runtime, "SND_SOC_SOF_AMD_COMMON"); ++ + MODULE_LICENSE("Dual BSD/GPL"); + MODULE_DESCRIPTION("AMD ACP sof driver"); + MODULE_IMPORT_NS("SOUNDWIRE_AMD_INIT"); +diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h +--- a/sound/soc/sof/amd/acp.h ++++ b/sound/soc/sof/amd/acp.h +@@ -76,6 +76,12 @@ + #define ACP70_PCI_ID 0x70 + #define ACP71_PCI_ID 0x71 + #define ACP72_PCI_ID 0x72 ++#define ACP7B_PCI_ID 0x7B ++#define ACP7F_PCI_ID 0x7F ++ ++#define ACP7X_PGFSM_CNTL_POWER_ON_MASK 0x7F ++#define ACP7X_PGFSM_STATUS_MASK 0xFFF ++#define ACP7X_SRAM_PTE_OFFSET ACP6X_SRAM_PTE_OFFSET + + #define HOST_BRIDGE_CZN 0x1630 + #define HOST_BRIDGE_VGH 0x1645 +@@ -107,6 +113,9 @@ + #define PROBE_STATUS_BIT BIT(31) + + #define ACP_FIRMWARE_SIGNATURE 0x100 ++#define ACP_IMAGE_HEADER_SIZE ACP_FIRMWARE_SIGNATURE ++#define ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF 0x14 ++ + #define ACP_ERROR_IRQ_MASK BIT(29) + #define ACP_SDW0_IRQ_MASK BIT(21) + #define ACP_SDW1_IRQ_MASK BIT(2) +@@ -271,6 +280,7 @@ struct acp_dev_data { + /* acp70_sdw1_wake_event flag set to true when wake irq asserted for SW1 instance */ + bool acp70_sdw1_wake_event; + unsigned int pci_rev; ++ int acp_sof_signed_firmware_image; + }; + + void memcpy_to_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *src, size_t bytes); +@@ -344,6 +354,16 @@ int sof_acp63_ops_init(struct snd_sof_dev *sdev); + extern struct snd_sof_dsp_ops sof_acp70_ops; + int sof_acp70_ops_init(struct snd_sof_dev *sdev); + ++extern struct snd_sof_dsp_ops sof_acp7x_ops; ++int sof_acp7x_ops_init(struct snd_sof_dev *sdev); ++ ++int amd_sof_acp7x_probe(struct snd_sof_dev *sdev); ++void amd_sof_acp7x_remove(struct snd_sof_dev *sdev); ++int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state); ++int amd_sof_acp7x_resume(struct snd_sof_dev *sdev); ++int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev); ++int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev); ++ + struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev); + /* Machine configuration */ + int snd_amd_acp_find_config(struct pci_dev *pci); +diff --git a/sound/soc/sof/amd/acp7x.c b/sound/soc/sof/amd/acp7x.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/sof/amd/acp7x.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) ++// ++// This file is provided under a dual BSD/GPLv2 license. When using or ++// redistributing this file, you may do so under either license. ++// ++// Copyright(c) 2025 Advanced Micro Devices, Inc. ++// ++// Authors: Vijendar Mukunda ++ ++/* ++ * Hardware interface for Audio DSP on ACP7.B/7.F platforms ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "../ops.h" ++#include "../sof-audio.h" ++#include "acp.h" ++#include "acp-dsp-offset.h" ++ ++#define I2S_TDM0_INSTANCE 0 ++#define I2S_TDM1_INSTANCE 1 ++#define I2S_TDM2_INSTANCE 2 ++#define PDM0_DMIC_INSTANCE 3 ++#define PDM1_DMIC_INSTANCE 4 ++ ++static struct snd_soc_dai_driver acp7x_sof_dai[] = { ++ [I2S_TDM0_INSTANCE] = { ++ .id = I2S_TDM0_INSTANCE, ++ .name = "acp-sof-i2s0", ++ .playback = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 8, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ /* Supporting only stereo for I2S HS controller capture */ ++ .channels_min = 2, ++ .channels_max = 2, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [I2S_TDM1_INSTANCE] = { ++ .id = I2S_TDM1_INSTANCE, ++ .name = "acp-sof-i2s1", ++ .playback = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 8, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ /* Supporting only stereo for I2S BT controller capture */ ++ .channels_min = 2, ++ .channels_max = 2, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [I2S_TDM2_INSTANCE] = { ++ .id = I2S_TDM2_INSTANCE, ++ .name = "acp-sof-i2s2", ++ .playback = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 8, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ /* Supporting only stereo for I2S SP controller capture */ ++ .channels_min = 2, ++ .channels_max = 2, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [PDM0_DMIC_INSTANCE] = { ++ .id = PDM0_DMIC_INSTANCE, ++ .name = "acp-sof-dmic0", ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 4, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [PDM1_DMIC_INSTANCE] = { ++ .id = PDM1_DMIC_INSTANCE, ++ .name = "acp-sof-dmic1", ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 4, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ }, ++}; ++ ++static int sof_acp7x_post_fw_run_delay(struct snd_sof_dev *sdev) ++{ ++ /* ++ * Resuming from suspend in some cases may cause the DSP firmware ++ * to enter an unrecoverable faulty state. Delaying a bit any host ++ * to DSP transmission right after firmware boot completion seems ++ * to resolve the issue. ++ */ ++ if (!sdev->first_boot) ++ usleep_range(100, 150); ++ ++ return 0; ++} ++ ++struct snd_sof_dsp_ops sof_acp7x_ops; ++EXPORT_SYMBOL_NS(sof_acp7x_ops, "SND_SOC_SOF_AMD_COMMON"); ++ ++int sof_acp7x_ops_init(struct snd_sof_dev *sdev) ++{ ++ struct acpi_device *adev = ACPI_COMPANION(&to_pci_dev(sdev->dev)->dev); ++ const union acpi_object *obj; ++ int acp_sof_signed_firmware_image = 0; ++ int acp_sof_post_fw_run_delay = 0; ++ ++ /* common defaults */ ++ memcpy(&sof_acp7x_ops, &sof_acp_common_ops, sizeof(struct snd_sof_dsp_ops)); ++ ++ sof_acp7x_ops.drv = acp7x_sof_dai; ++ sof_acp7x_ops.num_drv = ARRAY_SIZE(acp7x_sof_dai); ++ sof_acp7x_ops.probe = amd_sof_acp7x_probe; ++ sof_acp7x_ops.remove = amd_sof_acp7x_remove; ++ ++ if (adev) { ++ if (!acpi_dev_get_property(adev, "acp-sof-signed-firmware-image", ++ ACPI_TYPE_INTEGER, &obj)) ++ acp_sof_signed_firmware_image = obj->integer.value; ++ ++ if (!acpi_dev_get_property(adev, "acp-sof-post_fw_run_delay", ++ ACPI_TYPE_INTEGER, &obj)) ++ acp_sof_post_fw_run_delay = obj->integer.value; ++ } ++ ++ if (acp_sof_signed_firmware_image) ++ sof_acp7x_ops.load_firmware = acp_sof_load_signed_firmware; ++ ++ if (acp_sof_post_fw_run_delay) ++ sof_acp7x_ops.post_fw_run = sof_acp7x_post_fw_run_delay; ++ ++ sof_acp7x_ops.suspend = amd_sof_acp7x_suspend; ++ sof_acp7x_ops.resume = amd_sof_acp7x_resume; ++ sof_acp7x_ops.runtime_suspend = amd_sof_acp7x_suspend_runtime; ++ sof_acp7x_ops.runtime_resume = amd_sof_acp7x_resume_runtime; ++ ++ return 0; ++} +diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/sof/amd/pci-acp7x.c +@@ -0,0 +1,116 @@ ++// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) ++// ++// This file is provided under a dual BSD/GPLv2 license. When using or ++// redistributing this file, you may do so under either license. ++// ++// Copyright(c) 2025 Advanced Micro Devices, Inc. All rights reserved. ++// ++// Authors: Vijendar Mukunda ++ ++/* ++ * PCI interface for ACP7.B/7.F devices ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "../ops.h" ++#include "../sof-pci-dev.h" ++#include "../../amd/mach-config.h" ++#include "acp.h" ++#include "acp-dsp-offset.h" ++ ++#define ACP7X_FUTURE_REG_ACLK_0 0x18e0 ++#define ACP7X_REG_START 0x1240000 ++#define ACP7X_REG_END 0x125C000 ++ ++static const struct sof_amd_acp_desc acp7x_chip_info = { ++ .name = "acp7x", ++ .pgfsm_base = ACP7X_PGFSM_BASE, ++ .ext_intr_enb = ACP6X_EXTERNAL_INTR_ENB, ++ .ext_intr_cntl = ACP7X_EXTERNAL_INTR_CNTL, ++ .ext_intr_stat = ACP7X_EXT_INTR_STAT, ++ .ext_intr_stat1 = ACP7X_EXT_INTR_STAT1, ++ .dsp_intr_base = ACP7X_DSP_SW_INTR_BASE, ++ .acp_error_stat = ACP7X_ERROR_STATUS, ++ .sram_pte_offset = ACP7X_SRAM_PTE_OFFSET, ++ .hw_semaphore_offset = ACP7X_AXI2DAGB_SEM_0, ++ .fusion_dsp_offset = ACP7X_DSP_FUSION_RUNSTALL, ++ .probe_reg_offset = ACP7X_FUTURE_REG_ACLK_0, ++ .reg_start_addr = ACP7X_REG_START, ++ .reg_end_addr = ACP7X_REG_END, ++}; ++ ++static const struct sof_dev_desc acp7x_desc = { ++ .machines = snd_soc_acpi_amd_acp7x_sof_machines, ++ .resindex_lpe_base = 0, ++ .resindex_pcicfg_base = -1, ++ .resindex_imr_base = -1, ++ .irqindex_host_ipc = -1, ++ .chip_info = &acp7x_chip_info, ++ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3), ++ .ipc_default = SOF_IPC_TYPE_3, ++ .default_fw_path = { ++ [SOF_IPC_TYPE_3] = "amd/sof", ++ }, ++ .default_tplg_path = { ++ [SOF_IPC_TYPE_3] = "amd/sof-tplg", ++ }, ++ .default_fw_filename = { ++ [SOF_IPC_TYPE_3] = "sof-acp7x.ri", ++ }, ++ .nocodec_tplg_filename = "sof-acp.tplg", ++ .ops = &sof_acp7x_ops, ++ .ops_init = sof_acp7x_ops_init, ++}; ++ ++static int acp7x_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) ++{ ++ unsigned int flag; ++ ++ switch (pci->revision) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ break; ++ default: ++ return -ENODEV; ++ } ++ ++ flag = snd_amd_acp_find_config(pci); ++ if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) ++ return -ENODEV; ++ ++ return sof_pci_probe(pci, pci_id); ++} ++ ++static void acp7x_pci_remove(struct pci_dev *pci) ++{ ++ sof_pci_remove(pci); ++} ++ ++/* PCI IDs */ ++static const struct pci_device_id acp7x_pci_ids[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), ++ .driver_data = (unsigned long)&acp7x_desc}, ++ { 0, } ++}; ++MODULE_DEVICE_TABLE(pci, acp7x_pci_ids); ++ ++/* pci_driver definition */ ++static struct pci_driver snd_sof_pci_amd_acp7x_driver = { ++ .name = KBUILD_MODNAME, ++ .id_table = acp7x_pci_ids, ++ .probe = acp7x_pci_probe, ++ .remove = acp7x_pci_remove, ++ .driver = { ++ .pm = pm_ptr(&sof_pci_pm), ++ }, ++}; ++module_pci_driver(snd_sof_pci_amd_acp7x_driver); ++ ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_DESCRIPTION("ACP7X SOF Driver"); ++MODULE_IMPORT_NS("SND_SOC_SOF_AMD_COMMON"); ++MODULE_IMPORT_NS("SND_SOC_SOF_PCI_DEV"); +diff --git a/sound/soc/sof/fw-file-profile.c b/sound/soc/sof/fw-file-profile.c +--- a/sound/soc/sof/fw-file-profile.c ++++ b/sound/soc/sof/fw-file-profile.c +@@ -16,20 +16,19 @@ static int sof_test_firmware_file(struct device *dev, + enum sof_ipc_type *ipc_type_to_adjust) + { + enum sof_ipc_type fw_ipc_type; +- const struct firmware *fw; +- const char *fw_filename; + const u32 *magic; + int ret; + +- fw_filename = kasprintf(GFP_KERNEL, "%s/%s", profile->fw_path, +- profile->fw_name); ++ const char *fw_filename __free(kfree) = ++ kasprintf(GFP_KERNEL, "%s/%s", profile->fw_path, ++ profile->fw_name); + if (!fw_filename) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = firmware_request_nowarn(&fw, fw_filename, dev); + if (ret < 0) { + dev_dbg(dev, "Failed to open firmware file: %s\n", fw_filename); +- kfree(fw_filename); + return ret; + } + +@@ -44,8 +43,7 @@ static int sof_test_firmware_file(struct device *dev, + break; + default: + dev_err(dev, "Invalid firmware magic: %#x\n", *magic); +- ret = -EINVAL; +- goto out; ++ return -EINVAL; + } + + if (ipc_type_to_adjust) { +@@ -54,13 +52,10 @@ static int sof_test_firmware_file(struct device *dev, + dev_err(dev, + "ipc type mismatch between %s and expected: %d vs %d\n", + fw_filename, fw_ipc_type, profile->ipc_type); +- ret = -EINVAL; ++ return -EINVAL; + } +-out: +- release_firmware(fw); +- kfree(fw_filename); + +- return ret; ++ return 0; + } + + static int sof_test_topology_file(struct device *dev, +diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c +--- a/sound/soc/sof/intel/hda-ctrl.c ++++ b/sound/soc/sof/intel/hda-ctrl.c +@@ -186,6 +186,7 @@ EXPORT_SYMBOL_NS(hda_dsp_ctrl_clock_power_gating, "SND_SOC_SOF_INTEL_HDA_COMMON" + int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec) + { + struct hdac_bus *bus = sof_to_bus(sdev); ++ struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); + struct hdac_stream *stream; + int sd_offset, ret = 0; + u32 gctl; +@@ -193,6 +194,16 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec) + if (bus->chip_init) + return 0; + ++ /* ++ * The controller reset clears the ACE2+ link DMA stream allocation ++ * constraints; reset the masks to reflect this. ++ */ ++ memset(sof_hda->link_dma_active_sdw_mask, 0, ++ sizeof(sof_hda->link_dma_active_sdw_mask)); ++ memset(sof_hda->link_dma_active_multi_mask, 0, ++ sizeof(sof_hda->link_dma_active_multi_mask)); ++ sof_hda->link_dma_out_hda_used_mask = 0; ++ + hda_codec_set_codec_wakeup(sdev, true); + + hda_dsp_ctrl_misc_clock_gating(sdev, false); +@@ -223,6 +234,14 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec) + /* Accept unsolicited responses */ + snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL); + ++ /* Perform a one-time enumeration of the Multi-Link capability */ ++ ret = hda_bus_ml_init(bus); ++ if (ret < 0) { ++ dev_err(sdev->dev, "%s: failed to enumerate multi-links\n", ++ __func__); ++ goto err; ++ } ++ + if (detect_codec) + hda_codec_detect_mask(sdev); + +diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c +--- a/sound/soc/sof/intel/hda-dai-ops.c ++++ b/sound/soc/sof/intel/hda-dai-ops.c +@@ -20,7 +20,7 @@ + /* These ops are only applicable for the HDA DAI's in their current form */ + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK) + /* +- * This function checks if the host dma channel corresponding ++ * This function checks if the host DMA stream corresponding + * to the link DMA stream_tag argument is assigned to one + * of the FEs connected to the BE DAI. + */ +@@ -42,23 +42,53 @@ static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd, + } + + static struct hdac_ext_stream * +-hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream) ++hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream, ++ enum hda_bus_ml_link_type link_type) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); + struct sof_intel_hda_stream *hda_stream; + const struct sof_intel_dsp_desc *chip; + struct snd_sof_dev *sdev; + struct hdac_ext_stream *res = NULL; + struct hdac_stream *hstream = NULL; +- + int stream_dir = substream->stream; ++ bool is_multi = link_type == HDA_BUS_ML_LINK_HDA || link_type == HDA_BUS_ML_LINK_UAOL; ++ bool is_play = stream_dir == SNDRV_PCM_STREAM_PLAYBACK; ++ bool is_sdw = link_type == HDA_BUS_ML_LINK_SDW; ++ bool is_hda = link_type == HDA_BUS_ML_LINK_HDA; ++ u32 concur_block_mask = 0; ++ u32 seq_block_mask = 0; ++ unsigned int stream_idx; + + if (!bus->ppcap) { + dev_err(bus->dev, "stream type not supported\n"); + return NULL; + } + ++ /* ++ * On ACE2+ the link DMA stream allocator must avoid two HW errata, ++ * see the comment on struct sof_intel_hda_dev. ++ * ++ * - Concurrent cross-direction: SoundWire conflicts with HDA, iDisp ++ * and UAOL on the same physical stream index; SSP and DMIC are safe. ++ * - Sequential playback: a stream index previously used by an HDA/iDisp ++ * link cannot drive any non-HDA/iDisp link in the same direction ++ * until the next controller reset. ++ * ++ * The masks are protected by bus->reg_lock; sample them inside the ++ * lock together with the stream walk to keep the decision atomic ++ * with concurrent allocations and releases. ++ */ + guard(spinlock_irq)(&bus->reg_lock); ++ ++ if (is_sdw) ++ concur_block_mask = sof_hda->link_dma_active_multi_mask[!stream_dir]; ++ else if (is_multi) ++ concur_block_mask = sof_hda->link_dma_active_sdw_mask[!stream_dir]; ++ if (is_play && !is_hda) ++ seq_block_mask = sof_hda->link_dma_out_hda_used_mask; ++ + list_for_each_entry(hstream, &bus->stream_list, list) { + struct hdac_ext_stream *hext_stream = + stream_to_hdac_ext_stream(hstream); +@@ -69,6 +99,12 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream + sdev = hda_stream->sdev; + chip = get_chip_info(sdev->pdata); + ++ stream_idx = hstream->stream_tag - 1; ++ ++ /* skip streams blocked by the ACE2+ allocator constraints */ ++ if ((concur_block_mask | seq_block_mask) & BIT(stream_idx)) ++ continue; ++ + /* check if link is available */ + if (!hext_stream->link_locked) { + /* +@@ -95,7 +131,7 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream + + /* + * This must be a hostless stream. +- * So reserve the host DMA channel. ++ * So reserve the host DMA stream. + */ + hda_stream->host_reserved = 1; + break; +@@ -109,6 +145,16 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream + + res->link_locked = 1; + res->link_substream = substream; ++ ++ stream_idx = res->hstream.stream_tag - 1; ++ if (is_sdw) ++ sof_hda->link_dma_active_sdw_mask[stream_dir] |= BIT(stream_idx); ++ else if (is_multi) ++ sof_hda->link_dma_active_multi_mask[stream_dir] |= BIT(stream_idx); ++ ++ /* persistent OUT HDA/iDisp shadow, cleared only on CRST# */ ++ if (is_hda && is_play) ++ sof_hda->link_dma_out_hda_used_mask |= BIT(stream_idx); + } + + return res; +@@ -143,11 +189,13 @@ static struct hdac_ext_stream *hda_ipc4_get_hext_stream(struct snd_sof_dev *sdev + + static struct hdac_ext_stream *hda_assign_hext_stream(struct snd_sof_dev *sdev, + struct snd_soc_dai *cpu_dai, +- struct snd_pcm_substream *substream) ++ struct snd_pcm_substream *substream, ++ struct hdac_ext_link *hlink) + { + struct hdac_ext_stream *hext_stream; ++ enum hda_bus_ml_link_type link_type = hda_bus_ml_link_get_type(hlink); + +- hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream); ++ hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream, link_type); + if (!hext_stream) + return NULL; + +@@ -160,6 +208,22 @@ static void hda_release_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai + struct snd_pcm_substream *substream) + { + struct hdac_ext_stream *hext_stream = hda_get_hext_stream(sdev, cpu_dai, substream); ++ struct sof_intel_hda_dev *sof_hda = sdev->pdata->hw_pdata; ++ struct hdac_bus *bus = sof_to_bus(sdev); ++ int dir = substream->stream; ++ unsigned int stream_idx = hext_stream->hstream.stream_tag - 1; ++ ++ /* ++ * Drop the stream index from the per-direction active concurrency masks. ++ * The two masks are mutually exclusive for a given stream/direction ++ * (and a stream of the SSP/DMIC kind appears in neither), so a blind ++ * clear of both is safe and lets us avoid having to remember the ++ * link type at allocation time. ++ */ ++ scoped_guard(spinlock_irq, &bus->reg_lock) { ++ sof_hda->link_dma_active_sdw_mask[dir] &= ~BIT(stream_idx); ++ sof_hda->link_dma_active_multi_mask[dir] &= ~BIT(stream_idx); ++ } + + snd_soc_dai_set_dma_data(cpu_dai, substream, NULL); + snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK); +diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c +--- a/sound/soc/sof/intel/hda-dai.c ++++ b/sound/soc/sof/intel/hda-dai.c +@@ -188,7 +188,7 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, + + if (!hext_stream) { + if (ops->assign_hext_stream) +- hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream); ++ hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream, hlink); + } + + if (!hext_stream) +diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c +--- a/sound/soc/sof/intel/hda-dsp.c ++++ b/sound/soc/sof/intel/hda-dsp.c +@@ -1114,11 +1114,7 @@ static int hda_dsp_s5_quirk(struct snd_sof_dev *sdev) + usleep_range(500, 1000); + + /* Restore state for shutdown, back to reset */ +- ret = hda_dsp_ctrl_link_reset(sdev, true); +- if (ret < 0) +- return ret; +- +- return ret; ++ return hda_dsp_ctrl_link_reset(sdev, true); + } + + int hda_dsp_shutdown_dma_flush(struct snd_sof_dev *sdev) +diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c +--- a/sound/soc/sof/intel/hda-loader.c ++++ b/sound/soc/sof/intel/hda-loader.c +@@ -216,9 +216,10 @@ int hda_cl_trigger(struct device *dev, struct hdac_ext_stream *hext_stream, int + EXPORT_SYMBOL_NS(hda_cl_trigger, "SND_SOC_SOF_INTEL_HDA_COMMON"); + + int hda_cl_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream) ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool is_iccmax) + { +- return hda_data_stream_cleanup(dev, dmab, persistent_buffer, hext_stream, false); ++ return hda_data_stream_cleanup(dev, dmab, persistent_buffer, hext_stream, ++ is_iccmax, false); + } + EXPORT_SYMBOL_NS(hda_cl_cleanup, "SND_SOC_SOF_INTEL_HDA_COMMON"); + +@@ -302,7 +303,7 @@ int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev) + * If the cleanup also fails, we return the initial error + */ + ret1 = hda_cl_cleanup(sdev->dev, &hda->iccmax_dmab, +- persistent_cl_buffer, iccmax_stream); ++ persistent_cl_buffer, iccmax_stream, true); + if (ret1 < 0) { + dev_err(sdev->dev, "error: ICCMAX stream cleanup failed\n"); + +@@ -458,7 +459,7 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) + * If the cleanup also fails, we return the initial error + */ + ret1 = hda_cl_cleanup(sdev->dev, &hda->cl_dmab, +- persistent_cl_buffer, hext_stream); ++ persistent_cl_buffer, hext_stream, false); + if (ret1 < 0) { + dev_err(sdev->dev, "error: Code loader DSP cleanup failed\n"); + +@@ -587,7 +588,7 @@ int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev, + cleanup: + /* clean up even in case of error and return the first error */ + ret1 = hda_cl_cleanup(sdev->dev, &hda->cl_dmab, persistent_cl_buffer, +- hext_stream); ++ hext_stream, false); + if (ret1 < 0) { + dev_err(sdev->dev, "%s: Code loader DSP cleanup failed\n", __func__); + +diff --git a/sound/soc/sof/intel/hda-mlink.c b/sound/soc/sof/intel/hda-mlink.c +--- a/sound/soc/sof/intel/hda-mlink.c ++++ b/sound/soc/sof/intel/hda-mlink.c +@@ -432,6 +432,10 @@ int hda_bus_ml_init(struct hdac_bus *bus) + if (!bus->mlcap) + return 0; + ++ /* Enumeration is a one time operation, skip if already done */ ++ if (!list_empty(&bus->hlink_list)) ++ return 0; ++ + link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1; + + dev_dbg(bus->dev, "HDAudio Multi-Link count: %d\n", link_count); +@@ -880,19 +884,6 @@ int hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y, + return 0; + } EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_map_stream_ch, "SND_SOC_SOF_HDA_MLINK"); + +-void hda_bus_ml_put_all(struct hdac_bus *bus) +-{ +- struct hdac_ext_link *hlink; +- +- list_for_each_entry(hlink, &bus->hlink_list, list) { +- struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink); +- +- if (!h2link->alt) +- snd_hdac_ext_bus_link_put(bus, hlink); +- } +-} +-EXPORT_SYMBOL_NS(hda_bus_ml_put_all, "SND_SOC_SOF_HDA_MLINK"); +- + void hda_bus_ml_reset_losidv(struct hdac_bus *bus) + { + struct hdac_ext_link *hlink; +@@ -903,6 +894,24 @@ void hda_bus_ml_reset_losidv(struct hdac_bus *bus) + } + EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, "SND_SOC_SOF_HDA_MLINK"); + ++enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink) ++{ ++ struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink); ++ ++ if (!h2link->alt) ++ return HDA_BUS_ML_LINK_HDA; ++ ++ switch (h2link->elid) { ++ case AZX_REG_ML_LEPTR_ID_SDW: ++ return HDA_BUS_ML_LINK_SDW; ++ case AZX_REG_ML_LEPTR_ID_INTEL_UAOL: ++ return HDA_BUS_ML_LINK_UAOL; ++ default: ++ return HDA_BUS_ML_LINK_OTHER; ++ } ++} ++EXPORT_SYMBOL_NS(hda_bus_ml_link_get_type, "SND_SOC_SOF_HDA_MLINK"); ++ + int hda_bus_ml_resume(struct hdac_bus *bus) + { + struct hdac_ext_link *hlink; +diff --git a/sound/soc/sof/intel/hda-sdw-bpt.c b/sound/soc/sof/intel/hda-sdw-bpt.c +--- a/sound/soc/sof/intel/hda-sdw-bpt.c ++++ b/sound/soc/sof/intel/hda-sdw-bpt.c +@@ -163,7 +163,7 @@ static int hda_sdw_bpt_dma_deprepare(struct device *dev, struct hdac_ext_stream + u32 mask; + int ret; + +- ret = hda_data_stream_cleanup(sdev->dev, dmab_bdl, false, sdw_bpt_stream, true); ++ ret = hda_data_stream_cleanup(sdev->dev, dmab_bdl, false, sdw_bpt_stream, false, true); + if (ret < 0) { + dev_err(sdev->dev, "%s: SDW BPT DMA cleanup failed\n", + __func__); +diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c +--- a/sound/soc/sof/intel/hda-stream.c ++++ b/sound/soc/sof/intel/hda-stream.c +@@ -198,13 +198,18 @@ int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, + + mask = (1 << hstream->index); + ++ /* Reset the spib_addr before disabling SPIB */ ++ if (!enable) ++ sof_io_write(sdev, hstream->spib_addr, 0); ++ + /* enable/disable SPIB for the stream */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_SPIB_BAR, + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL, mask, + enable << hstream->index); + + /* set the SPIB value */ +- sof_io_write(sdev, hstream->spib_addr, size); ++ if (enable) ++ sof_io_write(sdev, hstream->spib_addr, size); + + return 0; + } +@@ -608,6 +613,9 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, + return ret; + } + ++ /* Host DMA is not running */ ++ hstream->running = false; ++ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_SD_STS, + SOF_HDA_CL_DMA_SD_INT_MASK, +@@ -1323,16 +1331,18 @@ hda_data_stream_prepare(struct device *dev, unsigned int format, unsigned int si + EXPORT_SYMBOL_NS(hda_data_stream_prepare, "SND_SOC_SOF_INTEL_HDA_COMMON"); + + int hda_data_stream_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool pair) ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, ++ bool is_iccmax, bool pair) + { + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct hdac_stream *hstream = hdac_stream(hext_stream); + int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + int ret = 0; + +- if (hstream->direction == SNDRV_PCM_STREAM_PLAYBACK) ++ if (!is_iccmax) + ret = hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_DISABLE, 0); +- else ++ ++ if (hstream->direction == SNDRV_PCM_STREAM_CAPTURE) + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, + SOF_HDA_SD_CTL_DMA_START, 0); + +diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c +--- a/sound/soc/sof/intel/hda.c ++++ b/sound/soc/sof/intel/hda.c +@@ -421,6 +421,20 @@ static inline void hda_dsp_sdw_process_mic_privacy(struct snd_sof_dev *sdev) { } + /* pre fw run operations */ + int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev) + { ++ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; ++ const struct sof_intel_dsp_desc *chip = hda->desc; ++ int ret; ++ ++ /* Power down DSP if left enabled to ensure a clean boot state. */ ++ if (hda_dsp_core_is_enabled(sdev, chip->host_managed_cores_mask)) { ++ dev_dbg(sdev->dev, "DSP core enabled, power down DSP first\n"); ++ ++ ret = chip->power_down_dsp(sdev); ++ if (ret < 0) ++ dev_warn(sdev->dev, ++ "%s: failed to power down already-enabled DSP\n", __func__); ++ } ++ + /* disable clock gating and power gating */ + return hda_dsp_ctrl_clock_power_gating(sdev, false); + } +@@ -625,8 +639,6 @@ static int hda_init_caps(struct snd_sof_dev *sdev) + return ret; + } + +- hda_bus_ml_init(bus); +- + /* Skip SoundWire if it is not supported */ + if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH))) + goto skip_soundwire; +@@ -670,8 +682,6 @@ static int hda_init_caps(struct snd_sof_dev *sdev) + if (!HDA_IDISP_CODEC(bus->codec_mask)) + hda_codec_i915_display_power(sdev, false); + +- hda_bus_ml_put_all(bus); +- + return 0; + } + +diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h +--- a/sound/soc/sof/intel/hda.h ++++ b/sound/soc/sof/intel/hda.h +@@ -523,6 +523,29 @@ struct sof_intel_hda_dev { + /* the maximum number of streams (playback + capture) supported */ + u32 stream_max; + ++ /* ++ * ACE2+ link DMA stream allocation constraints (stream index = ++ * stream_tag - 1, shared between input and output directions). All ++ * masks are cleared by hda_dsp_ctrl_init_chip() on controller reset ++ * (CRST#). ++ * ++ * - Concurrent (cross-direction) constraint: a SoundWire stream and ++ * a HDA/iDisp/UAOL stream cannot share a physical stream index ++ * across directions, the resulting LLP/timestamp values are wrong. ++ * link_dma_active_sdw_mask and link_dma_active_multi_mask ++ * (indexed by SNDRV_PCM_STREAM_*) track currently allocated ++ * streams per direction in each of the conflicting groups; SSP ++ * and DMIC do not participate. Bits are cleared on stream release. ++ * ++ * - Sequential (playback only) constraint: once a HDA/iDisp link ++ * has used a playback stream index, that index cannot drive a ++ * non-HDA/iDisp link in the same direction until the next CRST#. ++ * link_dma_out_hda_used_mask records this. ++ */ ++ u32 link_dma_active_sdw_mask[SNDRV_PCM_STREAM_LAST + 1]; ++ u32 link_dma_active_multi_mask[SNDRV_PCM_STREAM_LAST + 1]; ++ u32 link_dma_out_hda_used_mask; ++ + /* PM related */ + bool l1_disabled;/* is DMI link L1 disabled? */ + +@@ -739,7 +762,7 @@ struct hdac_ext_stream *hda_cl_prepare(struct device *dev, unsigned int format, + int hda_cl_trigger(struct device *dev, struct hdac_ext_stream *hext_stream, int cmd); + + int hda_cl_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream); ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool is_iccmax); + int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot); + #define HDA_CL_STREAM_FORMAT 0x40 + +@@ -911,7 +934,8 @@ hda_data_stream_prepare(struct device *dev, unsigned int format, unsigned int si + bool is_iccmax, bool pair); + + int hda_data_stream_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool pair); ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, ++ bool is_iccmax, bool pair); + + /* common dai driver */ + extern struct snd_soc_dai_driver skl_dai[]; +@@ -1030,7 +1054,8 @@ struct hda_dai_widget_dma_ops { + struct snd_pcm_substream *substream); + struct hdac_ext_stream *(*assign_hext_stream)(struct snd_sof_dev *sdev, + struct snd_soc_dai *cpu_dai, +- struct snd_pcm_substream *substream); ++ struct snd_pcm_substream *substream, ++ struct hdac_ext_link *hlink); + void (*release_hext_stream)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream); + void (*setup_hext_stream)(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream, +diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c +--- a/sound/soc/sof/intel/icl.c ++++ b/sound/soc/sof/intel/icl.c +@@ -9,10 +9,14 @@ + * Hardware interface for audio DSP on IceLake. + */ + +-#include +-#include +-#include ++#include + #include ++#include ++#include ++#include ++#include ++#include ++ + #include "../ipc4-priv.h" + #include "../ops.h" + #include "hda.h" +diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c +--- a/sound/soc/sof/intel/mtl.c ++++ b/sound/soc/sof/intel/mtl.c +@@ -236,6 +236,17 @@ int mtl_enable_interrupts(struct snd_sof_dev *sdev, bool enable) + } + EXPORT_SYMBOL_NS(mtl_enable_interrupts, "SND_SOC_SOF_INTEL_MTL"); + ++static bool mtl_dsp_is_enabled(struct snd_sof_dev *sdev) ++{ ++ int val; ++ ++ val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFDSSCS); ++ if (val & MTL_HFDSSCS_CPA_MASK) ++ return true; ++ ++ return false; ++} ++ + /* pre fw run operations */ + static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev) + { +@@ -249,6 +260,18 @@ static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev) + u32 dsppwrsts; + const struct sof_intel_dsp_desc *chip; + ++ /* Power down the DSP if it is left enabled to ensure clean boot state */ ++ if (mtl_dsp_is_enabled(sdev)) { ++ dev_dbg(sdev->dev, "powering down DSP first\n"); ++ ++ ret = mtl_power_down_dsp(sdev); ++ if (ret < 0) { ++ dev_warn(sdev->dev, ++ "%s: failed to power down already-enabled DSP\n", __func__); ++ /* Continue anyway to attempt recovery */ ++ } ++ } ++ + chip = get_chip_info(sdev->pdata); + if (chip->hw_ip_version > SOF_INTEL_ACE_2_0) { + dsppwrctl = PTL_HFPWRCTL2; +diff --git a/sound/soc/sof/ipc3-pcm.c b/sound/soc/sof/ipc3-pcm.c +--- a/sound/soc/sof/ipc3-pcm.c ++++ b/sound/soc/sof/ipc3-pcm.c +@@ -421,6 +421,12 @@ static int sof_ipc3_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, + dev_dbg(component->dev, "AMD_SDW channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; ++ case SOF_DAI_AMD_I2S: ++ rate->min = private->dai_config->acp_i2s.fsync_rate; ++ rate->max = private->dai_config->acp_i2s.fsync_rate; ++ channels->min = private->dai_config->acp_i2s.tdm_slots; ++ channels->max = private->dai_config->acp_i2s.tdm_slots; ++ break; + default: + dev_err(component->dev, "Invalid DAI type %d\n", private->dai_config->type); + break; +diff --git a/sound/soc/sof/ipc3-topology.c b/sound/soc/sof/ipc3-topology.c +--- a/sound/soc/sof/ipc3-topology.c ++++ b/sound/soc/sof/ipc3-topology.c +@@ -281,7 +281,10 @@ static const struct sof_topology_token acpdmic_tokens[] = { + offsetof(struct sof_ipc_dai_acpdmic_params, pdm_ch)}, + }; + +-/* ACPI2S */ ++/* ++ * ACPI2S tokens fill struct sof_ipc_dai_acp_params; SOF_DAI_AMD_I2S (ACPTDM ++ * on ACP7.B/7.F) reuses this tuple group rather than defining a parallel set. ++ */ + static const struct sof_topology_token acpi2s_tokens[] = { + {SOF_TKN_AMD_ACPI2S_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_acp_params, fsync_rate)}, +@@ -289,6 +292,8 @@ static const struct sof_topology_token acpi2s_tokens[] = { + offsetof(struct sof_ipc_dai_acp_params, tdm_slots)}, + {SOF_TKN_AMD_ACPI2S_TDM_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_acp_params, tdm_mode)}, ++ {SOF_TKN_AMD_ACPI2S_FORMAT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, ++ offsetof(struct sof_ipc_dai_acp_params, format)}, + }; + + /* MICFIL PDM */ +@@ -1363,6 +1368,35 @@ static int sof_link_acp_sdw_load(struct snd_soc_component *scomp, struct snd_sof + return 0; + } + ++static int sof_link_acp_i2s_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, ++ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) ++{ ++ struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs; ++ struct sof_dai_private_data *private = dai->private; ++ u32 size = sizeof(*config); ++ int ret; ++ ++ /* handle master/slave and inverted clocks */ ++ sof_dai_set_format(hw_config, config); ++ ++ /* init IPC */ ++ memset(&config->acp_i2s, 0, sizeof(config->acp_i2s)); ++ config->hdr.size = size; ++ ++ ret = sof_update_ipc_object(scomp, &config->acp_i2s, SOF_ACPI2S_TOKENS, slink->tuples, ++ slink->num_tuples, size, slink->num_hw_configs); ++ if (ret < 0) ++ return ret; ++ ++ dai->number_configs = 1; ++ dai->current_config = 0; ++ private->dai_config = kmemdup(config, size, GFP_KERNEL); ++ if (!private->dai_config) ++ return -ENOMEM; ++ ++ return 0; ++} ++ + static int sof_link_afe_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) + { +@@ -1692,6 +1726,9 @@ static int sof_ipc3_widget_setup_comp_dai(struct snd_sof_widget *swidget) + case SOF_DAI_AMD_SDW: + ret = sof_link_acp_sdw_load(scomp, slink, config, dai); + break; ++ case SOF_DAI_AMD_I2S: ++ ret = sof_link_acp_i2s_load(scomp, slink, config, dai); ++ break; + default: + break; + } +diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c +--- a/sound/soc/sof/ipc4-topology.c ++++ b/sound/soc/sof/ipc4-topology.c +@@ -163,11 +163,11 @@ static const struct sof_topology_token comp_ext_tokens[] = { + {SOF_TKN_COMP_SCHED_DOMAIN, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_domain, + offsetof(struct snd_sof_widget, comp_domain)}, + {SOF_TKN_COMP_DOMAIN_ID, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, +- offsetof(struct snd_sof_widget, dp_domain_id)}, ++ offsetof(struct snd_sof_widget, domain_id)}, + {SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, +- offsetof(struct snd_sof_widget, dp_heap_bytes)}, ++ offsetof(struct snd_sof_widget, heap_bytes)}, + {SOF_TKN_COMP_STACK_BYTES_REQUIREMENT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, +- offsetof(struct snd_sof_widget, dp_stack_bytes)}, ++ offsetof(struct snd_sof_widget, stack_bytes)}, + }; + + static const struct sof_topology_token gain_tokens[] = { +@@ -406,6 +406,39 @@ sof_ipc4_get_input_pin_audio_fmt(struct snd_sof_widget *swidget, int pin_index) + return NULL; + } + ++static void ++sof_ipc4_evaluate_params_change(struct sof_ipc4_available_audio_format *available_fmt) ++{ ++ struct sof_ipc4_audio_format *fmt; ++ u32 in_rate, in_channels, in_valid_bits; ++ u32 out_rate, out_channels, out_valid_bits; ++ u32 changed_params = 0; ++ int i, j; ++ ++ for (i = 0; i < available_fmt->num_input_formats; i++) { ++ fmt = &available_fmt->input_pin_fmts[i].audio_fmt; ++ in_rate = fmt->sampling_frequency; ++ in_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); ++ in_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); ++ ++ for (j = 0; j < available_fmt->num_output_formats; j++) { ++ fmt = &available_fmt->output_pin_fmts[j].audio_fmt; ++ out_rate = fmt->sampling_frequency; ++ out_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); ++ out_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); ++ ++ if (in_rate != out_rate) ++ changed_params |= BIT(SNDRV_PCM_HW_PARAM_RATE); ++ if (in_channels != out_channels) ++ changed_params |= BIT(SNDRV_PCM_HW_PARAM_CHANNELS); ++ if (in_valid_bits != out_valid_bits) ++ changed_params |= BIT(SNDRV_PCM_HW_PARAM_FORMAT); ++ } ++ } ++ ++ available_fmt->changed_params = changed_params; ++} ++ + /** + * sof_ipc4_get_audio_fmt - get available audio formats from swidget->tuples + * @scomp: pointer to pointer to SOC component +@@ -497,6 +530,8 @@ static int sof_ipc4_get_audio_fmt(struct snd_soc_component *scomp, + available_fmt->num_output_formats); + } + ++ sof_ipc4_evaluate_params_change(available_fmt); ++ + return 0; + + err_out: +@@ -661,6 +696,9 @@ static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget) + if (ret) + goto free_copier; + ++ /* Copier can only change format */ ++ available_fmt->changed_params &= BIT(SNDRV_PCM_HW_PARAM_FORMAT); ++ + /* + * This callback is used by host copier and module-to-module copier, + * and only host copier needs to set gtw_cfg. +@@ -789,6 +827,9 @@ static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget) + if (ret) + goto free_copier; + ++ /* Copier can only change format */ ++ available_fmt->changed_params &= BIT(SNDRV_PCM_HW_PARAM_FORMAT); ++ + ret = sof_update_ipc_object(scomp, &node_type, + SOF_COPIER_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(node_type), 1); +@@ -1127,6 +1168,7 @@ static int sof_ipc4_widget_setup_comp_src(struct snd_sof_widget *swidget) + "Invalid number of formats: input: %d, output: %d\n", + src->available_fmt.num_input_formats, + src->available_fmt.num_output_formats); ++ ret = -EINVAL; + goto err; + } + +@@ -1179,6 +1221,7 @@ static int sof_ipc4_widget_setup_comp_asrc(struct snd_sof_widget *swidget) + "Invalid number of formats: input: %d, output: %d\n", + asrc->available_fmt.num_input_formats, + asrc->available_fmt.num_output_formats); ++ ret = -EINVAL; + goto err; + } + +@@ -1369,6 +1412,22 @@ sof_ipc4_update_resource_usage(struct snd_sof_dev *sdev, struct snd_sof_widget * + pipeline = pipe_widget->private; + pipeline->mem_usage += total; + ++ /* ++ * If this is not a Data Processing module instance, add the ++ * required heap sizes to the sum of all module instances belonging ++ * to the same pipeline, and find the maximum stack requirement ++ * among all module instances belonging to the same pipeline. ++ */ ++ if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) { ++ pipe_widget->heap_bytes += swidget->heap_bytes; ++ if (pipe_widget->stack_bytes < swidget->stack_bytes) ++ pipe_widget->stack_bytes = swidget->stack_bytes; ++ ++ dev_dbg(sdev->dev, "%s mem reqs to %s heap %u stack %u", ++ swidget->widget->name, pipe_widget->widget->name, ++ pipe_widget->heap_bytes, pipe_widget->stack_bytes); ++ } ++ + /* Update base_config->cpc from the module manifest */ + sof_ipc4_update_cpc_from_manifest(sdev, fw_module, base_config); + +@@ -1686,6 +1745,8 @@ static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget) + pipe_widget = swidget->spipe->pipe_widget; + pipeline = pipe_widget->private; + pipeline->mem_usage = 0; ++ pipe_widget->heap_bytes = 0; ++ pipe_widget->stack_bytes = 0; + + if (WIDGET_IS_AIF(swidget->id) || swidget->id == snd_soc_dapm_buffer) { + if (pipeline->use_chain_dma) { +@@ -2063,10 +2124,57 @@ static void sof_ipc4_host_config(struct snd_sof_dev *sdev, struct snd_sof_widget + } + + static int +-sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, +- struct snd_pcm_hw_params *fe_params, +- struct snd_sof_platform_stream_params *platform_params, +- struct snd_pcm_hw_params *pipeline_params, int dir) ++sof_ipc4_copier_module_update_params(struct snd_sof_widget *swidget, ++ struct snd_pcm_hw_params *pipeline_params) ++{ ++ struct snd_soc_component *scomp = swidget->scomp; ++ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); ++ struct sof_ipc4_copier_data *copier_data; ++ struct sof_ipc4_copier *ipc4_copier; ++ ++ switch (swidget->id) { ++ case snd_soc_dapm_aif_in: ++ case snd_soc_dapm_aif_out: ++ case snd_soc_dapm_buffer: ++ ipc4_copier = swidget->private; ++ copier_data = &ipc4_copier->data; ++ break; ++ case snd_soc_dapm_dai_in: ++ case snd_soc_dapm_dai_out: ++ { ++ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; ++ struct sof_ipc4_pipeline *pipeline = pipe_widget->private; ++ struct snd_sof_dai *dai; ++ ++ if (pipeline->use_chain_dma) ++ return 0; ++ ++ dai = swidget->private; ++ ++ ipc4_copier = (struct sof_ipc4_copier *)dai->private; ++ copier_data = &ipc4_copier->data; ++ ++ break; ++ } ++ default: ++ dev_err(sdev->dev, "unsupported type %d for copier %s", ++ swidget->id, swidget->widget->name); ++ return -EINVAL; ++ } ++ ++ /* modify the input params for the next widget */ ++ return sof_ipc4_update_hw_params(sdev, pipeline_params, ++ &copier_data->out_format, ++ BIT(SNDRV_PCM_HW_PARAM_FORMAT) | ++ BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | ++ BIT(SNDRV_PCM_HW_PARAM_RATE)); ++} ++ ++static int ++_sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, ++ struct snd_pcm_hw_params *fe_params, ++ struct snd_sof_platform_stream_params *platform_params, ++ struct snd_pcm_hw_params *pipeline_params, int dir) + { + struct sof_ipc4_available_audio_format *available_fmt; + struct snd_soc_component *scomp = swidget->scomp; +@@ -2538,6 +2646,21 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, + return 0; + } + ++static int ++sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, ++ struct snd_pcm_hw_params *fe_params, ++ struct snd_sof_platform_stream_params *platform_params, ++ struct snd_pcm_hw_params *pipeline_params, int dir) ++{ ++ if (swidget->prepared) ++ return sof_ipc4_copier_module_update_params(swidget, ++ pipeline_params); ++ ++ return _sof_ipc4_prepare_copier_module(swidget, fe_params, ++ platform_params, pipeline_params, ++ dir); ++} ++ + static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, +@@ -2551,6 +2674,10 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; + int input_fmt_index, output_fmt_index; + ++ /* This cannot happen */ ++ if (unlikely(swidget->prepared)) ++ return 0; ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &gain->data.base_config, + pipeline_params, +@@ -2596,6 +2723,10 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; + int input_fmt_index, output_fmt_index; + ++ /* Already prepared, nothing to do */ ++ if (swidget->prepared) ++ return 0; ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &mixer->base_config, + pipeline_params, +@@ -2642,6 +2773,10 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; + int output_fmt_index, input_fmt_index; + ++ /* This cannot happen */ ++ if (unlikely(swidget->prepared)) ++ return 0; ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &src->data.base_config, + pipeline_params, +@@ -2808,6 +2943,18 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + int ret; + + if (available_fmt->num_input_formats) { ++ if (swidget->prepared) { ++ if (!available_fmt->num_output_formats) ++ return 0; ++ ++ /* modify the pipeline params with the output format */ ++ return sof_ipc4_update_hw_params(sdev, pipeline_params, ++ &process->output_format, ++ BIT(SNDRV_PCM_HW_PARAM_FORMAT) | ++ BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | ++ BIT(SNDRV_PCM_HW_PARAM_RATE)); ++ } ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &process->base_config, + pipeline_params, +@@ -2820,39 +2967,69 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + if (available_fmt->num_output_formats) { + struct sof_ipc4_audio_format *in_fmt; + struct sof_ipc4_pin_format *pin_fmt; +- u32 out_ref_rate, out_ref_channels; +- int out_ref_valid_bits, out_ref_type; ++ u32 ref_rate, ref_channels; ++ int ref_valid_bits, ref_type; + + if (available_fmt->num_input_formats) { ++ /* ++ * The process module can change parameters and their operation ++ * depends on the direction: ++ * Playback: typically they have single output format. This is ++ * to 'force' the conversion from input to output. ++ * Use the input format as reference since the single ++ * format is going to be picked. ++ * Capture: typically they have multiple output formats to ++ * convert from dai (input) to FE (output) parameters. ++ * Use the input format as base and replace the param ++ * which is changed by the module with the FE parameter ++ * Reason: we can have module which changes the ++ * parameters in path, we cannot use the full ++ * FE param set for the module output lookup. ++ */ + in_fmt = &available_fmt->input_pin_fmts[input_fmt_index].audio_fmt; + +- out_ref_rate = in_fmt->sampling_frequency; +- out_ref_channels = ++ ref_rate = in_fmt->sampling_frequency; ++ ref_channels = + SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); +- out_ref_valid_bits = ++ ref_valid_bits = + SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); +- out_ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); ++ ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); + } else { + /* for modules without input formats, use FE params as reference */ +- out_ref_rate = params_rate(fe_params); +- out_ref_channels = params_channels(fe_params); ++ ref_rate = params_rate(fe_params); ++ ref_channels = params_channels(fe_params); + ret = sof_ipc4_get_sample_type(sdev, fe_params); + if (ret < 0) + return ret; +- out_ref_type = (u32)ret; ++ ref_type = (u32)ret; + +- out_ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); +- if (out_ref_valid_bits < 0) +- return out_ref_valid_bits; ++ ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); ++ if (ref_valid_bits < 0) ++ return ref_valid_bits; + } + ++ if (dir == SNDRV_PCM_STREAM_CAPTURE) { ++ if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_RATE)) ++ ref_rate = params_rate(fe_params); ++ if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_CHANNELS)) ++ ref_channels = params_channels(fe_params); ++ if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_FORMAT)) { ++ ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); ++ if (ref_valid_bits < 0) ++ return ref_valid_bits; ++ ++ ref_type = sof_ipc4_get_sample_type(sdev, fe_params); ++ if (ref_type < 0) ++ return ref_type; ++ } ++ } + output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, + &process->base_config, + available_fmt, +- out_ref_rate, +- out_ref_channels, +- out_ref_valid_bits, +- out_ref_type); ++ ref_rate, ++ ref_channels, ++ ref_valid_bits, ++ ref_type); + if (output_fmt_index < 0) + return output_fmt_index; + +@@ -2866,9 +3043,7 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + /* modify the pipeline params with the output format */ + ret = sof_ipc4_update_hw_params(sdev, pipeline_params, + &process->output_format, +- BIT(SNDRV_PCM_HW_PARAM_FORMAT) | +- BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | +- BIT(SNDRV_PCM_HW_PARAM_RATE)); ++ available_fmt->changed_params); + if (ret) + return ret; + } +@@ -3083,27 +3258,47 @@ static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_contr + return 0; + } + +-static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, +- struct snd_sof_widget *swidget, +- struct sof_ipc4_msg *msg, +- void *ipc_data, u32 ipc_size, +- void **new_data) ++static void sof_ipc4_add_init_ext_dp_memory_data(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ u32 *payload, u32 *ext_pos, ++ struct sof_ipc4_module_init_ext_object **hdr) ++{ ++ /* Add memory_data if comp_domain indicates DP */ ++ if (swidget->comp_domain == SOF_COMP_DOMAIN_DP) { ++ struct sof_ipc4_mod_init_ext_dp_memory_data *dp_mem_data; ++ ++ *hdr = (struct sof_ipc4_module_init_ext_object *)&payload[*ext_pos]; ++ (*hdr)->header = ++ SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA) | ++ SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*dp_mem_data), ++ sizeof(u32))); ++ *ext_pos += DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)); ++ dp_mem_data = (struct sof_ipc4_mod_init_ext_dp_memory_data *)&payload[*ext_pos]; ++ dp_mem_data->domain_id = swidget->domain_id; ++ dp_mem_data->stack_bytes = swidget->stack_bytes; ++ dp_mem_data->heap_bytes = swidget->heap_bytes; ++ *ext_pos += DIV_ROUND_UP(sizeof(*dp_mem_data), sizeof(u32)); ++ } ++} ++ ++static int sof_ipc4_widget_mod_init_msg_payload(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ struct sof_ipc4_msg *msg, ++ void *ipc_data, u32 ipc_size, ++ void **new_data) + { +- struct sof_ipc4_mod_init_ext_dp_memory_data *dp_mem_data; + struct sof_ipc4_module_init_ext_init *ext_init; +- struct sof_ipc4_module_init_ext_object *hdr; ++ struct sof_ipc4_module_init_ext_object *hdr = NULL; + int new_size; + u32 *payload; + u32 ext_pos; + +- /* For the moment the only reason for adding init_ext_init payload is DP +- * memory data. If both stack and heap size are 0 (= use default), then +- * there is no need for init_ext_init payload. ++ /* ++ * Only DP widgets currently add init-ext objects here. Avoid allocating ++ * a max-sized payload buffer for widgets that will immediately return 0. + */ +- if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) { +- msg->extension &= ~SOF_IPC4_MOD_EXT_EXTENDED_INIT_MASK; ++ if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) + return 0; +- } + + payload = kzalloc(sdev->ipc->max_payload_size, GFP_KERNEL); + if (!payload) +@@ -3111,27 +3306,26 @@ static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, + + /* Add ext_init first and set objects array flag to 1 */ + ext_init = (struct sof_ipc4_module_init_ext_init *)payload; +- ext_init->word0 |= SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_MASK; + ext_pos = DIV_ROUND_UP(sizeof(*ext_init), sizeof(u32)); + + /* Add object array objects after ext_init */ + +- /* Add dp_memory_data if comp_domain indicates DP */ +- if (swidget->comp_domain == SOF_COMP_DOMAIN_DP) { +- hdr = (struct sof_ipc4_module_init_ext_object *)&payload[ext_pos]; +- hdr->header = SOF_IPC4_MOD_INIT_EXT_OBJ_LAST_MASK | +- SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA) | +- SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*dp_mem_data), +- sizeof(u32))); +- ext_pos += DIV_ROUND_UP(sizeof(*hdr), sizeof(u32)); +- dp_mem_data = (struct sof_ipc4_mod_init_ext_dp_memory_data *)&payload[ext_pos]; +- dp_mem_data->domain_id = swidget->dp_domain_id; +- dp_mem_data->stack_bytes = swidget->dp_stack_bytes; +- dp_mem_data->heap_bytes = swidget->dp_heap_bytes; +- ext_pos += DIV_ROUND_UP(sizeof(*dp_mem_data), sizeof(u32)); ++ sof_ipc4_add_init_ext_dp_memory_data(sdev, swidget, payload, &ext_pos, &hdr); ++ ++ /* Add following object array items here */ ++ ++ if (!hdr) { ++ /* ++ * NOTE: Remove this early bail out, when struct ++ * sof_ipc4_module_init_ext_init alone has some ++ * function. ++ */ ++ kfree(payload); ++ return 0; + } + +- /* If another array object is added, remember clear previous OBJ_LAST bit */ ++ ext_init->word0 |= SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_MASK; ++ hdr->header |= SOF_IPC4_MOD_INIT_EXT_OBJ_LAST_MASK; + + /* Calculate final size and check that it fits to max payload size */ + new_size = ext_pos * sizeof(u32) + ipc_size; +@@ -3154,6 +3348,69 @@ static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, + return new_size; + } + ++static void sof_ipc4_widget_pipe_ext_obj_memory_data(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ u32 *payload, u32 *ext_pos, ++ struct sof_ipc4_glb_pipe_ext_object **hdr) ++{ ++ struct sof_ipc4_glb_pipe_ext_obj_memory_data *mem_data; ++ ++ *hdr = (struct sof_ipc4_glb_pipe_ext_object *)&payload[*ext_pos]; ++ (*hdr)->header = ++ SOF_IPC4_GLB_PIPE_EXT_OBJ_ID(SOF_IPC4_GLB_PIPE_DATA_ID_MEM_DATA) | ++ SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*mem_data), ++ sizeof(u32))); ++ *ext_pos += DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)); ++ mem_data = (struct sof_ipc4_glb_pipe_ext_obj_memory_data *)&payload[*ext_pos]; ++ mem_data->domain_id = swidget->domain_id; ++ mem_data->stack_bytes = swidget->stack_bytes; ++ mem_data->heap_bytes = swidget->heap_bytes; ++ *ext_pos += DIV_ROUND_UP(sizeof(*mem_data), sizeof(u32)); ++ ++ dev_dbg(sdev->dev, ++ "%s; domain_id %u stack %u heap %u bytes", ++ swidget->widget->name, mem_data->domain_id, mem_data->stack_bytes, ++ mem_data->heap_bytes); ++} ++ ++static int sof_ipc4_widget_pipe_create_msg_payload(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ struct sof_ipc4_msg *msg, ++ void **new_data) ++{ ++ struct sof_ipc4_glb_pipe_payload *payload_hdr; ++ struct sof_ipc4_glb_pipe_ext_object *hdr = NULL; ++ u32 *payload; ++ u32 ext_pos; ++ ++ payload = kzalloc(sdev->ipc->max_payload_size, GFP_KERNEL); ++ if (!payload) ++ return -ENOMEM; ++ ++ /* Add sof_ipc4_glb_pipe_payload and set array bit to 1 */ ++ payload_hdr = (struct sof_ipc4_glb_pipe_payload *)payload; ++ payload_hdr->word0 |= SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_MASK; ++ ext_pos = DIV_ROUND_UP(sizeof(*payload_hdr), sizeof(u32)); ++ ++ sof_ipc4_widget_pipe_ext_obj_memory_data(sdev, swidget, payload, &ext_pos, &hdr); ++ /* Add following array objects here */ ++ ++ /* Mark end of object array */ ++ hdr->header |= SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_MASK; ++ ++ /* Put total payload size in words to the payload header */ ++ payload_hdr->word0 |= SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS(ext_pos); ++ *new_data = payload; ++ ++ /* Update msg extension bits according to the payload changes */ ++ msg->extension |= SOF_IPC4_GLB_PIPE_PAYLOAD_MASK; ++ ++ dev_dbg(sdev->dev, "%s: payload word0 %#x", swidget->widget->name, ++ payload_hdr->word0); ++ ++ return ext_pos * sizeof(int32_t); ++} ++ + static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) + { + struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; +@@ -3340,8 +3597,8 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget + swidget->widget->name, swidget->pipeline_id, module_id, + swidget->instance_id, swidget->core); + +- ret = sof_ipc4_widget_setup_msg_payload(sdev, swidget, msg, ipc_data, ipc_size, +- &ext_data); ++ ret = sof_ipc4_widget_mod_init_msg_payload(sdev, swidget, msg, ipc_data, ipc_size, ++ &ext_data); + if (ret < 0) + goto fail; + +@@ -3353,6 +3610,17 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget + dev_dbg(sdev->dev, "Create pipeline %s (pipe %d) - instance %d, core %d\n", + swidget->widget->name, swidget->pipeline_id, + swidget->instance_id, swidget->core); ++ ++ msg->extension &= ~SOF_IPC4_GLB_PIPE_PAYLOAD_MASK; ++ ret = sof_ipc4_widget_pipe_create_msg_payload(sdev, swidget, msg, ++ &ext_data); ++ if (ret < 0) ++ goto fail; ++ ++ if (ret > 0) { ++ ipc_size = ret; ++ ipc_data = ext_data; ++ } + } + + msg->data_size = ipc_size; +@@ -3410,6 +3678,8 @@ static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget + swidget->widget->name); + + pipeline->mem_usage = 0; ++ swidget->heap_bytes = 0; ++ swidget->stack_bytes = 0; + pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED; + ida_free(&pipeline_ida, swidget->instance_id); + swidget->instance_id = -EINVAL; +diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h +--- a/sound/soc/sof/ipc4-topology.h ++++ b/sound/soc/sof/ipc4-topology.h +@@ -167,7 +167,7 @@ struct sof_ipc4_pipeline { + }; + + /** +- * struct sof_ipc4_multi_pipeline_data - multi pipeline trigger IPC data ++ * struct ipc4_pipeline_set_state_data - multi pipeline trigger IPC data + * @count: Number of pipelines to be triggered + * @pipeline_instance_ids: Flexible array of IDs of the pipelines to be triggered + */ +@@ -197,12 +197,15 @@ struct sof_ipc4_pin_format { + * @input_pin_fmts: Available input pin formats + * @num_input_formats: Number of input pin formats + * @num_output_formats: Number of output pin formats ++ * @changed_params: Mask of changed params by the module instance between it's ++ * input and output formts (rate, channels, depth) + */ + struct sof_ipc4_available_audio_format { + struct sof_ipc4_pin_format *output_pin_fmts; + struct sof_ipc4_pin_format *input_pin_fmts; + u32 num_input_formats; + u32 num_output_formats; ++ u32 changed_params; + }; + + /** +@@ -210,7 +213,7 @@ struct sof_ipc4_available_audio_format { + * @node_id: ID of Gateway Node + * @dma_buffer_size: Preferred Gateway DMA buffer size (in bytes) + * @config_length: Length of gateway node configuration blob specified in #config_data +- * config_data: Gateway node configuration blob ++ * @config_data: Gateway node configuration blob + */ + struct sof_copier_gateway_cfg { + uint32_t node_id; +@@ -285,7 +288,9 @@ struct sof_ipc4_dma_stream_ch_map { + struct sof_ipc4_dma_config { + uint8_t dma_method; + uint8_t pre_allocated_by_host; ++ /* private: */ + uint16_t rsvd; ++ /* public: */ + uint32_t dma_channel_id; + uint32_t stream_id; + struct sof_ipc4_dma_stream_ch_map dma_stream_channel_map; +@@ -296,7 +301,7 @@ struct sof_ipc4_dma_config { + #define SOF_IPC4_GTW_DMA_CONFIG_ID 0x1000 + + /** +- * struct sof_ipc4_dma_config: DMA configuration ++ * struct sof_ipc4_dma_config_tlv - DMA configuration + * @type: set to SOF_IPC4_GTW_DMA_CONFIG_ID + * @length: sizeof(struct sof_ipc4_dma_config) + dma_config.dma_priv_config_size + * @dma_config: actual DMA configuration +@@ -321,6 +326,7 @@ struct sof_ipc4_alh_configuration_blob { + * @data: IPC copier data + * @copier_config: Copier + blob + * @ipc_config_size: Size of copier_config ++ * @ipc_config_data: Copier module config data + * @available_fmt: Available audio format + * @frame_fmt: frame format + * @msg: message structure for copier +diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c +--- a/sound/soc/sof/ipc4.c ++++ b/sound/soc/sof/ipc4.c +@@ -289,6 +289,89 @@ static void sof_ipc4_dump_payload(struct snd_sof_dev *sdev, + 16, 4, ipc_data, size, false); + } + ++static const char *sof_ipc4_resource_type_str(u32 type) ++{ ++ switch (type) { ++ case SOF_IPC4_MODULE_INSTANCE: ++ return "resource: MODULE_INSTANCE"; ++ case SOF_IPC4_PIPELINE: ++ return "resource: PIPELINE"; ++ case SOF_IPC4_GATEWAY: ++ return "resource: GATEWAY"; ++ case SOF_IPC4_EDF_TASK: ++ return "resource: EDF_TASK"; ++ case SOF_IPC4_INVALID_RESOURCE_TYPE: ++ return "Resource is invalid"; ++ default: ++ return "Unknown resource type"; ++ } ++} ++ ++static const char *sof_ipc4_resource_event_type_str(u32 event_type) ++{ ++ switch (event_type) { ++ case SOF_IPC4_MIXER_UNDERRUN_DETECTED: ++ return "event: MIXER_UNDERRUN_DETECTED"; ++ case SOF_IPC4_PROCESS_DATA_ERROR: ++ return "event: PROCESS_DATA_ERROR"; ++ case SOF_IPC4_GATEWAY_UNDERRUN_DETECTED: ++ return "event: GATEWAY_UNDERRUN_DETECTED"; ++ case SOF_IPC4_GATEWAY_OVERRUN_DETECTED: ++ return "event: GATEWAY_OVERRUN_DETECTED"; ++ default: ++ return "Unknown event type"; ++ } ++} ++ ++static void sof_ipc4_resource_event_handler(struct snd_sof_dev *sdev, ++ struct sof_ipc4_msg *ipc4_msg) ++{ ++ struct sof_ipc4_notify_resource_data *data = ipc4_msg->data_ptr; ++ ++ /* Print event details */ ++ switch (data->event_type) { ++ case SOF_IPC4_MIXER_UNDERRUN_DETECTED: ++ dev_dbg(sdev->dev, "%s (%u): eos %u, mixed %u, expected %u\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type, data->data.mixer_underrun.eos_flag, ++ data->data.mixer_underrun.data_mixed, ++ data->data.mixer_underrun.expected_data_mixed); ++ break; ++ case SOF_IPC4_PROCESS_DATA_ERROR: ++ dev_dbg(sdev->dev, "%s (%u): error_code %#x\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type, data->data.process_data_error.error_code); ++ break; ++ case SOF_IPC4_GATEWAY_UNDERRUN_DETECTED: ++ case SOF_IPC4_GATEWAY_OVERRUN_DETECTED: ++ dev_dbg(sdev->dev, "%s (%u)\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type); ++ break; ++ default: ++ dev_dbg(sdev->dev, "%s (%u): raw dws %#x %#x %#x %#x %#x %#x\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type, ++ data->data.dws[0], data->data.dws[1], data->data.dws[2], ++ data->data.dws[3], data->data.dws[4], data->data.dws[5]); ++ break; ++ } ++ ++ /* Print resource details */ ++ if (data->resource_type == SOF_IPC4_MODULE_INSTANCE) { ++ u32 module_id = SOF_IPC4_MOD_ID_GET(data->resource_id); ++ u32 instance_id = SOF_IPC4_MOD_INSTANCE_GET(data->resource_id); ++ ++ dev_dbg(sdev->dev, "%s (%u), module_id %u, instance_id %u\n", ++ sof_ipc4_resource_type_str(data->resource_type), ++ data->resource_type, module_id, instance_id); ++ } else if (data->resource_type != SOF_IPC4_INVALID_RESOURCE_TYPE) { ++ dev_dbg(sdev->dev, "%s (%u), id %u\n", ++ sof_ipc4_resource_type_str(data->resource_type), ++ data->resource_type, data->resource_id); ++ } ++} ++ + static int sof_ipc4_get_reply(struct snd_sof_dev *sdev) + { + struct snd_sof_ipc_msg *msg = sdev->msg; +@@ -734,6 +817,7 @@ static void sof_ipc4_rx_msg(struct snd_sof_dev *sdev) + break; + case SOF_IPC4_NOTIFY_RESOURCE_EVENT: + data_size = sizeof(struct sof_ipc4_notify_resource_data); ++ handler_func = sof_ipc4_resource_event_handler; + break; + case SOF_IPC4_NOTIFY_LOG_BUFFER_STATUS: + sof_ipc4_mtrace_update_pos(sdev, SOF_IPC4_LOG_CORE_GET(ipc4_msg->primary)); +diff --git a/sound/soc/sof/nocodec.c b/sound/soc/sof/nocodec.c +--- a/sound/soc/sof/nocodec.c ++++ b/sound/soc/sof/nocodec.c +@@ -21,12 +21,13 @@ static struct snd_soc_card sof_nocodec_card = { + static int sof_nocodec_bes_setup(struct device *dev, + struct snd_soc_dai_driver *drv, + struct snd_soc_dai_link *links, +- int link_num, struct snd_soc_card *card) ++ int link_num) + { ++ struct snd_soc_card *card = &sof_nocodec_card; + struct snd_soc_dai_link_component *dlc; + int i; + +- if (!drv || !links || !card) ++ if (!drv || !links) + return -EINVAL; + + /* set up BE dai_links */ +@@ -67,10 +68,10 @@ static int sof_nocodec_bes_setup(struct device *dev, + return 0; + } + +-static int sof_nocodec_setup(struct device *dev, +- u32 num_dai_drivers, +- struct snd_soc_dai_driver *dai_drivers) ++static int sof_nocodec_setup(struct device *dev, struct snd_soc_acpi_mach *mach) + { ++ u32 num_dai_drivers = mach->mach_params.num_dai_drivers; ++ struct snd_soc_dai_driver *dai_drivers = mach->mach_params.dai_drivers; + struct snd_soc_dai_link *links; + + /* create dummy BE dai_links */ +@@ -78,7 +79,7 @@ static int sof_nocodec_setup(struct device *dev, + if (!links) + return -ENOMEM; + +- return sof_nocodec_bes_setup(dev, dai_drivers, links, num_dai_drivers, &sof_nocodec_card); ++ return sof_nocodec_bes_setup(dev, dai_drivers, links, num_dai_drivers); + } + + static int sof_nocodec_probe(struct platform_device *pdev) +@@ -92,8 +93,7 @@ static int sof_nocodec_probe(struct platform_device *pdev) + + snd_soc_card_set_topology_name(card, "sof"); + +- ret = sof_nocodec_setup(card->dev, mach->mach_params.num_dai_drivers, +- mach->mach_params.dai_drivers); ++ ret = sof_nocodec_setup(card->dev, mach); + if (ret < 0) + return ret; + +diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c +--- a/sound/soc/sof/pcm.c ++++ b/sound/soc/sof/pcm.c +@@ -62,7 +62,7 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) + * To avoid sending IPC before the previous IPC is handled, we + * schedule delayed work here to call the snd_pcm_period_elapsed(). + */ +- schedule_work(&spcm->stream[substream->stream].period_elapsed_work); ++ queue_work(system_highpri_wq, &spcm->stream[substream->stream].period_elapsed_work); + } + EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); + +diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c +--- a/sound/soc/sof/sof-audio.c ++++ b/sound/soc/sof/sof-audio.c +@@ -501,9 +501,8 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget + !sof_widget_in_same_direction(swidget, dir)) + return 0; + +- /* skip widgets already prepared or aggregated DAI widgets*/ +- if (!widget_ops[widget->id].ipc_prepare || swidget->prepared || +- is_aggregated_dai(swidget)) ++ /* skip widgets aggregated DAI widgets */ ++ if (!widget_ops[widget->id].ipc_prepare || is_aggregated_dai(swidget)) + goto sink_prepare; + + /* prepare the source widget */ +diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h +--- a/sound/soc/sof/sof-audio.h ++++ b/sound/soc/sof/sof-audio.h +@@ -459,10 +459,10 @@ struct snd_sof_widget { + /* Scheduling domain (enum sof_comp_domain), unset, Low Latency, or Data Processing */ + u32 comp_domain; + +- /* The values below are added to mod_init pay load if comp_domain indicates DP component */ +- u32 dp_domain_id; /* DP process userspace domain ID */ +- u32 dp_stack_bytes; /* DP process stack size requirement in bytes */ +- u32 dp_heap_bytes; /* DP process heap size requirement in bytes */ ++ /* Module instance's memory configuration. */ ++ u32 domain_id; /* Module instance's userspace domain ID */ ++ u32 stack_bytes; /* Module instance's stack size requirement */ ++ u32 heap_bytes; /* Module instance's heap size requirement */ + + struct snd_soc_dapm_widget *widget; + struct list_head list; /* list in sdev widget list */ +diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c +--- a/sound/soc/sof/topology.c ++++ b/sound/soc/sof/topology.c +@@ -309,7 +309,7 @@ static const struct sof_dai_types sof_dais[] = { + {"ACPHS_VIRTUAL", SOF_DAI_AMD_HS_VIRTUAL}, + {"MICFIL", SOF_DAI_IMX_MICFIL}, + {"ACP_SDW", SOF_DAI_AMD_SDW}, +- ++ {"ACPTDM", SOF_DAI_AMD_I2S}, + }; + + static enum sof_ipc_dai_type find_dai(const char *name) +@@ -846,6 +846,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + struct snd_soc_tplg_mixer_control *mc = + container_of(hdr, struct snd_soc_tplg_mixer_control, hdr); + int tlv[SOF_TLV_ITEMS]; ++ u32 min, max; + unsigned int mask; + int ret; + +@@ -853,6 +854,11 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN) + return -EINVAL; + ++ min = le32_to_cpu(mc->min); ++ max = le32_to_cpu(mc->max); ++ if (min > max || max >= INT_MAX) ++ return -EINVAL; ++ + /* + * If control has more than 2 channels we need to override the info. This is because even if + * ASoC layer has defined topology's max channel count to SND_SOC_TPLG_MAX_CHAN = 8, the +@@ -863,12 +869,12 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + kc->info = snd_sof_volume_info; + + scontrol->comp_id = sdev->next_comp_id; +- scontrol->min_volume_step = le32_to_cpu(mc->min); +- scontrol->max_volume_step = le32_to_cpu(mc->max); ++ scontrol->min_volume_step = min; ++ scontrol->max_volume_step = max; + scontrol->num_channels = le32_to_cpu(mc->num_channels); + +- scontrol->max = le32_to_cpu(mc->max); +- if (le32_to_cpu(mc->max) == 1) ++ scontrol->max = max; ++ if (max == 1) + goto skip; + + /* extract tlv data */ +@@ -878,7 +884,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + } + + /* set up volume table */ +- ret = set_up_volume_table(scontrol, tlv, le32_to_cpu(mc->max) + 1); ++ ret = set_up_volume_table(scontrol, tlv, max + 1); + if (ret < 0) { + dev_err(scomp->dev, "error: setting up volume table\n"); + return ret; +@@ -911,7 +917,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + return 0; + + err: +- if (le32_to_cpu(mc->max) > 1) ++ if (max > 1) + kfree(scontrol->volume_table); + + return ret; +@@ -1102,7 +1108,7 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp, + + full = NULL; + partial = NULL; +- list_for_each_entry(rtd, &card->rtd_list, list) { ++ for_each_card_rtds(card, rtd) { + /* does stream match DAI link ? */ + if (rtd->dai_link->stream_name) { + if (!strcmp(rtd->dai_link->stream_name, w->sname)) { +@@ -1167,7 +1173,7 @@ static void sof_disconnect_dai_widget(struct snd_soc_component *scomp, + else + return; + +- list_for_each_entry(rtd, &card->rtd_list, list) { ++ for_each_card_rtds(card, rtd) { + /* does stream match DAI link ? */ + if (!rtd->dai_link->stream_name || + !strstr(rtd->dai_link->stream_name, sname)) +@@ -1943,8 +1949,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + private->array, le32_to_cpu(private->size)); + if (ret < 0) { + dev_err(scomp->dev, "Failed tp parse common DAI link tokens\n"); +- kfree(slink); +- return ret; ++ goto free_slink; + } + + token_list = tplg_ops ? tplg_ops->token_list : NULL; +@@ -1995,6 +2000,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + case SOF_DAI_AMD_HS: + case SOF_DAI_AMD_SP_VIRTUAL: + case SOF_DAI_AMD_HS_VIRTUAL: ++ case SOF_DAI_AMD_I2S: + token_id = SOF_ACPI2S_TOKENS; + num_tuples += token_list[SOF_ACPI2S_TOKENS].count; + break; +@@ -2013,8 +2019,8 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + /* allocate memory for tuples array */ + slink->tuples = kzalloc_objs(*slink->tuples, num_tuples); + if (!slink->tuples) { +- kfree(slink); +- return -ENOMEM; ++ ret = -ENOMEM; ++ goto free_slink; + } + + if (token_list[SOF_DAI_LINK_TOKENS].tokens) { +@@ -2070,6 +2076,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + + err: + kfree(slink->tuples); ++free_slink: + kfree(slink); + + return ret; +@@ -2505,13 +2512,12 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_pdata *sof_pdata = sdev->pdata; + const char *tplg_filename_prefix = sof_pdata->tplg_filename_prefix; +- const struct firmware *fw; +- const char **tplg_files; + int tplg_cnt = 0; + int ret; + int i; + +- tplg_files = kcalloc(scomp->card->num_links, sizeof(char *), GFP_KERNEL); ++ const char **tplg_files __free(kfree) = ++ kcalloc(scomp->card->num_links, sizeof(char *), GFP_KERNEL); + if (!tplg_files) + return -ENOMEM; + +@@ -2537,10 +2543,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + tplg_filename_prefix, + &tplg_files, + no_fallback); +- if (tplg_cnt < 0) { +- kfree(tplg_files); ++ if (tplg_cnt < 0) + return tplg_cnt; +- } + } + + /* +@@ -2551,8 +2555,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + if (strstr(file, "dummy")) { + dev_err(scomp->dev, + "Function topology is required, please upgrade sof-firmware\n"); +- +- kfree(tplg_files); + return -EINVAL; + } + tplg_files[0] = file; +@@ -2567,6 +2569,7 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + if (tplg_files[0] != file) + dev_info(scomp->dev, "loading topology %d: %s\n", i, tplg_files[i]); + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, tplg_files[i], scomp->dev); + if (ret < 0) { + /* +@@ -2585,8 +2588,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + else + ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); + +- release_firmware(fw); +- + if (ret < 0) { + dev_err(scomp->dev, "tplg %s component load failed %d\n", + tplg_files[i], ret); +@@ -2605,6 +2606,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + goto out; + } + dev_info(scomp->dev, "loading feature topology %d: %s\n", i, feature_topology); ++ ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, feature_topology, scomp->dev); + if (ret < 0) { + /* +@@ -2629,8 +2632,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + else + ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); + +- release_firmware(fw); +- + if (ret < 0) { + dev_err(scomp->dev, "feature tplg %s component load failed %d\n", + feature_topologies[i], ret); +@@ -2649,8 +2650,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + if (ret >= 0 && sdev->led_present) + ret = snd_ctl_led_request(); + +- kfree(tplg_files); +- + return ret; + } + EXPORT_SYMBOL(snd_sof_load_topology); +diff --git a/sound/soc/sophgo/cv1800b-tdm.c b/sound/soc/sophgo/cv1800b-tdm.c +--- a/sound/soc/sophgo/cv1800b-tdm.c ++++ b/sound/soc/sophgo/cv1800b-tdm.c +@@ -558,7 +558,7 @@ static const struct snd_soc_dai_ops cv1800b_i2s_dai_ops = { + .set_sysclk = cv1800b_i2s_dai_set_sysclk, + }; + +-static const struct snd_soc_dai_driver cv1800b_i2s_dai_template = { ++static struct snd_soc_dai_driver cv1800b_i2s_dai_template = { + .name = "cv1800b-i2s", + .playback = { + .stream_name = "Playback", +@@ -636,7 +636,6 @@ static int cv1800b_i2s_probe(struct platform_device *pdev) + struct cv1800b_i2s *i2s; + struct resource *res; + void __iomem *regs; +- struct snd_soc_dai_driver *dai; + int ret; + + i2s = devm_kzalloc(dev, sizeof(*i2s), GFP_KERNEL); +@@ -666,21 +665,14 @@ static int cv1800b_i2s_probe(struct platform_device *pdev) + platform_set_drvdata(pdev, i2s); + cv1800b_i2s_setup_tdm(i2s); + +- dai = devm_kmemdup(dev, &cv1800b_i2s_dai_template, sizeof(*dai), +- GFP_KERNEL); +- if (!dai) +- return -ENOMEM; +- +- ret = devm_snd_soc_register_component(dev, &cv1800b_i2s_component, dai, +- 1); ++ ret = devm_snd_soc_register_component(dev, &cv1800b_i2s_component, ++ &cv1800b_i2s_dai_template, 1); + if (ret) + return ret; + + ret = devm_snd_dmaengine_pcm_register(dev, &cv1800b_i2s_pcm_config, 0); +- if (ret) { +- dev_err(dev, "dmaengine_pcm_register failed: %d\n", ret); ++ if (ret) + return ret; +- } + + return 0; + } +diff --git a/sound/soc/spacemit/k1_i2s.c b/sound/soc/spacemit/k1_i2s.c +--- a/sound/soc/spacemit/k1_i2s.c ++++ b/sound/soc/spacemit/k1_i2s.c +@@ -52,7 +52,7 @@ struct spacemit_i2s_dev { + + struct clk *sysclk; + struct clk *bclk; +- struct clk *sspa_clk; ++ struct clk *func_clk; + struct clk *sysclk_div; + struct clk *c_sysclk; + struct clk *c_bclk; +@@ -221,7 +221,7 @@ static int spacemit_i2s_hw_params(struct snd_pcm_substream *substream, + if (ret) + return ret; + +- return clk_set_rate(i2s->sspa_clk, bclk_rate); ++ return clk_set_rate(i2s->func_clk, bclk_rate); + } + + static int spacemit_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, +@@ -335,6 +335,11 @@ static int spacemit_i2s_dai_remove(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 spacemit_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B; ++ + static const struct snd_soc_dai_ops spacemit_i2s_dai_ops = { + .probe = spacemit_i2s_dai_probe, + .remove = spacemit_i2s_dai_remove, +@@ -343,26 +348,12 @@ static const struct snd_soc_dai_ops spacemit_i2s_dai_ops = { + .set_sysclk = spacemit_i2s_set_sysclk, + .set_fmt = spacemit_i2s_set_fmt, + .trigger = spacemit_i2s_trigger, ++ .auto_selectable_formats = &spacemit_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver spacemit_i2s_dai = { + .ops = &spacemit_i2s_dai_ops, +- .playback = { +- .channels_min = 1, +- .channels_max = 2, +- .rates = SPACEMIT_PCM_RATES, +- .rate_min = SNDRV_PCM_RATE_8000, +- .rate_max = SNDRV_PCM_RATE_48000, +- .formats = SPACEMIT_PCM_FORMATS, +- }, +- .capture = { +- .channels_min = 1, +- .channels_max = 2, +- .rates = SPACEMIT_PCM_RATES, +- .rate_min = SNDRV_PCM_RATE_8000, +- .rate_max = SNDRV_PCM_RATE_48000, +- .formats = SPACEMIT_PCM_FORMATS, +- }, + .symmetric_rate = 1, + }; + +@@ -392,6 +383,8 @@ static int spacemit_i2s_init_dai(struct spacemit_i2s_dev *i2s, + dai->playback.channels_min = 1; + dai->playback.channels_max = 2; + dai->playback.rates = SPACEMIT_PCM_RATES; ++ dai->playback.rate_min = SNDRV_PCM_RATE_8000; ++ dai->playback.rate_max = SNDRV_PCM_RATE_48000; + dai->playback.formats = SPACEMIT_PCM_FORMATS; + + i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; +@@ -404,6 +397,8 @@ static int spacemit_i2s_init_dai(struct spacemit_i2s_dev *i2s, + dai->capture.channels_min = 1; + dai->capture.channels_max = 2; + dai->capture.rates = SPACEMIT_PCM_RATES; ++ dai->capture.rate_min = SNDRV_PCM_RATE_8000; ++ dai->capture.rate_max = SNDRV_PCM_RATE_48000; + dai->capture.formats = SPACEMIT_PCM_FORMATS; + + i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; +@@ -445,14 +440,14 @@ static int spacemit_i2s_probe(struct platform_device *pdev) + if (IS_ERR(i2s->bclk)) + return dev_err_probe(i2s->dev, PTR_ERR(i2s->bclk), "failed to enable bit clock\n"); + +- clk = devm_clk_get_enabled(i2s->dev, "sspa_bus"); ++ clk = devm_clk_get_enabled(i2s->dev, "bus"); + if (IS_ERR(clk)) +- return dev_err_probe(i2s->dev, PTR_ERR(clk), "failed to enable sspa_bus clock\n"); ++ return dev_err_probe(i2s->dev, PTR_ERR(clk), "failed to enable bus clock\n"); + +- i2s->sspa_clk = devm_clk_get_enabled(i2s->dev, "sspa"); +- if (IS_ERR(i2s->sspa_clk)) +- return dev_err_probe(i2s->dev, PTR_ERR(i2s->sspa_clk), +- "failed to enable sspa clock\n"); ++ i2s->func_clk = devm_clk_get_enabled(i2s->dev, "func"); ++ if (IS_ERR(i2s->func_clk)) ++ return dev_err_probe(i2s->dev, PTR_ERR(i2s->func_clk), ++ "failed to enable func clock\n"); + + i2s->sysclk_div = devm_clk_get_optional_enabled(i2s->dev, "sysclk_div"); + if (IS_ERR(i2s->sysclk_div)) +@@ -471,7 +466,7 @@ static int spacemit_i2s_probe(struct platform_device *pdev) + + i2s->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(i2s->base)) +- return dev_err_probe(i2s->dev, PTR_ERR(i2s->base), "failed to map registers\n"); ++ return PTR_ERR(i2s->base); + + i2s->reset = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(i2s->reset)) +@@ -488,7 +483,7 @@ static int spacemit_i2s_probe(struct platform_device *pdev) + &spacemit_i2s_component, + dai, 1); + if (ret) +- return dev_err_probe(i2s->dev, ret, "failed to register component"); ++ return ret; + + return devm_snd_dmaengine_pcm_register(&pdev->dev, &spacemit_dmaengine_pcm_config, 0); + } +diff --git a/sound/soc/spear/spdif_in.c b/sound/soc/spear/spdif_in.c +--- a/sound/soc/spear/spdif_in.c ++++ b/sound/soc/spear/spdif_in.c +@@ -55,7 +55,7 @@ static int spdif_in_dai_probe(struct snd_soc_dai *dai) + struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); + + host->dma_params_rx.filter_data = &host->dma_params; +- dai->capture_dma_data = &host->dma_params_rx; ++ snd_soc_dai_dma_data_set_capture(dai, &host->dma_params_rx); + + return 0; + } +@@ -150,12 +150,12 @@ static int spdif_in_trigger(struct snd_pcm_substream *substream, int cmd, + + static const struct snd_soc_dai_ops spdif_in_dai_ops = { + .shutdown = spdif_in_shutdown, ++ .probe = spdif_in_dai_probe, + .trigger = spdif_in_trigger, + .hw_params = spdif_in_hw_params, + }; + + static struct snd_soc_dai_driver spdif_in_dai = { +- .probe = spdif_in_dai_probe, + .capture = { + .channels_min = 2, + .channels_max = 2, +@@ -218,10 +218,8 @@ static int spdif_in_probe(struct platform_device *pdev) + + host->io_base = io_base; + host->irq = platform_get_irq(pdev, 0); +- if (host->irq < 0) { +- dev_warn(&pdev->dev, "failed to get IRQ: %d\n", host->irq); ++ if (host->irq < 0) + return host->irq; +- } + + host->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) +@@ -243,10 +241,8 @@ static int spdif_in_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, host->irq, spdif_in_irq, 0, + "spdif-in", host); +- if (ret) { +- dev_warn(&pdev->dev, "request_irq failed\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_soc_register_component(&pdev->dev, &spdif_in_component, + &spdif_in_dai, 1); +diff --git a/sound/soc/spear/spdif_out.c b/sound/soc/spear/spdif_out.c +--- a/sound/soc/spear/spdif_out.c ++++ b/sound/soc/spear/spdif_out.c +@@ -249,6 +249,7 @@ static int spdif_soc_dai_probe(struct snd_soc_dai *dai) + } + + static const struct snd_soc_dai_ops spdif_out_dai_ops = { ++ .probe = spdif_soc_dai_probe, + .mute_stream = spdif_mute, + .startup = spdif_out_startup, + .shutdown = spdif_out_shutdown, +@@ -266,7 +267,6 @@ static struct snd_soc_dai_driver spdif_out_dai = { + SNDRV_PCM_RATE_192000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +- .probe = spdif_soc_dai_probe, + .ops = &spdif_out_dai_ops, + }; + +diff --git a/sound/soc/sprd/sprd-mcdt.c b/sound/soc/sprd/sprd-mcdt.c +--- a/sound/soc/sprd/sprd-mcdt.c ++++ b/sound/soc/sprd/sprd-mcdt.c +@@ -931,10 +931,8 @@ static int sprd_mcdt_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, irq, sprd_mcdt_irq_handler, + 0, "sprd-mcdt", mcdt); +- if (ret) { +- dev_err(&pdev->dev, "Failed to request MCDT IRQ\n"); ++ if (ret) + return ret; +- } + + sprd_mcdt_init_chans(mcdt, res); + +diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c +--- a/sound/soc/sprd/sprd-pcm-dma.c ++++ b/sound/soc/sprd/sprd-pcm-dma.c +@@ -469,8 +469,6 @@ static int sprd_soc_platform_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, &sprd_soc_component, + NULL, 0); +- if (ret) +- dev_err(&pdev->dev, "could not register platform:%d\n", ret); + + return ret; + } +diff --git a/sound/soc/starfive/jh7110_pwmdac.c b/sound/soc/starfive/jh7110_pwmdac.c +--- a/sound/soc/starfive/jh7110_pwmdac.c ++++ b/sound/soc/starfive/jh7110_pwmdac.c +@@ -477,25 +477,22 @@ static int jh7110_pwmdac_probe(struct platform_device *pdev) + &jh7110_pwmdac_component, + &jh7110_pwmdac_dai, 1); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "failed to register dai\n"); ++ return ret; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "failed to register pcm\n"); ++ return ret; + + pm_runtime_enable(dev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = jh7110_pwmdac_runtime_resume(&pdev->dev); +- if (ret) +- goto err_pm_disable; ++ if (ret) { ++ pm_runtime_disable(&pdev->dev); ++ return ret; ++ } + } + + return 0; +- +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +- +- return ret; + } + + static void jh7110_pwmdac_remove(struct platform_device *pdev) +diff --git a/sound/soc/starfive/jh7110_tdm.c b/sound/soc/starfive/jh7110_tdm.c +--- a/sound/soc/starfive/jh7110_tdm.c ++++ b/sound/soc/starfive/jh7110_tdm.c +@@ -559,10 +559,8 @@ static int jh7110_tdm_clk_reset_get(struct platform_device *pdev, + tdm->clks[5].id = "tdm"; + + ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(tdm->clks), tdm->clks); +- if (ret) { +- dev_err(&pdev->dev, "Failed to get tdm clocks\n"); ++ if (ret) + return ret; +- } + + tdm->resets = devm_reset_control_array_get_exclusive(&pdev->dev); + if (IS_ERR(tdm->resets)) { +@@ -589,42 +587,33 @@ static int jh7110_tdm_probe(struct platform_device *pdev) + tdm->dev = &pdev->dev; + + ret = jh7110_tdm_clk_reset_get(pdev, tdm); +- if (ret) { +- dev_err(&pdev->dev, "Failed to enable audio-tdm clock\n"); ++ if (ret) + return ret; +- } + + jh7110_tdm_init_params(tdm); + + dev_set_drvdata(&pdev->dev, tdm); + ret = devm_snd_soc_register_component(&pdev->dev, &jh7110_tdm_component, + &jh7110_tdm_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register dai\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, + &jh7110_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_COMPAT); +- if (ret) { +- dev_err(&pdev->dev, "Could not register pcm: %d\n", ret); ++ if (ret) + return ret; +- } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = jh7110_tdm_runtime_resume(&pdev->dev); +- if (ret) +- goto err_pm_disable; ++ if (ret) { ++ pm_runtime_disable(&pdev->dev); ++ return ret; ++ } + } + + return 0; +- +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +- +- return ret; + } + + static void jh7110_tdm_dev_remove(struct platform_device *pdev) +diff --git a/sound/soc/stm/stm32_adfsdm.c b/sound/soc/stm/stm32_adfsdm.c +--- a/sound/soc/stm/stm32_adfsdm.c ++++ b/sound/soc/stm/stm32_adfsdm.c +@@ -360,11 +360,8 @@ static int stm32_adfsdm_probe(struct platform_device *pdev) + ret = devm_snd_soc_register_component(&pdev->dev, + &stm32_adfsdm_soc_platform, + NULL, 0); +- if (ret < 0) { +- dev_err(&pdev->dev, "%s: Failed to register PCM platform\n", +- __func__); ++ if (ret < 0) + return ret; +- } + + pm_runtime_enable(&pdev->dev); + +diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c +--- a/sound/soc/stm/stm32_i2s.c ++++ b/sound/soc/stm/stm32_i2s.c +@@ -1238,10 +1238,8 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev, + + ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, 0, + dev_name(&pdev->dev), i2s); +- if (ret) { +- dev_err(&pdev->dev, "irq request returned %d\n", ret); ++ if (ret) + return ret; +- } + + /* Reset */ + rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); +@@ -1295,7 +1293,7 @@ static int stm32_i2s_probe(struct platform_device *pdev) + + ret = snd_dmaengine_pcm_register(&pdev->dev, &stm32_i2s_pcm_config, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "PCM DMA register error\n"); ++ return ret; + + ret = snd_soc_register_component(&pdev->dev, &stm32_i2s_component, + i2s->dai_drv, 1); +diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c +--- a/sound/soc/stm/stm32_sai_sub.c ++++ b/sound/soc/stm/stm32_sai_sub.c +@@ -1696,19 +1696,15 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, sai->pdata->irq, stm32_sai_isr, + IRQF_SHARED, dev_name(&pdev->dev), sai); +- if (ret) { +- dev_err(&pdev->dev, "IRQ request returned %d\n", ret); ++ if (ret) + goto err_unprepare_pclk; +- } + + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) + conf = &stm32_sai_pcm_config_spdif; + + ret = snd_dmaengine_pcm_register(&pdev->dev, conf, 0); +- if (ret) { +- ret = dev_err_probe(&pdev->dev, ret, "Could not register pcm dma\n"); ++ if (ret) + goto err_unprepare_pclk; +- } + + ret = snd_soc_register_component(&pdev->dev, &stm32_component, + &sai->cpu_dai_drv, 1); +diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c +--- a/sound/soc/stm/stm32_spdifrx.c ++++ b/sound/soc/stm/stm32_spdifrx.c +@@ -970,10 +970,8 @@ static int stm32_spdifrx_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, spdifrx->irq, stm32_spdifrx_isr, 0, + dev_name(&pdev->dev), spdifrx); +- if (ret) { +- dev_err(&pdev->dev, "IRQ request returned %d\n", ret); ++ if (ret) + return ret; +- } + + rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); + if (IS_ERR(rst)) +@@ -987,7 +985,7 @@ static int stm32_spdifrx_probe(struct platform_device *pdev) + pcm_config = &stm32_spdifrx_pcm_config; + ret = snd_dmaengine_pcm_register(&pdev->dev, pcm_config, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "PCM DMA register error\n"); ++ return ret; + + ret = snd_soc_register_component(&pdev->dev, + &stm32_spdifrx_component, +diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c +--- a/sound/soc/sunxi/sun4i-codec.c ++++ b/sound/soc/sunxi/sun4i-codec.c +@@ -22,13 +22,13 @@ + #include + + #include ++#include ++#include + #include + #include + #include + #include + #include +-#include +-#include + + /* Codec DAC digital controls and FIFO registers */ + #define SUN4I_CODEC_DAC_DPC (0x00) +@@ -2317,67 +2317,54 @@ static int sun4i_codec_probe(struct platform_device *pdev) + + scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base, + quirks->regmap_config); +- if (IS_ERR(scodec->regmap)) { +- dev_err(&pdev->dev, "Failed to create our regmap\n"); +- return PTR_ERR(scodec->regmap); +- } ++ if (IS_ERR(scodec->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->regmap), ++ "Failed to create our regmap\n"); + + /* Get the clocks from the DT */ + scodec->clk_apb = devm_clk_get_enabled(&pdev->dev, "apb"); +- if (IS_ERR(scodec->clk_apb)) { +- dev_err(&pdev->dev, "Failed to get the APB clock\n"); +- return PTR_ERR(scodec->clk_apb); +- } ++ if (IS_ERR(scodec->clk_apb)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->clk_apb), ++ "Failed to get the APB clock\n"); + + scodec->clk_module = devm_clk_get(&pdev->dev, "codec"); +- if (IS_ERR(scodec->clk_module)) { +- dev_err(&pdev->dev, "Failed to get the module clock\n"); +- return PTR_ERR(scodec->clk_module); +- } ++ if (IS_ERR(scodec->clk_module)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->clk_module), ++ "Failed to get the module clock\n"); + + if (quirks->has_reset) { + scodec->rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); +- if (IS_ERR(scodec->rst)) { +- dev_err(&pdev->dev, "Failed to get reset control\n"); +- return PTR_ERR(scodec->rst); +- } ++ if (IS_ERR(scodec->rst)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->rst), ++ "Failed to get reset control\n"); + } + + scodec->gpio_pa = devm_gpiod_get_optional(&pdev->dev, "allwinner,pa", + GPIOD_OUT_LOW); +- if (IS_ERR(scodec->gpio_pa)) { +- ret = PTR_ERR(scodec->gpio_pa); +- dev_err_probe(&pdev->dev, ret, "Failed to get pa gpio\n"); +- return ret; +- } ++ if (IS_ERR(scodec->gpio_pa)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->gpio_pa), ++ "Failed to get pa gpio\n"); ++ + + scodec->gpio_hp = devm_gpiod_get_optional(&pdev->dev, "hp-det", GPIOD_IN); +- if (IS_ERR(scodec->gpio_hp)) { +- ret = PTR_ERR(scodec->gpio_hp); +- dev_err_probe(&pdev->dev, ret, "Failed to get hp-det gpio\n"); +- return ret; +- } ++ if (IS_ERR(scodec->gpio_hp)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->gpio_hp), ++ "Failed to get hp-det gpio\n"); + + /* reg_field setup */ + scodec->reg_adc_fifoc = devm_regmap_field_alloc(&pdev->dev, + scodec->regmap, + quirks->reg_adc_fifoc); +- if (IS_ERR(scodec->reg_adc_fifoc)) { +- ret = PTR_ERR(scodec->reg_adc_fifoc); +- dev_err(&pdev->dev, "Failed to create regmap fields: %d\n", +- ret); +- return ret; +- } ++ if (IS_ERR(scodec->reg_adc_fifoc)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->reg_adc_fifoc), ++ "Failed to create regmap fields\n"); + + scodec->reg_dac_fifoc = devm_regmap_field_alloc(&pdev->dev, + scodec->regmap, + quirks->reg_dac_fifoc); +- if (IS_ERR(scodec->reg_dac_fifoc)) { +- ret = PTR_ERR(scodec->reg_dac_fifoc); +- dev_err(&pdev->dev, "Failed to create regmap fields: %d\n", +- ret); +- return ret; +- } ++ if (IS_ERR(scodec->reg_dac_fifoc)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->reg_dac_fifoc), ++ "Failed to create regmap fields\n"); + + /* DMA configuration for TX FIFO */ + scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata; +@@ -2394,39 +2381,29 @@ static int sun4i_codec_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, quirks->codec, + &sun4i_codec_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register our codec\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_soc_register_component(&pdev->dev, + &sun4i_codec_component, + &dummy_cpu_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register our DAI\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register against DMAEngine\n"); ++ if (ret) + return ret; +- } + + card = quirks->create_card(&pdev->dev); +- if (IS_ERR(card)) { +- ret = PTR_ERR(card); +- dev_err(&pdev->dev, "Failed to create our card\n"); +- return ret; +- } ++ if (IS_ERR(card)) ++ return PTR_ERR(card); + + snd_soc_card_set_drvdata(card, scodec); + + ret = snd_soc_register_card(card); +- if (ret) { +- dev_err_probe(&pdev->dev, ret, "Failed to register our card\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "Failed to register our card\n"); + + return 0; + } +diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c +--- a/sound/soc/sunxi/sun4i-i2s.c ++++ b/sound/soc/sunxi/sun4i-i2s.c +@@ -1550,30 +1550,26 @@ static int sun4i_i2s_probe(struct platform_device *pdev) + } + + i2s->bus_clk = devm_clk_get(&pdev->dev, "apb"); +- if (IS_ERR(i2s->bus_clk)) { +- dev_err(&pdev->dev, "Can't get our bus clock\n"); +- return PTR_ERR(i2s->bus_clk); +- } ++ if (IS_ERR(i2s->bus_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->bus_clk), ++ "Can't get our bus clock\n"); + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + i2s->variant->sun4i_i2s_regmap); +- if (IS_ERR(i2s->regmap)) { +- dev_err(&pdev->dev, "Regmap initialisation failed\n"); +- return PTR_ERR(i2s->regmap); +- } ++ if (IS_ERR(i2s->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->regmap), ++ "Regmap initialisation failed\n"); + + i2s->mod_clk = devm_clk_get(&pdev->dev, "mod"); +- if (IS_ERR(i2s->mod_clk)) { +- dev_err(&pdev->dev, "Can't get our mod clock\n"); +- return PTR_ERR(i2s->mod_clk); +- } ++ if (IS_ERR(i2s->mod_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->mod_clk), ++ "Can't get our mod clock\n"); + + if (i2s->variant->has_reset) { + i2s->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); +- if (IS_ERR(i2s->rst)) { +- dev_err(&pdev->dev, "Failed to get reset control\n"); +- return PTR_ERR(i2s->rst); +- } ++ if (IS_ERR(i2s->rst)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->rst), ++ "Failed to get reset control\n"); + } + + if (!IS_ERR(i2s->rst)) { +diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c +--- a/sound/soc/sunxi/sun4i-spdif.c ++++ b/sound/soc/sunxi/sun4i-spdif.c +@@ -684,26 +684,23 @@ static int sun4i_spdif_probe(struct platform_device *pdev) + + host->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &sun4i_spdif_regmap_config); +- if (IS_ERR(host->regmap)) { +- dev_err(&pdev->dev, "failed to initialise regmap.\n"); +- return PTR_ERR(host->regmap); +- } ++ if (IS_ERR(host->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(host->regmap), ++ "failed to initialise regmap.\n"); + + /* Clocks */ + host->apb_clk = devm_clk_get(&pdev->dev, "apb"); +- if (IS_ERR(host->apb_clk)) { +- dev_err(&pdev->dev, "failed to get a apb clock.\n"); +- return PTR_ERR(host->apb_clk); +- } ++ if (IS_ERR(host->apb_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(host->apb_clk), ++ "failed to get a apb clock.\n"); + + if (quirks->tx_clk_name) + tx_clk_name = quirks->tx_clk_name; + host->spdif_clk = devm_clk_get(&pdev->dev, tx_clk_name); +- if (IS_ERR(host->spdif_clk)) { +- dev_err(&pdev->dev, "failed to get the \"%s\" clock.\n", +- tx_clk_name); +- return PTR_ERR(host->spdif_clk); +- } ++ if (IS_ERR(host->spdif_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(host->spdif_clk), ++ "failed to get the \"%s\" clock.\n", ++ tx_clk_name); + + host->dma_params_tx.addr = res->start + quirks->reg_dac_txdata; + host->dma_params_tx.maxburst = 8; +diff --git a/sound/soc/sunxi/sun50i-codec-analog.c b/sound/soc/sunxi/sun50i-codec-analog.c +--- a/sound/soc/sunxi/sun50i-codec-analog.c ++++ b/sound/soc/sunxi/sun50i-codec-analog.c +@@ -551,16 +551,13 @@ static int sun50i_codec_analog_probe(struct platform_device *pdev) + bool enable; + + base = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(base)) { +- dev_err(&pdev->dev, "Failed to map the registers\n"); ++ if (IS_ERR(base)) + return PTR_ERR(base); +- } + + regmap = sun8i_adda_pr_regmap_init(&pdev->dev, base); +- if (IS_ERR(regmap)) { +- dev_err(&pdev->dev, "Failed to create regmap\n"); +- return PTR_ERR(regmap); +- } ++ if (IS_ERR(regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(regmap), ++ "Failed to create regmap\n"); + + enable = device_property_read_bool(&pdev->dev, + "allwinner,internal-bias-resistor"); +diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c +--- a/sound/soc/sunxi/sun8i-codec-analog.c ++++ b/sound/soc/sunxi/sun8i-codec-analog.c +@@ -822,16 +822,13 @@ static int sun8i_codec_analog_probe(struct platform_device *pdev) + void __iomem *base; + + base = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(base)) { +- dev_err(&pdev->dev, "Failed to map the registers\n"); ++ if (IS_ERR(base)) + return PTR_ERR(base); +- } + + regmap = sun8i_adda_pr_regmap_init(&pdev->dev, base); +- if (IS_ERR(regmap)) { +- dev_err(&pdev->dev, "Failed to create regmap\n"); +- return PTR_ERR(regmap); +- } ++ if (IS_ERR(regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(regmap), ++ "Failed to create regmap\n"); + + return devm_snd_soc_register_component(&pdev->dev, + &sun8i_codec_analog_cmpnt_drv, +diff --git a/sound/soc/tegra/tegra20_das.c b/sound/soc/tegra/tegra20_das.c +--- a/sound/soc/tegra/tegra20_das.c ++++ b/sound/soc/tegra/tegra20_das.c +@@ -167,10 +167,9 @@ static int tegra20_das_probe(struct platform_device *pdev) + + das->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra20_das_regmap_config); +- if (IS_ERR(das->regmap)) { +- dev_err(&pdev->dev, "regmap init failed\n"); +- return PTR_ERR(das->regmap); +- } ++ if (IS_ERR(das->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(das->regmap), ++ "regmap init failed\n"); + + tegra20_das_connect_dap_to_dac(das, TEGRA20_DAS_DAP_ID_1, + TEGRA20_DAS_DAP_SEL_DAC1); +diff --git a/sound/soc/tegra/tegra210_adx.c b/sound/soc/tegra/tegra210_adx.c +--- a/sound/soc/tegra/tegra210_adx.c ++++ b/sound/soc/tegra/tegra210_adx.c +@@ -496,16 +496,12 @@ static struct snd_kcontrol_new tegra264_adx_controls[] = { + static int tegra210_adx_component_probe(struct snd_soc_component *component) + { + struct tegra210_adx *adx = snd_soc_component_get_drvdata(component); +- int err = 0; + +- if (adx->soc_data->num_controls) { +- err = snd_soc_add_component_controls(component, adx->soc_data->controls, +- adx->soc_data->num_controls); +- if (err) +- dev_err(component->dev, "can't add ADX controls, err: %d\n", err); +- } ++ if (adx->soc_data->num_controls) ++ return snd_soc_add_component_controls(component, adx->soc_data->controls, ++ adx->soc_data->num_controls); + +- return err; ++ return 0; + } + + static const struct snd_soc_component_driver tegra210_adx_cmpnt = { +diff --git a/sound/soc/tegra/tegra210_amx.c b/sound/soc/tegra/tegra210_amx.c +--- a/sound/soc/tegra/tegra210_amx.c ++++ b/sound/soc/tegra/tegra210_amx.c +@@ -504,16 +504,12 @@ static struct snd_kcontrol_new tegra264_amx_controls[] = { + static int tegra210_amx_component_probe(struct snd_soc_component *component) + { + struct tegra210_amx *amx = snd_soc_component_get_drvdata(component); +- int err = 0; + +- if (amx->soc_data->num_controls) { +- err = snd_soc_add_component_controls(component, amx->soc_data->controls, +- amx->soc_data->num_controls); +- if (err) +- dev_err(component->dev, "can't add AMX controls, err: %d\n", err); +- } ++ if (amx->soc_data->num_controls) ++ return snd_soc_add_component_controls(component, amx->soc_data->controls, ++ amx->soc_data->num_controls); + +- return err; ++ return 0; + } + + static const struct snd_soc_component_driver tegra210_amx_cmpnt = { +diff --git a/sound/soc/tegra/tegra210_i2s.c b/sound/soc/tegra/tegra210_i2s.c +--- a/sound/soc/tegra/tegra210_i2s.c ++++ b/sound/soc/tegra/tegra210_i2s.c +@@ -1090,13 +1090,13 @@ static int tegra210_i2s_probe(struct platform_device *pdev) + "can't retrieve I2S bit clock\n"); + + /* +- * Not an error, as this clock is needed only when some other I/O +- * requires input clock from current I2S instance, which is +- * configurable from DT. ++ * This clock is optional and is only needed when another I/O uses ++ * the current I2S instance as its input clock, as configured in DT. + */ +- i2s->clk_sync_input = devm_clk_get(dev, "sync_input"); ++ i2s->clk_sync_input = devm_clk_get_optional(dev, "sync_input"); + if (IS_ERR(i2s->clk_sync_input)) +- dev_dbg(dev, "can't retrieve I2S sync input clock\n"); ++ return dev_err_probe(dev, PTR_ERR(i2s->clk_sync_input), ++ "can't retrieve I2S sync input clock\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) +diff --git a/sound/soc/ti/ams-delta.c b/sound/soc/ti/ams-delta.c +--- a/sound/soc/ti/ams-delta.c ++++ b/sound/soc/ti/ams-delta.c +@@ -562,21 +562,21 @@ static int ams_delta_probe(struct platform_device *pdev) + + card->dev = &pdev->dev; + +- handset_mute = devm_gpiod_get(card->dev, "handset_mute", ++ handset_mute = devm_gpiod_get(&pdev->dev, "handset_mute", + GPIOD_OUT_HIGH); + if (IS_ERR(handset_mute)) + return PTR_ERR(handset_mute); + +- handsfree_mute = devm_gpiod_get(card->dev, "handsfree_mute", ++ handsfree_mute = devm_gpiod_get(&pdev->dev, "handsfree_mute", + GPIOD_OUT_HIGH); + if (IS_ERR(handsfree_mute)) + return PTR_ERR(handsfree_mute); + + ret = snd_soc_register_card(card); + if (ret) { +- dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + card->dev = NULL; +- return ret; ++ return dev_err_probe(&pdev->dev, ret, ++ "snd_soc_register_card() failed\n"); + } + return 0; + } +diff --git a/sound/soc/ti/davinci-evm.c b/sound/soc/ti/davinci-evm.c +--- a/sound/soc/ti/davinci-evm.c ++++ b/sound/soc/ti/davinci-evm.c +@@ -245,9 +245,8 @@ static int davinci_evm_probe(struct platform_device *pdev) + + snd_soc_card_set_drvdata(&evm_soc_card, drvdata); + ret = devm_snd_soc_register_card(&pdev->dev, &evm_soc_card); +- + if (ret) { +- dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n"); + goto err_put; + } + +diff --git a/sound/soc/ti/j721e-evm.c b/sound/soc/ti/j721e-evm.c +--- a/sound/soc/ti/j721e-evm.c ++++ b/sound/soc/ti/j721e-evm.c +@@ -4,6 +4,7 @@ + * Author: Peter Ujfalusi + */ + ++#include + #include + #include + #include +@@ -263,7 +264,7 @@ static int j721e_audio_startup(struct snd_pcm_substream *substream) + int ret = 0; + int i; + +- guard(mutex)(&priv->mutex); ++ mutex_lock(&priv->mutex); + + domain->active++; + +@@ -303,6 +304,7 @@ static int j721e_audio_startup(struct snd_pcm_substream *substream) + out: + if (ret) + domain->active--; ++ mutex_unlock(&priv->mutex); + + return ret; + } +@@ -868,10 +870,9 @@ static int j721e_soc_probe(struct platform_device *pdev) + card->num_dapm_routes = ARRAY_SIZE(j721e_cpb_dapm_routes); + card->fully_routed = 1; + +- if (snd_soc_of_parse_card_name(card, "model")) { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } ++ ret = snd_soc_of_parse_card_name(card, "model"); ++ if (ret) ++ return ret; + + link_cnt = 0; + conf_cnt = 0; +diff --git a/sound/soc/ti/omap-abe-twl6040.c b/sound/soc/ti/omap-abe-twl6040.c +--- a/sound/soc/ti/omap-abe-twl6040.c ++++ b/sound/soc/ti/omap-abe-twl6040.c +@@ -234,16 +234,13 @@ static int omap_abe_probe(struct platform_device *pdev) + card->dapm_routes = audio_map; + card->num_dapm_routes = ARRAY_SIZE(audio_map); + +- if (snd_soc_of_parse_card_name(card, "ti,model")) { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } ++ ret = snd_soc_of_parse_card_name(card, "ti,model"); ++ if (ret) ++ return ret; + + ret = snd_soc_of_parse_audio_routing(card, "ti,audio-routing"); +- if (ret) { +- dev_err(&pdev->dev, "Error while parsing DAPM routing\n"); ++ if (ret) + return ret; +- } + + dai_node = of_parse_phandle(node, "ti,mcpdm", 0); + if (!dai_node) { +@@ -299,8 +296,8 @@ static int omap_abe_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) +- dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n", +- ret); ++ dev_err_probe(&pdev->dev, ret, ++ "devm_snd_soc_register_card() failed\n"); + + return ret; + } +diff --git a/sound/soc/ti/omap-dmic.c b/sound/soc/ti/omap-dmic.c +--- a/sound/soc/ti/omap-dmic.c ++++ b/sound/soc/ti/omap-dmic.c +@@ -465,10 +465,9 @@ static int asoc_dmic_probe(struct platform_device *pdev) + mutex_init(&dmic->mutex); + + dmic->fclk = devm_clk_get(dmic->dev, "fck"); +- if (IS_ERR(dmic->fclk)) { +- dev_err(dmic->dev, "can't get fck\n"); +- return -ENODEV; +- } ++ if (IS_ERR(dmic->fclk)) ++ return dev_err_probe(dmic->dev, PTR_ERR(dmic->fclk), ++ "can't get fck\n"); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma"); + if (!res) { +diff --git a/sound/soc/ti/omap-hdmi.c b/sound/soc/ti/omap-hdmi.c +--- a/sound/soc/ti/omap-hdmi.c ++++ b/sound/soc/ti/omap-hdmi.c +@@ -24,8 +24,6 @@ + #define DRV_NAME "omap-hdmi-audio" + + struct hdmi_audio_data { +- struct snd_soc_card *card; +- + const struct omap_hdmi_audio_ops *ops; + struct device *dssdev; + struct snd_dmaengine_dai_dma_data dma_data; +@@ -375,12 +373,9 @@ static int omap_hdmi_audio_probe(struct platform_device *pdev) + card->dev = dev; + + ret = devm_snd_soc_register_card(dev, card); +- if (ret) { +- dev_err(dev, "snd_soc_register_card failed (%d)\n", ret); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "snd_soc_register_card() failed\n"); + +- ad->card = card; + snd_soc_card_set_drvdata(card, ad); + + dev_set_drvdata(dev, ad); +diff --git a/sound/soc/ti/omap-twl4030.c b/sound/soc/ti/omap-twl4030.c +--- a/sound/soc/ti/omap-twl4030.c ++++ b/sound/soc/ti/omap-twl4030.c +@@ -17,7 +17,6 @@ + */ + + #include +-#include + #include + #include + +@@ -29,6 +28,7 @@ + #include "omap-mcbsp.h" + + struct omap_twl4030 { ++ struct snd_soc_jack_gpio hs_jack_gpio; + struct snd_soc_jack hs_jack; + }; + +@@ -124,38 +124,18 @@ static struct snd_soc_jack_pin hs_jack_pins[] = { + }, + }; + +-/* Headset jack detection gpios */ +-static struct snd_soc_jack_gpio hs_jack_gpios[] = { +- { +- .name = "ti,jack-det", +- .report = SND_JACK_HEADSET, +- .debounce_time = 200, +- }, +-}; +- +-static inline void twl4030_disconnect_pin(struct snd_soc_dapm_context *dapm, +- int connected, char *pin) +-{ +- if (!connected) +- snd_soc_dapm_disable_pin(dapm, pin); +-} +- + static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd) + { + struct snd_soc_card *card = rtd->card; +- struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); +- struct omap_tw4030_pdata *pdata = dev_get_platdata(card->dev); ++ + struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card); +- int ret = 0; ++ int ret; + + /* + * This is a bit of a hack, but the GPIO is optional so we + * only want to add the jack detection if the GPIO is there. + */ + if (of_property_present(card->dev->of_node, "ti,jack-det-gpio")) { +- hs_jack_gpios[0].gpiod_dev = card->dev; +- hs_jack_gpios[0].idx = 0; +- + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET, + &priv->hs_jack, hs_jack_pins, +@@ -163,36 +143,19 @@ static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd) + if (ret) + return ret; + +- ret = snd_soc_jack_add_gpios(&priv->hs_jack, +- ARRAY_SIZE(hs_jack_gpios), +- hs_jack_gpios); ++ priv->hs_jack_gpio.name = "ti,jack-det"; ++ priv->hs_jack_gpio.report = SND_JACK_HEADSET; ++ priv->hs_jack_gpio.debounce_time = 200; ++ priv->hs_jack_gpio.gpiod_dev = card->dev; ++ priv->hs_jack_gpio.idx = 0; ++ ++ ret = snd_soc_jack_add_gpios(&priv->hs_jack, 1, ++ &priv->hs_jack_gpio); + if (ret) + return ret; + } + +- /* +- * NULL pdata means we booted with DT. In this case the routing is +- * provided and the card is fully routed, no need to mark pins. +- */ +- if (!pdata || !pdata->custom_routing) +- return ret; +- +- /* Disable not connected paths if not used */ +- twl4030_disconnect_pin(dapm, pdata->has_ear, "Earpiece Spk"); +- twl4030_disconnect_pin(dapm, pdata->has_hf, "Handsfree Spk"); +- twl4030_disconnect_pin(dapm, pdata->has_hs, "Headset Stereophone"); +- twl4030_disconnect_pin(dapm, pdata->has_predriv, "Ext Spk"); +- twl4030_disconnect_pin(dapm, pdata->has_carkit, "Carkit Spk"); +- +- twl4030_disconnect_pin(dapm, pdata->has_mainmic, "Main Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_submic, "Sub Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_hsmic, "Headset Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_carkitmic, "Carkit Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_digimic0, "Digital0 Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_digimic1, "Digital1 Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_linein, "Line In"); +- +- return ret; ++ return 0; + } + + /* Digital audio interface glue - connects codec <--> CPU */ +@@ -237,11 +200,15 @@ static struct snd_soc_card omap_twl4030_card = { + + static int omap_twl4030_probe(struct platform_device *pdev) + { +- struct omap_tw4030_pdata *pdata = dev_get_platdata(&pdev->dev); +- struct device_node *node = pdev->dev.of_node; + struct snd_soc_card *card = &omap_twl4030_card; ++ struct device_node *node, *dai_node; + struct omap_twl4030 *priv; +- int ret = 0; ++ struct property *prop; ++ int ret; ++ ++ node = pdev->dev.of_node; ++ if (!node) ++ return -ENODEV; + + card->dev = &pdev->dev; + +@@ -249,69 +216,52 @@ static int omap_twl4030_probe(struct platform_device *pdev) + if (priv == NULL) + return -ENOMEM; + +- if (node) { +- struct device_node *dai_node; +- struct property *prop; ++ ret = snd_soc_of_parse_card_name(card, "ti,model"); ++ if (ret) ++ return ret; + +- if (snd_soc_of_parse_card_name(card, "ti,model")) { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } +- +- dai_node = of_parse_phandle(node, "ti,mcbsp", 0); +- if (!dai_node) { +- dev_err(&pdev->dev, "McBSP node is not provided\n"); +- return -EINVAL; +- } +- omap_twl4030_dai_links[0].cpus->dai_name = NULL; +- omap_twl4030_dai_links[0].cpus->of_node = dai_node; +- +- omap_twl4030_dai_links[0].platforms->name = NULL; +- omap_twl4030_dai_links[0].platforms->of_node = dai_node; +- +- dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0); +- if (!dai_node) { +- card->num_links = 1; +- } else { +- omap_twl4030_dai_links[1].cpus->dai_name = NULL; +- omap_twl4030_dai_links[1].cpus->of_node = dai_node; +- +- omap_twl4030_dai_links[1].platforms->name = NULL; +- omap_twl4030_dai_links[1].platforms->of_node = dai_node; +- } +- +- /* Optional: audio routing can be provided */ +- prop = of_find_property(node, "ti,audio-routing", NULL); +- if (prop) { +- ret = snd_soc_of_parse_audio_routing(card, +- "ti,audio-routing"); +- if (ret) +- return ret; +- +- card->fully_routed = 1; +- } +- } else if (pdata) { +- if (pdata->card_name) { +- card->name = pdata->card_name; +- } else { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } +- +- if (!pdata->voice_connected) +- card->num_links = 1; +- } else { +- dev_err(&pdev->dev, "Missing pdata\n"); ++ if (!card->name) { ++ dev_err(&pdev->dev, "Card name is not provided\n"); + return -ENODEV; + } + ++ dai_node = of_parse_phandle(node, "ti,mcbsp", 0); ++ if (!dai_node) { ++ dev_err(&pdev->dev, "McBSP node is not provided\n"); ++ return -EINVAL; ++ } ++ omap_twl4030_dai_links[0].cpus->dai_name = NULL; ++ omap_twl4030_dai_links[0].cpus->of_node = dai_node; ++ ++ omap_twl4030_dai_links[0].platforms->name = NULL; ++ omap_twl4030_dai_links[0].platforms->of_node = dai_node; ++ ++ dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0); ++ if (!dai_node) { ++ card->num_links = 1; ++ } else { ++ omap_twl4030_dai_links[1].cpus->dai_name = NULL; ++ omap_twl4030_dai_links[1].cpus->of_node = dai_node; ++ ++ omap_twl4030_dai_links[1].platforms->name = NULL; ++ omap_twl4030_dai_links[1].platforms->of_node = dai_node; ++ } ++ ++ /* Optional: audio routing can be provided */ ++ prop = of_find_property(node, "ti,audio-routing", NULL); ++ if (prop) { ++ ret = snd_soc_of_parse_audio_routing(card, "ti,audio-routing"); ++ if (ret) ++ return ret; ++ ++ card->fully_routed = 1; ++ } ++ + snd_soc_card_set_drvdata(card, priv); + ret = devm_snd_soc_register_card(&pdev->dev, card); +- if (ret) { +- dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n", +- ret); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "devm_snd_soc_register_card() failed\n"); + + return 0; + } +diff --git a/sound/soc/ti/rx51.c b/sound/soc/ti/rx51.c +--- a/sound/soc/ti/rx51.c ++++ b/sound/soc/ti/rx51.c +@@ -358,21 +358,22 @@ static struct snd_soc_card rx51_sound_card = { + static int rx51_soc_probe(struct platform_device *pdev) + { + struct rx51_audio_pdata *pdata; +- struct device_node *np = pdev->dev.of_node; ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node; + struct snd_soc_card *card = &rx51_sound_card; + int err; + + if (!of_machine_is_compatible("nokia,omap3-n900")) + return -ENODEV; + +- card->dev = &pdev->dev; ++ card->dev = dev; + + if (np) { + struct device_node *dai_node; + + dai_node = of_parse_phandle(np, "nokia,cpu-dai", 0); + if (!dai_node) { +- dev_err(card->dev, "McBSP node is not provided\n"); ++ dev_err(dev, "McBSP node is not provided\n"); + return -EINVAL; + } + rx51_dai[0].cpus->dai_name = NULL; +@@ -382,7 +383,7 @@ static int rx51_soc_probe(struct platform_device *pdev) + + dai_node = of_parse_phandle(np, "nokia,audio-codec", 0); + if (!dai_node) { +- dev_err(card->dev, "Codec node is not provided\n"); ++ dev_err(dev, "Codec node is not provided\n"); + return -EINVAL; + } + rx51_dai[0].codecs->name = NULL; +@@ -390,7 +391,7 @@ static int rx51_soc_probe(struct platform_device *pdev) + + dai_node = of_parse_phandle(np, "nokia,audio-codec", 1); + if (!dai_node) { +- dev_err(card->dev, "Auxiliary Codec node is not provided\n"); ++ dev_err(dev, "Auxiliary Codec node is not provided\n"); + return -EINVAL; + } + rx51_aux_dev[0].dlc.name = NULL; +@@ -400,7 +401,7 @@ static int rx51_soc_probe(struct platform_device *pdev) + + dai_node = of_parse_phandle(np, "nokia,headphone-amplifier", 0); + if (!dai_node) { +- dev_err(card->dev, "Headphone amplifier node is not provided\n"); ++ dev_err(dev, "Headphone amplifier node is not provided\n"); + return -EINVAL; + } + rx51_aux_dev[1].dlc.name = NULL; +@@ -409,40 +410,36 @@ static int rx51_soc_probe(struct platform_device *pdev) + rx51_codec_conf[1].dlc.of_node = dai_node; + } + +- pdata = devm_kzalloc(card->dev, sizeof(*pdata), GFP_KERNEL); ++ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (pdata == NULL) + return -ENOMEM; + + snd_soc_card_set_drvdata(card, pdata); + +- pdata->tvout_selection_gpio = devm_gpiod_get(card->dev, ++ pdata->tvout_selection_gpio = devm_gpiod_get(dev, + "tvout-selection", + GPIOD_OUT_LOW); +- if (IS_ERR(pdata->tvout_selection_gpio)) { +- dev_err(card->dev, "could not get tvout selection gpio\n"); +- return PTR_ERR(pdata->tvout_selection_gpio); +- } ++ if (IS_ERR(pdata->tvout_selection_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pdata->tvout_selection_gpio), ++ "could not get tvout selection gpio\n"); + +- pdata->eci_sw_gpio = devm_gpiod_get(card->dev, "eci-switch", ++ pdata->eci_sw_gpio = devm_gpiod_get(dev, "eci-switch", + GPIOD_OUT_HIGH); +- if (IS_ERR(pdata->eci_sw_gpio)) { +- dev_err(card->dev, "could not get eci switch gpio\n"); +- return PTR_ERR(pdata->eci_sw_gpio); +- } ++ if (IS_ERR(pdata->eci_sw_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pdata->eci_sw_gpio), ++ "could not get eci switch gpio\n"); + +- pdata->speaker_amp_gpio = devm_gpiod_get(card->dev, ++ pdata->speaker_amp_gpio = devm_gpiod_get(dev, + "speaker-amplifier", + GPIOD_OUT_LOW); +- if (IS_ERR(pdata->speaker_amp_gpio)) { +- dev_err(card->dev, "could not get speaker enable gpio\n"); +- return PTR_ERR(pdata->speaker_amp_gpio); +- } ++ if (IS_ERR(pdata->speaker_amp_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pdata->speaker_amp_gpio), ++ "could not get speaker enable gpio\n"); + +- err = devm_snd_soc_register_card(card->dev, card); +- if (err) { +- dev_err(card->dev, "snd_soc_register_card failed (%d)\n", err); +- return err; +- } ++ err = devm_snd_soc_register_card(dev, card); ++ if (err) ++ return dev_err_probe(dev, err, ++ "snd_soc_register_card() failed\n"); + + return 0; + } +diff --git a/sound/soc/uniphier/aio-cpu.c b/sound/soc/uniphier/aio-cpu.c +--- a/sound/soc/uniphier/aio-cpu.c ++++ b/sound/soc/uniphier/aio-cpu.c +@@ -795,16 +795,12 @@ int uniphier_aio_probe(struct platform_device *pdev) + ret = devm_snd_soc_register_component(dev, &uniphier_aio_component, + chip->chip_spec->dais, + chip->chip_spec->num_dais); +- if (ret) { +- dev_err(dev, "Register component failed.\n"); ++ if (ret) + goto err_out_reset; +- } + + ret = uniphier_aiodma_soc_register_platform(pdev); +- if (ret) { +- dev_err(dev, "Register platform failed.\n"); ++ if (ret) + goto err_out_reset; +- } + + return 0; + +diff --git a/sound/soc/uniphier/aio.h b/sound/soc/uniphier/aio.h +--- a/sound/soc/uniphier/aio.h ++++ b/sound/soc/uniphier/aio.h +@@ -156,7 +156,7 @@ struct uniphier_aio_selector { + int hw; + }; + +-/** ++/* + * 'SoftWare MAPping' setting of UniPhier AIO registers. + * + * We have to setup 'virtual' register maps to access 'real' registers of AIO. +diff --git a/sound/soc/ux500/mop500.c b/sound/soc/ux500/mop500.c +--- a/sound/soc/ux500/mop500.c ++++ b/sound/soc/ux500/mop500.c +@@ -68,10 +68,11 @@ static void mop500_of_node_put(void) + of_node_put(mop500_dai_links[0].codecs->of_node); + } + +-static int mop500_of_probe(struct platform_device *pdev, +- struct device_node *np) ++static int mop500_of_probe(struct snd_soc_card *card) + { ++ struct device *dev = card->dev; + struct device_node *codec_np, *msp_np[2]; ++ struct device_node *np = dev->of_node; + int i; + + msp_np[0] = of_parse_phandle(np, "stericsson,cpu-dai", 0); +@@ -79,7 +80,7 @@ static int mop500_of_probe(struct platform_device *pdev, + codec_np = of_parse_phandle(np, "stericsson,audio-codec", 0); + + if (!(msp_np[0] && msp_np[1] && codec_np)) { +- dev_err(&pdev->dev, "Phandle missing or invalid\n"); ++ dev_err(dev, "Phandle missing or invalid\n"); + for (i = 0; i < 2; i++) + of_node_put(msp_np[i]); + of_node_put(codec_np); +@@ -95,21 +96,20 @@ static int mop500_of_probe(struct platform_device *pdev, + mop500_dai_links[i].codecs->name = NULL; + } + +- snd_soc_of_parse_card_name(&mop500_card, "stericsson,card-name"); ++ snd_soc_of_parse_card_name(card, "stericsson,card-name"); + + return 0; + } + + static int mop500_probe(struct platform_device *pdev) + { +- struct device_node *np = pdev->dev.of_node; + int ret; + + dev_dbg(&pdev->dev, "%s: Enter.\n", __func__); + + mop500_card.dev = &pdev->dev; + +- ret = mop500_of_probe(pdev, np); ++ ret = mop500_of_probe(&mop500_card); + if (ret) + return ret; + +diff --git a/sound/soc/ux500/mop500_ab8500.c b/sound/soc/ux500/mop500_ab8500.c +--- a/sound/soc/ux500/mop500_ab8500.c ++++ b/sound/soc/ux500/mop500_ab8500.c +@@ -433,5 +433,5 @@ void mop500_ab8500_remove(struct snd_soc_card *card) + clk_put(drvdata->clk_ptr_ulpclk); + clk_put(drvdata->clk_ptr_intclk); + +- snd_soc_card_set_drvdata(card, drvdata); ++ snd_soc_card_set_drvdata(card, NULL); + } +diff --git a/sound/soc/xilinx/xlnx_formatter_pcm.c b/sound/soc/xilinx/xlnx_formatter_pcm.c +--- a/sound/soc/xilinx/xlnx_formatter_pcm.c ++++ b/sound/soc/xilinx/xlnx_formatter_pcm.c +@@ -9,8 +9,6 @@ + #include + #include + #include +-#include +-#include + #include + + #include +@@ -384,7 +382,7 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + if (err) { + dev_err(component->dev, + "Unable to set constraint on period bytes\n"); +- return err; ++ goto error; + } + + /* Resize the buffer bytes as divisible by 64 */ +@@ -394,7 +392,7 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + if (err) { + dev_err(component->dev, + "Unable to set constraint on buffer bytes\n"); +- return err; ++ goto error; + } + + /* Set periods as integer multiple */ +@@ -403,7 +401,7 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + if (err < 0) { + dev_err(component->dev, + "Unable to set constraint on periods to be integer\n"); +- return err; ++ goto error; + } + + /* enable DMA IOC irq */ +@@ -412,6 +410,14 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + writel(val, stream_data->mmio + XLNX_AUD_CTRL); + + return 0; ++ ++error: ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ++ adata->play_stream = NULL; ++ else ++ adata->capture_stream = NULL; ++ kfree(stream_data); ++ return err; + } + + static int xlnx_formatter_pcm_close(struct snd_soc_component *component, +diff --git a/sound/soc/xilinx/xlnx_i2s.c b/sound/soc/xilinx/xlnx_i2s.c +--- a/sound/soc/xilinx/xlnx_i2s.c ++++ b/sound/soc/xilinx/xlnx_i2s.c +@@ -9,9 +9,8 @@ + + #include + #include +-#include +-#include + #include ++#include + #include + #include + +@@ -174,7 +173,6 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + int ret; + u32 format; + struct device *dev = &pdev->dev; +- struct device_node *node = dev->of_node; + + drv_data = devm_kzalloc(&pdev->dev, sizeof(*drv_data), GFP_KERNEL); + if (!drv_data) +@@ -184,18 +182,16 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + if (IS_ERR(drv_data->base)) + return PTR_ERR(drv_data->base); + +- ret = of_property_read_u32(node, "xlnx,num-channels", &drv_data->channels); +- if (ret < 0) { +- dev_err(dev, "cannot get supported channels\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,num-channels", &drv_data->channels); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get supported channels\n"); ++ + drv_data->channels *= 2; + +- ret = of_property_read_u32(node, "xlnx,dwidth", &drv_data->data_width); +- if (ret < 0) { +- dev_err(dev, "cannot get data width\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,dwidth", &drv_data->data_width); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get data width\n"); ++ + switch (drv_data->data_width) { + case 16: + format = SNDRV_PCM_FMTBIT_S16_LE; +@@ -207,7 +203,7 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + return -EINVAL; + } + +- if (of_device_is_compatible(node, "xlnx,i2s-transmitter-1.0")) { ++ if (device_is_compatible(dev, "xlnx,i2s-transmitter-1.0")) { + drv_data->dai_drv.name = "xlnx_i2s_playback"; + drv_data->dai_drv.playback.stream_name = "Playback"; + drv_data->dai_drv.playback.formats = format; +@@ -215,7 +211,7 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + drv_data->dai_drv.playback.channels_max = drv_data->channels; + drv_data->dai_drv.playback.rates = SNDRV_PCM_RATE_8000_192000; + drv_data->dai_drv.ops = &xlnx_i2s_dai_ops; +- } else if (of_device_is_compatible(node, "xlnx,i2s-receiver-1.0")) { ++ } else if (device_is_compatible(dev, "xlnx,i2s-receiver-1.0")) { + drv_data->dai_drv.name = "xlnx_i2s_capture"; + drv_data->dai_drv.capture.stream_name = "Capture"; + drv_data->dai_drv.capture.formats = format; +@@ -233,10 +229,8 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, &xlnx_i2s_component, + &drv_data->dai_drv, 1); +- if (ret) { +- dev_err(&pdev->dev, "i2s component registration failed\n"); ++ if (ret) + return ret; +- } + + dev_info(&pdev->dev, "%s DAI registered\n", drv_data->dai_drv.name); + +diff --git a/sound/soc/xilinx/xlnx_spdif.c b/sound/soc/xilinx/xlnx_spdif.c +--- a/sound/soc/xilinx/xlnx_spdif.c ++++ b/sound/soc/xilinx/xlnx_spdif.c +@@ -10,8 +10,7 @@ + #include + #include + #include +-#include +-#include ++#include + #include + #include + #include +@@ -242,28 +241,24 @@ static int xlnx_spdif_probe(struct platform_device *pdev) + struct spdif_dev_data *ctx; + + struct device *dev = &pdev->dev; +- struct device_node *node = dev->of_node; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->axi_clk = devm_clk_get_enabled(dev, "s_axi_aclk"); +- if (IS_ERR(ctx->axi_clk)) { +- ret = PTR_ERR(ctx->axi_clk); +- dev_err(dev, "failed to get s_axi_aclk(%d)\n", ret); +- return ret; +- } ++ if (IS_ERR(ctx->axi_clk)) ++ return dev_err_probe(dev, PTR_ERR(ctx->axi_clk), ++ "failed to get s_axi_aclk\n"); + + ctx->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ctx->base)) + return PTR_ERR(ctx->base); + +- ret = of_property_read_u32(node, "xlnx,spdif-mode", &ctx->mode); +- if (ret < 0) { +- dev_err(dev, "cannot get SPDIF mode\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,spdif-mode", &ctx->mode); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get SPDIF mode\n"); ++ + if (ctx->mode) { + dai_drv = &xlnx_spdif_tx_dai; + } else { +@@ -274,29 +269,23 @@ static int xlnx_spdif_probe(struct platform_device *pdev) + ret = devm_request_irq(dev, ret, + xlnx_spdifrx_irq_handler, + 0, "XLNX_SPDIF_RX", ctx); +- if (ret) { +- dev_err(dev, "spdif rx irq request failed\n"); +- return -ENODEV; +- } ++ if (ret) ++ return ret; + + init_waitqueue_head(&ctx->chsts_q); + dai_drv = &xlnx_spdif_rx_dai; + } + +- ret = of_property_read_u32(node, "xlnx,aud_clk_i", &ctx->aclk); +- if (ret < 0) { +- dev_err(dev, "cannot get aud_clk_i value\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,aud_clk_i", &ctx->aclk); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get aud_clk_i value\n"); + + dev_set_drvdata(dev, ctx); + + ret = devm_snd_soc_register_component(dev, &xlnx_spdif_component, + dai_drv, 1); +- if (ret) { +- dev_err(dev, "SPDIF component registration failed\n"); ++ if (ret) + return ret; +- } + + writel(XSPDIF_SOFT_RESET_VALUE, ctx->base + XSPDIF_SOFT_RESET_REG); + dev_info(dev, "%s DAI registered\n", dai_drv->name); +diff --git a/sound/soc/xtensa/xtfpga-i2s.c b/sound/soc/xtensa/xtfpga-i2s.c +--- a/sound/soc/xtensa/xtfpga-i2s.c ++++ b/sound/soc/xtensa/xtfpga-i2s.c +@@ -533,34 +533,25 @@ static int xtfpga_i2s_probe(struct platform_device *pdev) + int err, irq; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); +- if (!i2s) { +- err = -ENOMEM; +- goto err; +- } ++ if (!i2s) ++ return -ENOMEM; ++ + platform_set_drvdata(pdev, i2s); + i2s->dev = &pdev->dev; + dev_dbg(&pdev->dev, "dev: %p, i2s: %p\n", &pdev->dev, i2s); + + i2s->regs = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(i2s->regs)) { +- err = PTR_ERR(i2s->regs); +- goto err; +- } ++ if (IS_ERR(i2s->regs)) ++ return PTR_ERR(i2s->regs); + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->regs, + &xtfpga_i2s_regmap_config); +- if (IS_ERR(i2s->regmap)) { +- dev_err(&pdev->dev, "regmap init failed\n"); +- err = PTR_ERR(i2s->regmap); +- goto err; +- } ++ if (IS_ERR(i2s->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->regmap), "regmap init failed\n"); + + i2s->clk = devm_clk_get(&pdev->dev, NULL); +- if (IS_ERR(i2s->clk)) { +- dev_err(&pdev->dev, "couldn't get clock\n"); +- err = PTR_ERR(i2s->clk); +- goto err; +- } ++ if (IS_ERR(i2s->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->clk), "couldn't get clock\n"); + + regmap_write(i2s->regmap, XTFPGA_I2S_CONFIG, + (0x1 << XTFPGA_I2S_CONFIG_CHANNEL_BASE)); +@@ -568,41 +559,34 @@ static int xtfpga_i2s_probe(struct platform_device *pdev) + regmap_write(i2s->regmap, XTFPGA_I2S_INT_MASK, XTFPGA_I2S_INT_UNDERRUN); + + irq = platform_get_irq(pdev, 0); +- if (irq < 0) { +- err = irq; +- goto err; +- } ++ if (irq < 0) ++ return irq; ++ + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + xtfpga_i2s_threaded_irq_handler, + IRQF_SHARED | IRQF_ONESHOT, + pdev->name, i2s); +- if (err < 0) { +- dev_err(&pdev->dev, "request_irq failed\n"); +- goto err; +- } ++ if (err < 0) ++ return err; + + err = devm_snd_soc_register_component(&pdev->dev, + &xtfpga_i2s_component, + xtfpga_i2s_dai, + ARRAY_SIZE(xtfpga_i2s_dai)); +- if (err < 0) { +- dev_err(&pdev->dev, "couldn't register component\n"); +- goto err; +- } ++ if (err < 0) ++ return err; ++ + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + err = xtfpga_i2s_runtime_resume(&pdev->dev); +- if (err) +- goto err_pm_disable; ++ if (err) { ++ pm_runtime_disable(&pdev->dev); ++ return err; ++ } + } +- return 0; + +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +-err: +- dev_err(&pdev->dev, "%s: err = %d\n", __func__, err); +- return err; ++ return 0; + } + + static void xtfpga_i2s_remove(struct platform_device *pdev) +diff --git a/sound/sparc/dbri.c b/sound/sparc/dbri.c +--- a/sound/sparc/dbri.c ++++ b/sound/sparc/dbri.c +@@ -1840,6 +1840,7 @@ static void dbri_process_one_interrupt(struct snd_dbri *dbri, int x) + int rval = D_INTR_GETRVAL(x); + #endif + ++#ifdef DBRI_DEBUG + if (channel == D_INTR_CMD) { + dprintk(D_CMD, "INTR: Command: %-5s Value:%d\n", + cmds[command], val); +@@ -1847,6 +1848,7 @@ static void dbri_process_one_interrupt(struct snd_dbri *dbri, int x) + dprintk(D_INT, "INTR: Chan:%d Code:%d Val:%#x\n", + channel, code, rval); + } ++#endif + + switch (code) { + case D_INTR_CMDI: +diff --git a/sound/usb/6fire/firmware.c b/sound/usb/6fire/firmware.c +--- a/sound/usb/6fire/firmware.c ++++ b/sound/usb/6fire/firmware.c +@@ -194,23 +194,20 @@ static int usb6fire_fw_ezusb_upload( + int ret; + u8 data; + struct usb_device *device = interface_to_usbdev(intf); +- const struct firmware *fw = NULL; +- struct ihex_record *rec = kmalloc_obj(struct ihex_record); ++ struct ihex_record *rec __free(kfree) = kmalloc_obj(struct ihex_record); + + if (!rec) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, fwname, &device->dev); + if (ret < 0) { +- kfree(rec); + dev_err(&intf->dev, + "error requesting ezusb firmware %s.\n", fwname); + return ret; + } + ret = usb6fire_fw_ihex_init(fw, rec); + if (ret < 0) { +- kfree(rec); +- release_firmware(fw); + dev_err(&intf->dev, + "error validating ezusb firmware %s.\n", fwname); + return ret; +@@ -219,8 +216,6 @@ static int usb6fire_fw_ezusb_upload( + data = 0x01; /* stop ezusb cpu */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1); + if (ret) { +- kfree(rec); +- release_firmware(fw); + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: begin message.\n", + fwname); +@@ -231,8 +226,6 @@ static int usb6fire_fw_ezusb_upload( + ret = usb6fire_fw_ezusb_write(device, 0xa0, rec->address, + rec->data, rec->len); + if (ret) { +- kfree(rec); +- release_firmware(fw); + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: data urb.\n", + fwname); +@@ -240,8 +233,6 @@ static int usb6fire_fw_ezusb_upload( + } + } + +- release_firmware(fw); +- kfree(rec); + if (postdata) { /* write data after firmware has been uploaded */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, postaddr, + postdata, postlen); +@@ -270,19 +261,18 @@ static int usb6fire_fw_fpga_upload( + int ret; + int i; + struct usb_device *device = interface_to_usbdev(intf); +- u8 *buffer = kmalloc(FPGA_BUFSIZE, GFP_KERNEL); ++ u8 *buffer __free(kfree) = kmalloc(FPGA_BUFSIZE, GFP_KERNEL); + const char *c; + const char *end; +- const struct firmware *fw; + + if (!buffer) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, fwname, &device->dev); + if (ret < 0) { + dev_err(&intf->dev, "unable to get fpga firmware %s.\n", + fwname); +- kfree(buffer); + return -EIO; + } + +@@ -291,8 +281,6 @@ static int usb6fire_fw_fpga_upload( + + ret = usb6fire_fw_ezusb_write(device, 8, 0, NULL, 0); + if (ret) { +- kfree(buffer); +- release_firmware(fw); + dev_err(&intf->dev, + "unable to upload fpga firmware: begin urb.\n"); + return ret; +@@ -304,15 +292,11 @@ static int usb6fire_fw_fpga_upload( + + ret = usb6fire_fw_fpga_write(device, buffer, i); + if (ret < 0) { +- release_firmware(fw); +- kfree(buffer); + dev_err(&intf->dev, + "unable to upload fpga firmware: fw urb.\n"); + return ret; + } + } +- release_firmware(fw); +- kfree(buffer); + + ret = usb6fire_fw_ezusb_write(device, 9, 0, NULL, 0); + if (ret) { +diff --git a/sound/usb/card.c b/sound/usb/card.c +--- a/sound/usb/card.c ++++ b/sound/usb/card.c +@@ -715,7 +715,7 @@ static void snd_usb_init_quirk_flags(int idx, struct snd_usb_audio *chip) + + /* old style option found: the position-based integer value */ + if (quirk_flags[idx] && +- !kstrtou32(quirk_flags[idx], 0, &chip->quirk_flags)) { ++ !kstrtou64(quirk_flags[idx], 0, &chip->quirk_flags)) { + snd_usb_apply_flag_dbg("module param", chip, chip->quirk_flags); + return; + } +@@ -905,6 +905,40 @@ static int try_to_register_card(struct snd_usb_audio *chip, int ifnum) + return 0; + } + ++static void usb_audio_disconnect_components(struct snd_usb_audio *chip) ++{ ++ struct snd_usb_stream *as; ++ struct snd_usb_endpoint *ep; ++ struct usb_mixer_interface *mixer; ++ struct list_head *p; ++ ++ /* release the pcm resources */ ++ list_for_each_entry(as, &chip->pcm_list, list) { ++ snd_usb_stream_disconnect(as); ++ } ++ /* release the endpoint resources */ ++ list_for_each_entry(ep, &chip->ep_list, list) { ++ snd_usb_endpoint_release(ep); ++ } ++ /* release the midi resources */ ++ list_for_each(p, &chip->midi_list) { ++ snd_usbmidi_disconnect(p); ++ } ++ snd_usb_midi_v2_disconnect_all(chip); ++ /* ++ * Nice to check quirk && quirk->shares_media_device and ++ * then call the snd_media_device_delete(). Don't have ++ * access to the quirk here. snd_media_device_delete() ++ * accesses mixer_list ++ */ ++ snd_media_device_delete(chip); ++ ++ /* release mixer resources */ ++ list_for_each_entry(mixer, &chip->mixer_list, list) { ++ snd_usb_mixer_disconnect(mixer); ++ } ++} ++ + /* + * probe the active usb device + * +@@ -1077,8 +1111,10 @@ static int usb_audio_probe(struct usb_interface *intf, + * decrement before memory is possibly returned. + */ + atomic_dec(&chip->active); +- if (!chip->num_interfaces) ++ if (!chip->num_interfaces) { ++ usb_audio_disconnect_components(chip); + snd_card_free(chip->card); ++ } + } + return err; + } +@@ -1091,48 +1127,18 @@ static bool __usb_audio_disconnect(struct usb_interface *intf, + struct snd_usb_audio *chip, + struct snd_card *card) + { +- struct list_head *p; +- + guard(mutex)(®ister_mutex); + + if (platform_ops && platform_ops->disconnect_cb) + platform_ops->disconnect_cb(chip); + + if (atomic_inc_return(&chip->shutdown) == 1) { +- struct snd_usb_stream *as; +- struct snd_usb_endpoint *ep; +- struct usb_mixer_interface *mixer; +- + /* wait until all pending tasks done; + * they are protected by snd_usb_lock_shutdown() + */ + snd_refcount_sync(&chip->usage_count); + snd_card_disconnect(card); +- /* release the pcm resources */ +- list_for_each_entry(as, &chip->pcm_list, list) { +- snd_usb_stream_disconnect(as); +- } +- /* release the endpoint resources */ +- list_for_each_entry(ep, &chip->ep_list, list) { +- snd_usb_endpoint_release(ep); +- } +- /* release the midi resources */ +- list_for_each(p, &chip->midi_list) { +- snd_usbmidi_disconnect(p); +- } +- snd_usb_midi_v2_disconnect_all(chip); +- /* +- * Nice to check quirk && quirk->shares_media_device and +- * then call the snd_media_device_delete(). Don't have +- * access to the quirk here. snd_media_device_delete() +- * accesses mixer_list +- */ +- snd_media_device_delete(chip); +- +- /* release mixer resources */ +- list_for_each_entry(mixer, &chip->mixer_list, list) { +- snd_usb_mixer_disconnect(mixer); +- } ++ usb_audio_disconnect_components(chip); + } + + if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND) +diff --git a/sound/usb/clock.c b/sound/usb/clock.c +--- a/sound/usb/clock.c ++++ b/sound/usb/clock.c +@@ -600,7 +600,7 @@ int snd_usb_set_sample_rate_v2v3(struct snd_usb_audio *chip, + static int set_sample_rate_v2v3(struct snd_usb_audio *chip, + const struct audioformat *fmt, int rate) + { +- int cur_rate, prev_rate; ++ int cur_rate, prev_rate = 0; + int clock; + + /* First, try to find a valid clock. This may trigger +@@ -625,9 +625,12 @@ static int set_sample_rate_v2v3(struct snd_usb_audio *chip, + return clock; + } + +- prev_rate = get_sample_rate_v2v3(chip, fmt->iface, fmt->altsetting, clock); +- if (prev_rate == rate) +- goto validation; ++ if (!(chip->quirk_flags & QUIRK_FLAG_ALWAYS_SET_RATE)) { ++ prev_rate = get_sample_rate_v2v3(chip, fmt->iface, ++ fmt->altsetting, clock); ++ if (prev_rate == rate) ++ goto validation; ++ } + + cur_rate = snd_usb_set_sample_rate_v2v3(chip, fmt, clock, rate); + if (cur_rate < 0) { +diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c +--- a/sound/usb/endpoint.c ++++ b/sound/usb/endpoint.c +@@ -1236,7 +1236,10 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) + /* try to use enough URBs to contain an entire ALSA buffer */ + max_urbs = min((unsigned) MAX_URBS, + MAX_QUEUE * packs_per_ms / urb_packs); +- ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods); ++ if (chip->quirk_flags & QUIRK_FLAG_PLAYBACK_URB_FIXUP) ++ ep->nurbs = MAX_URBS; ++ else ++ ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods); + } + + /* allocate and initialize data urbs */ +@@ -1260,6 +1263,8 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) + goto out_of_memory; + u->urb->pipe = ep->pipe; + u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; ++ if (chip->quirk_flags & QUIRK_FLAG_PLAYBACK_URB_FIXUP) ++ u->urb->transfer_flags |= URB_ISO_ASAP; + u->urb->interval = 1 << ep->datainterval; + u->urb->context = u; + u->urb->complete = snd_complete_urb; +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -505,6 +505,19 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { + {} + }; + ++/* Audient iD14: FU 12 advertises Volume on only 4 of its 6 logical channels ++ * and sits on the monitor mixer branch, but it is traced through to the ++ * Speaker output terminal and gets named "Speaker Playback Volume". Userspace ++ * then adopts it as the stream's hardware volume, and any setting below 0 dB ++ * attenuates some channels but not others (20 dB imbalance at 80%). Give it a ++ * non-standard name so that it is no longer taken for the stream's master ++ * volume, while remaining reachable for anyone who wants the monitor gain. ++ */ ++static const struct usbmix_name_map audient_id14_map[] = { ++ { 12, "Monitor Mix Playback" }, /* FU, partial coverage */ ++ {} ++}; ++ + /* + * Control map entries + */ +@@ -588,6 +601,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x2573, 0x0008), + .map = maya44_map, + }, ++ { ++ /* Audient iD14 */ ++ .id = USB_ID(0x2708, 0x0008), ++ .map = audient_id14_map, ++ }, + { + /* KEF X300A */ + .id = USB_ID(0x27ac, 0x1000), +diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c +--- a/sound/usb/mixer_quirks.c ++++ b/sound/usb/mixer_quirks.c +@@ -3920,6 +3920,7 @@ static int snd_rme_digiface_controls_create(struct usb_mixer_interface *mixer) + #define SND_DJM_450_IDX 0x5 + #define SND_DJM_A9_IDX 0x6 + #define SND_DJM_V10_IDX 0x7 ++#define SND_DJM_S11_IDX 0x8 + + #define SND_DJM_CTL(_name, suffix, _default_value, _windex) { \ + .name = _name, \ +@@ -4260,6 +4261,21 @@ static const struct snd_djm_ctl snd_djm_ctls_v10[] = { + // playback channels are fixed and controlled by hardware knobs on the mixer + }; + ++// DJM-S11 ++static const u16 snd_djm_opts_s11_cap1[] = { ++ 0x0100, 0x0103, 0x0106, 0x0107, 0x0108, 0x0109, 0x010d }; ++static const u16 snd_djm_opts_s11_cap2[] = { ++ 0x0200, 0x0203, 0x0206, 0x0207, 0x0208, 0x0209, 0x020d }; ++static const u16 snd_djm_opts_s11_cap3[] = { ++ 0x0307, 0x0308, 0x0309, 0x030a, 0x030d, 0x0311, 0x0312 }; ++ ++static const struct snd_djm_ctl snd_djm_ctls_s11[] = { ++ SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), ++ SND_DJM_CTL("Input 1 Capture Switch", s11_cap1, 1, SND_DJM_WINDEX_CAP), ++ SND_DJM_CTL("Input 2 Capture Switch", s11_cap2, 1, SND_DJM_WINDEX_CAP), ++ SND_DJM_CTL("Input 3 Capture Switch", s11_cap3, 3, SND_DJM_WINDEX_CAP) ++}; ++ + static const struct snd_djm_device snd_djm_devices[] = { + [SND_DJM_250MK2_IDX] = SND_DJM_DEVICE(250mk2), + [SND_DJM_750_IDX] = SND_DJM_DEVICE(750), +@@ -4269,6 +4285,7 @@ static const struct snd_djm_device snd_djm_devices[] = { + [SND_DJM_450_IDX] = SND_DJM_DEVICE(450), + [SND_DJM_A9_IDX] = SND_DJM_DEVICE(a9), + [SND_DJM_V10_IDX] = SND_DJM_DEVICE(v10), ++ [SND_DJM_S11_IDX] = SND_DJM_DEVICE(s11), + }; + + static int snd_djm_controls_info(struct snd_kcontrol *kctl, +@@ -4573,6 +4590,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) + case USB_ID(0x2b73, 0x0034): /* Pioneer DJ DJM-V10 */ + err = snd_djm_controls_create(mixer, SND_DJM_V10_IDX); + break; ++ case USB_ID(0x2b73, 0x0037): /* Pioneer DJ DJM-S11 */ ++ err = snd_djm_controls_create(mixer, SND_DJM_S11_IDX); ++ break; + case USB_ID(0x03f0, 0x0269): /* HP TB Dock G2 */ + err = hp_dock_mixer_create(mixer); + break; +diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h +--- a/sound/usb/quirks-table.h ++++ b/sound/usb/quirks-table.h +@@ -3320,6 +3320,73 @@ YAMAHA_DEVICE(0x7010, "UB99"), + } + } + }, ++{ ++ /* ++ * Pioneer DJ / AlphaTheta DJM-S11 ++ */ ++ USB_DEVICE(0x2b73, 0x0037), ++ QUIRK_DRIVER_INFO { ++ QUIRK_DATA_COMPOSITE { ++ { ++ QUIRK_DATA_STANDARD_MIXER(0) ++ }, ++ { ++ QUIRK_DATA_AUDIOFORMAT(1) { ++ .formats = SNDRV_PCM_FMTBIT_S24_3LE, ++ .channels = 14, ++ .iface = 1, ++ .altsetting = 1, ++ .altset_idx = 1, ++ .endpoint = 0x01, ++ .ep_attr = USB_ENDPOINT_XFER_ISOC | ++ USB_ENDPOINT_SYNC_ASYNC, ++ .rates = SNDRV_PCM_RATE_48000, ++ .rate_min = 48000, ++ .rate_max = 48000, ++ .nr_rates = 1, ++ .rate_table = (unsigned int[]) { 48000 }, ++ .clock = 1, ++ .fmt_type = UAC_FORMAT_TYPE_I ++ } ++ }, ++ { ++ QUIRK_DATA_AUDIOFORMAT(2) { ++ .formats = SNDRV_PCM_FMTBIT_S24_3LE, ++ .channels = 10, ++ .iface = 2, ++ .altsetting = 1, ++ .altset_idx = 1, ++ .endpoint = 0x82, ++ .ep_attr = USB_ENDPOINT_XFER_ISOC | ++ USB_ENDPOINT_SYNC_ASYNC | ++ USB_ENDPOINT_USAGE_IMPLICIT_FB, ++ .rates = SNDRV_PCM_RATE_48000, ++ .rate_min = 48000, ++ .rate_max = 48000, ++ .nr_rates = 1, ++ .rate_table = (unsigned int[]) { 48000 }, ++ .clock = 1, ++ .fmt_type = UAC_FORMAT_TYPE_I ++ } ++ }, ++ { ++ /* Audio Control (unknown purpose) */ ++ .ifnum = 3, ++ .type = QUIRK_IGNORE_INTERFACE ++ }, ++ { ++ .ifnum = 4, ++ .type = QUIRK_MIDI_STANDARD_INTERFACE ++ }, ++ { ++ /* HID */ ++ .ifnum = 5, ++ .type = QUIRK_IGNORE_INTERFACE ++ }, ++ QUIRK_COMPOSITE_END ++ } ++ } ++}, + + /* + * MacroSilicon MS2100/MS2106 based AV capture cards +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -2145,7 +2145,7 @@ struct usb_string_match { + + struct usb_audio_quirk_flags_table { + u32 id; +- u32 flags; ++ u64 flags; + const struct usb_string_match *usb_string_match; + }; + +@@ -2314,7 +2314,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x0661, 0x0883, /* iBasso DC04 Ultra */ + QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x0666, 0x0880, /* SPACETOUCH USB Audio */ +- QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), ++ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY | ++ QUIRK_FLAG_CTL_MSG_DELAY_5M), + DEVICE_FLG(0x06f8, 0xb000, /* Hercules DJ Console (Windows Edition) */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x06f8, 0xd002, /* Hercules DJ Console (Macintosh Edition) */ +@@ -2337,6 +2338,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x0951, 0x16ad, /* Kingston HyperX */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), ++ DEVICE_FLG(0x0a73, 0x003a, /* Mackie DLZ Creator XS */ ++ QUIRK_FLAG_ALWAYS_SET_RATE), + DEVICE_FLG(0x0b05, 0x18a6, /* ASUSTek Computer, Inc. */ + QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x0b0e, 0x0349, /* Jabra 550a */ +@@ -2432,6 +2435,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), + DEVICE_FLG(0x1bcf, 0x2283, /* NexiGo N930AF FHD Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), ++ DEVICE_FLG(0x1e0b, 0xd01e, /* Generic USB Audio Device */ ++ QUIRK_FLAG_PLAYBACK_URB_FIXUP), + DEVICE_FLG(0x1ff7, 0x0f81, /* SC13A Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x2040, 0x7200, /* Hauppauge HVR-950Q */ +@@ -2528,6 +2533,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */ + QUIRK_FLAG_ALIGN_TRANSFER), ++ DEVICE_FLG(0x84ef, 0x002a, /* Valeton GP-200 */ ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x84ef, 0x0082, /* Hotone Audio Pulze Mini */ + QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL | QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL), + +@@ -2641,18 +2648,12 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { + QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), + QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY), + QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN), ++ QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), ++ QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + NULL + }; + +-const char *snd_usb_quirk_flag_find_name(unsigned long index) +-{ +- if (index >= ARRAY_SIZE(snd_usb_audio_quirk_flag_names)) +- return NULL; +- +- return snd_usb_audio_quirk_flag_names[index]; +-} +- +-u32 snd_usb_quirk_flags_from_name(const char *name) ++static u64 snd_usb_quirk_flags_from_name(const char *name) + { + int i; + +@@ -2661,7 +2662,7 @@ u32 snd_usb_quirk_flags_from_name(const char *name) + + for (i = 0; snd_usb_audio_quirk_flag_names[i]; i++) { + if (strcasecmp(name, snd_usb_audio_quirk_flag_names[i]) == 0) +- return BIT_U32(i); ++ return BIT_U64(i); + } + + return 0; +@@ -2719,7 +2720,7 @@ void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + { + u16 chip_vid = USB_ID_VENDOR(chip->usb_id); + u16 chip_pid = USB_ID_PRODUCT(chip->usb_id); +- u32 mask_flags, unmask_flags, bit; ++ u64 mask_flags, unmask_flags, bit; + char *p, *field, *flag; + bool is_unmask; + u16 vid, pid; +@@ -2773,7 +2774,7 @@ void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + is_unmask = false; + } + +- if (!kstrtou32(flag, 16, &bit)) { ++ if (!kstrtou64(flag, 16, &bit)) { + if (is_unmask) + unmask_flags |= bit; + else +diff --git a/sound/usb/quirks.h b/sound/usb/quirks.h +--- a/sound/usb/quirks.h ++++ b/sound/usb/quirks.h +@@ -56,7 +56,4 @@ void snd_usb_init_quirk_flags_table(struct snd_usb_audio *chip); + void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + const char *str); + +-const char *snd_usb_quirk_flag_find_name(unsigned long flag); +-u32 snd_usb_quirk_flags_from_name(const char *name); +- + #endif /* __USBAUDIO_QUIRKS_H */ +diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h +--- a/sound/usb/usbaudio.h ++++ b/sound/usb/usbaudio.h +@@ -44,7 +44,7 @@ struct snd_usb_audio { + atomic_t active; + atomic_t shutdown; + struct snd_refcount usage_count; +- unsigned int quirk_flags; ++ u64 quirk_flags; + unsigned int need_delayed_register:1; /* warn for delayed registration */ + int num_interfaces; + int last_iface; +@@ -254,6 +254,16 @@ extern bool snd_usb_skip_validation; + * check being non-fatal and only disabling GET_CUR instead of the whole mixer. + * The current volume will then be provided by the internal cache that stores + * the last set volume ++ * QUIRK_FLAG_PLAYBACK_URB_FIXUP ++ * Set URB_ISO_ASAP flag for isochronous URBs and force nurbs to MAX_URBS. ++ * This is needed for devices that exhibit boot-time audio stuttering due ++ * to insufficient buffer depth combined with xHCI scheduling variability. ++ * The larger buffer (MAX_URBS = 12, ~64ms) absorbs system scheduling ++ * jitter during boot, while URB_ISO_ASAP ensures consistent xHCI scheduling. ++ * QUIRK_FLAG_ALWAYS_SET_RATE: ++ * Issue SET_CUR for the sample rate even when the clock already reports the ++ * requested rate. A device advertising a single rate is otherwise never sent ++ * the request at all, and some require it before streaming will start. + */ + + enum { +@@ -288,10 +298,12 @@ enum { + QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, + QUIRK_TYPE_IFB_SILENCE_ON_EMPTY = 29, + QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, ++ QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, ++ QUIRK_TYPE_ALWAYS_SET_RATE = 32, + /* Please also edit snd_usb_audio_quirk_flag_names */ + }; + +-#define QUIRK_FLAG(x) BIT_U32(QUIRK_TYPE_ ## x) ++#define QUIRK_FLAG(x) BIT_U64(QUIRK_TYPE_ ## x) + + #define QUIRK_FLAG_GET_SAMPLE_RATE QUIRK_FLAG(GET_SAMPLE_RATE) + #define QUIRK_FLAG_SHARE_MEDIA_DEVICE QUIRK_FLAG(SHARE_MEDIA_DEVICE) +@@ -324,5 +336,7 @@ enum { + #define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) + #define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY QUIRK_FLAG(IFB_SILENCE_ON_EMPTY) + #define QUIRK_FLAG_MIXER_GET_CUR_BROKEN QUIRK_FLAG(MIXER_GET_CUR_BROKEN) ++#define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) ++#define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) + + #endif /* __USBAUDIO_H */ +diff --git a/sound/usb/usx2y/us144mkii_pcm.h b/sound/usb/usx2y/us144mkii_pcm.h +--- a/sound/usb/usx2y/us144mkii_pcm.h ++++ b/sound/usb/usx2y/us144mkii_pcm.h +@@ -7,7 +7,7 @@ + #include "us144mkii.h" + + /** +- * tascam_pcm_hw - Hardware capabilities for TASCAM US-144MKII PCM. ++ * var tascam_pcm_hw - Hardware capabilities for TASCAM US-144MKII PCM. + * + * Defines the supported PCM formats, rates, channels, and buffer/period sizes + * for the TASCAM US-144MKII audio interface. +@@ -15,14 +15,14 @@ + extern const struct snd_pcm_hardware tascam_pcm_hw; + + /** +- * tascam_playback_ops - ALSA PCM operations for playback. ++ * var tascam_playback_ops - ALSA PCM operations for playback. + * + * This structure defines the callback functions for playback stream operations. + */ + extern const struct snd_pcm_ops tascam_playback_ops; + + /** +- * tascam_capture_ops - ALSA PCM operations for capture. ++ * var tascam_capture_ops - ALSA PCM operations for capture. + * + * This structure defines the callback functions for capture stream operations. + */ +diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c +--- a/sound/usb/usx2y/usbusx2yaudio.c ++++ b/sound/usb/usx2y/usbusx2yaudio.c +@@ -167,7 +167,6 @@ static int usx2y_urb_submit(struct snd_usx2y_substream *subs, struct urb *urb, i + if (!urb) + return -ENODEV; + urb->start_frame = frame + NRURBS * nr_of_packs(); // let hcd do rollover sanity checks +- urb->hcpriv = NULL; + urb->dev = subs->usx2y->dev; /* we need to set this at each time */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { diff --git a/pkgbuilds/linux-omarchy-bore/0511-sound-updates-fixes.patch b/pkgbuilds/linux-omarchy-bore/0511-sound-updates-fixes.patch new file mode 100644 index 0000000..c32b5c3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0511-sound-updates-fixes.patch @@ -0,0 +1,1655 @@ +diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +--- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h ++++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +@@ -237,34 +237,34 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 70 + +-#define LAPSS_CLK_ID_QAIF_IF0_IBIT 71 +-#define LAPSS_CLK_ID_QAIF_IF0_EBIT 72 +-#define LAPSS_CLK_ID_QAIF_IF1_IBIT 73 +-#define LAPSS_CLK_ID_QAIF_IF1_EBIT 74 +-#define LAPSS_CLK_ID_QAIF_IF2_IBIT 75 +-#define LAPSS_CLK_ID_QAIF_IF2_EBIT 76 +-#define LAPSS_CLK_ID_QAIF_IF3_IBIT 77 +-#define LAPSS_CLK_ID_QAIF_IF3_EBIT 78 +-#define LAPSS_CLK_ID_QAIF_IF4_IBIT 79 +-#define LAPSS_CLK_ID_QAIF_IF4_EBIT 80 +-#define LAPSS_CLK_ID_QAIF_IF5_IBIT 81 +-#define LAPSS_CLK_ID_QAIF_IF5_EBIT 82 +-#define LAPSS_CLK_ID_QAIF_IF6_IBIT 83 +-#define LAPSS_CLK_ID_QAIF_IF6_EBIT 84 +-#define LAPSS_CLK_ID_QAIF_IF7_IBIT 85 +-#define LAPSS_CLK_ID_QAIF_IF7_EBIT 86 +-#define LAPSS_CLK_ID_QAIF_IF8_IBIT 87 +-#define LAPSS_CLK_ID_QAIF_IF8_EBIT 88 +-#define LAPSS_CLK_ID_QAIF_IF9_IBIT 89 +-#define LAPSS_CLK_ID_QAIF_IF9_EBIT 90 +-#define LAPSS_CLK_ID_QAIF_IF10_IBIT 91 +-#define LAPSS_CLK_ID_QAIF_IF10_EBIT 92 +-#define LAPSS_CLK_ID_QAIF_IF11_IBIT 93 +-#define LAPSS_CLK_ID_QAIF_IF11_EBIT 94 +-#define LAPSS_CLK_ID_QAIF_IF12_IBIT 95 +-#define LAPSS_CLK_ID_QAIF_IF12_EBIT 96 +-#define LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 97 +-#define LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 98 ++#define LPASS_CLK_ID_QAIF_IF0_IBIT 71 ++#define LPASS_CLK_ID_QAIF_IF0_EBIT 72 ++#define LPASS_CLK_ID_QAIF_IF1_IBIT 73 ++#define LPASS_CLK_ID_QAIF_IF1_EBIT 74 ++#define LPASS_CLK_ID_QAIF_IF2_IBIT 75 ++#define LPASS_CLK_ID_QAIF_IF2_EBIT 76 ++#define LPASS_CLK_ID_QAIF_IF3_IBIT 77 ++#define LPASS_CLK_ID_QAIF_IF3_EBIT 78 ++#define LPASS_CLK_ID_QAIF_IF4_IBIT 79 ++#define LPASS_CLK_ID_QAIF_IF4_EBIT 80 ++#define LPASS_CLK_ID_QAIF_IF5_IBIT 81 ++#define LPASS_CLK_ID_QAIF_IF5_EBIT 82 ++#define LPASS_CLK_ID_QAIF_IF6_IBIT 83 ++#define LPASS_CLK_ID_QAIF_IF6_EBIT 84 ++#define LPASS_CLK_ID_QAIF_IF7_IBIT 85 ++#define LPASS_CLK_ID_QAIF_IF7_EBIT 86 ++#define LPASS_CLK_ID_QAIF_IF8_IBIT 87 ++#define LPASS_CLK_ID_QAIF_IF8_EBIT 88 ++#define LPASS_CLK_ID_QAIF_IF9_IBIT 89 ++#define LPASS_CLK_ID_QAIF_IF9_EBIT 90 ++#define LPASS_CLK_ID_QAIF_IF10_IBIT 91 ++#define LPASS_CLK_ID_QAIF_IF10_EBIT 92 ++#define LPASS_CLK_ID_QAIF_IF11_IBIT 93 ++#define LPASS_CLK_ID_QAIF_IF11_EBIT 94 ++#define LPASS_CLK_ID_QAIF_IF12_IBIT 95 ++#define LPASS_CLK_ID_QAIF_IF12_EBIT 96 ++#define LPASS_CLK_ID_VA_QAIF_IF0_IBIT 97 ++#define LPASS_CLK_ID_VA_QAIF_IF0_EBIT 98 + + #define LPASS_HW_AVTIMER_VOTE 101 + #define LPASS_HW_MACRO_VOTE 102 +diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h +--- a/include/sound/rawmidi.h ++++ b/include/sound/rawmidi.h +@@ -179,7 +179,8 @@ int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info + int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, + int mode, struct snd_rawmidi_file *rfile, + int depth); +-int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile); ++int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, ++ int depth); + int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params); + int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream, +@@ -201,6 +202,11 @@ static inline int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, + return snd_rawmidi_kernel_open_nested(rmidi, subdevice, mode, rfile, 0); + } + ++static inline int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) ++{ ++ return snd_rawmidi_kernel_release_nested(rfile, 0); ++} ++ + /* set up the tied devices */ + static inline void snd_rawmidi_tie_devices(struct snd_rawmidi *r1, + struct snd_rawmidi *r2) +diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c +--- a/sound/aoa/soundbus/i2sbus/core.c ++++ b/sound/aoa/soundbus/i2sbus/core.c +@@ -240,6 +240,8 @@ static int i2sbus_add_dev(struct macio_dev *macio, + } + for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) { + int irq = irq_of_parse_and_map(np, i); ++ if (!irq) ++ goto err; + if (request_irq(irq, ints[i], 0, dev->rnames[i], dev)) + goto err; + dev->interrupts[i] = irq; +diff --git a/sound/core/control_led.c b/sound/core/control_led.c +--- a/sound/core/control_led.c ++++ b/sound/core/control_led.c +@@ -255,6 +255,8 @@ static int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id, + kctl = snd_ctl_find_id(card, id); + if (!kctl) + return -ENOENT; ++ if (!kctl->info || !kctl->get) ++ return -EINVAL; + ioff = snd_ctl_get_ioff(kctl, id); + vd = &kctl->vd[ioff]; + access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; +diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c +--- a/sound/core/rawmidi.c ++++ b/sound/core/rawmidi.c +@@ -571,7 +571,6 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) + struct snd_rawmidi *rmidi; + + rmidi = rfile->rmidi; +- guard(mutex)(&rmidi->open_mutex); + if (rfile->input) { + close_substream(rmidi, rfile->input, 1); + rfile->input = NULL; +@@ -585,7 +584,8 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) + } + + /* called from sound/core/seq/seq_midi.c */ +-int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) ++int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, ++ int depth) + { + struct snd_rawmidi *rmidi; + +@@ -593,11 +593,13 @@ int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) + return -ENXIO; + + rmidi = rfile->rmidi; ++ mutex_lock_nested(&rmidi->open_mutex, depth); + rawmidi_release_priv(rfile); ++ mutex_unlock(&rmidi->open_mutex); + module_put(rmidi->card->module); + return 0; + } +-EXPORT_SYMBOL(snd_rawmidi_kernel_release); ++EXPORT_SYMBOL(snd_rawmidi_kernel_release_nested); + + static int snd_rawmidi_release(struct inode *inode, struct file *file) + { +@@ -607,7 +609,8 @@ static int snd_rawmidi_release(struct inode *inode, struct file *file) + + rfile = file->private_data; + rmidi = rfile->rmidi; +- rawmidi_release_priv(rfile); ++ scoped_guard(mutex, &rmidi->open_mutex) ++ rawmidi_release_priv(rfile); + kfree(rfile); + module = rmidi->card->module; + snd_card_file_remove(rmidi->card, file); +diff --git a/sound/core/ump.c b/sound/core/ump.c +--- a/sound/core/ump.c ++++ b/sound/core/ump.c +@@ -1184,7 +1184,8 @@ static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream) + ump->legacy_substreams[dir][group] = NULL; + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!--ump->legacy_out_opens) +- snd_rawmidi_kernel_release(&ump->legacy_out_rfile); ++ snd_rawmidi_kernel_release_nested(&ump->legacy_out_rfile, ++ SINGLE_DEPTH_NESTING); + } + return 0; + } +diff --git a/sound/drivers/mtpav.c b/sound/drivers/mtpav.c +--- a/sound/drivers/mtpav.c ++++ b/sound/drivers/mtpav.c +@@ -642,9 +642,7 @@ static void snd_mtpav_free(struct snd_card *card) + { + struct mtpav *crd = card->private_data; + +- guard(spinlock_irqsave)(&crd->spinlock); +- if (crd->istimer > 0) +- snd_mtpav_remove_output_timer(crd); ++ timer_shutdown_sync(&crd->timer); + } + + /* +diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c +--- a/sound/hda/codecs/conexant.c ++++ b/sound/hda/codecs/conexant.c +@@ -207,7 +207,7 @@ static void cx_remove(struct hda_codec *codec) + snd_hda_gen_remove(codec); + } + +-static void cx_process_headset_plugin(struct hda_codec *codec) ++static void cx_process_headset_detect_plug_type(struct hda_codec *codec) + { + unsigned int val; + unsigned int count = 0; +@@ -223,11 +223,9 @@ static void cx_process_headset_plugin(struct hda_codec *codec) + count++; + } while (count < 3); + val = snd_hda_codec_read(codec, 0x1c, 0, 0xcb0, 0x0); +- if (val & 0x800) { +- codec_dbg(codec, "headset plugin, type is CTIA\n"); +- snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); +- } else if (val & 0x400) { +- codec_dbg(codec, "headset plugin, type is OMTP\n"); ++ if (val & 0xc00) { ++ codec_dbg(codec, "headset plugin, type is %s\n", ++ val & 0x800 ? "CTIA" : "OMTP"); + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); + } else { + codec_dbg(codec, "headphone plugin\n"); +@@ -243,10 +241,12 @@ static void cx_update_headset_mic_vref(struct hda_codec *codec, struct hda_jack_ + * Check hp&mic tag to process headset plugin & plugout. + */ + mic_present = snd_hda_codec_read(codec, 0x19, 0, AC_VERB_GET_PIN_SENSE, 0x0); +- if (!(mic_present & AC_PINSENSE_PRESENCE)) /* mic plugout */ ++ if (!(mic_present & AC_PINSENSE_PRESENCE)) { /* mic plugout */ + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20); +- else +- cx_process_headset_plugin(codec); ++ } else { ++ cx_process_headset_detect_plug_type(codec); ++ snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); ++ } + } + + static int cx_suspend(struct hda_codec *codec) +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -1370,6 +1370,61 @@ static void alc269_fixup_hp_gpio_led(struct hda_codec *codec, + alc_fixup_hp_gpio_led(codec, action, 0x08, 0x10); + } + ++/* ++ * HP Laptop 15-fd0xxx (SSID 103c:8bb6) Speaker Mute LED fix ++ * ++ * The speaker mute LED is controlled via VREF100 on NID 0x1a. ++ * This pin must remain powered (D0) even during suspend, otherwise ++ * the LED state is lost and the pin defaults to Hi-Z on resume, ++ * causing the LED to stop responding to mute toggles. ++ * Windows keeps this pin powered unconditionally, so matching that ++ * behavior ensures consistent LED operation across suspend/resume. ++ * The mic-mute LED is controlled via GPIO 0 with active-low polarity. ++ */ ++static unsigned int hp_8bb6_power_filter(struct hda_codec *codec, ++ hda_nid_t nid, ++ unsigned int power_state) ++{ ++ if (nid == 0x1a) ++ return AC_PWRST_D0; ++ return snd_hda_gen_path_power_filter(codec, nid, power_state); ++} ++ ++static int hp_8bb6_speaker_mute_led_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); ++ unsigned int val = (brightness == LED_OFF) ? PIN_IN : PIN_VREF100; ++ ++ snd_hda_set_pin_ctl_cache(codec, 0x1a, val); ++ return 0; ++} ++ ++static void alc236_fixup_hp_15_fd0xxx(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ struct alc_spec *spec = codec->spec; ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->micmute_led_polarity = 1; ++ alc_fixup_hp_gpio_led(codec, action, 0x00, 0x01); ++ ++ spec->mute_led_polarity = 0; ++ snd_hda_gen_add_mute_led_cdev(codec, hp_8bb6_speaker_mute_led_set); ++ ++ codec->power_filter = hp_8bb6_power_filter; ++ spec->no_shutup_pins = 1; ++ break; ++ ++ case HDA_FIXUP_ACT_INIT: ++ if (spec->gen.vmaster_mute.hook) ++ snd_hda_sync_vmaster_hook(&spec->gen.vmaster_mute); ++ break; ++ } ++} ++ + static void alc285_fixup_hp_gpio_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { +@@ -4089,6 +4144,7 @@ enum { + ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE, + ALC225_FIXUP_S3_POP_NOISE, + ALC700_FIXUP_INTEL_REFERENCE, ++ ALC700_FIXUP_INTEL_HADES_CANYON, + ALC274_FIXUP_DELL_BIND_DACS, + ALC274_FIXUP_DELL_AIO_LINEOUT_VERB, + ALC298_FIXUP_TPT470_DOCK_FIX, +@@ -4152,6 +4208,7 @@ enum { + ALC236_FIXUP_HP_GPIO_LED, + ALC236_FIXUP_HP_MUTE_LED, + ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF, ++ ALC236_FIXUP_HP_15_FD0XXX, + ALC236_FIXUP_LENOVO_INV_DMIC, + ALC298_FIXUP_SAMSUNG_AMP, + ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, +@@ -4297,6 +4354,7 @@ enum { + ALC287_FIXUP_LENOVO_LEGION_AW88399, + ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, + ALC285_LENOVO_DAC_RENAME, ++ ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, + }; + + /* A special fixup for Lenovo C940 and Yoga Duet 7; +@@ -4333,6 +4391,19 @@ static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, + __snd_hda_apply_fixup(codec, id, action, 0); + } + ++static void alc700_fixup_intel_hades_canyon(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ /* ++ * Windows may leave coef 0x1b bit 0x0400 cleared, causing broken ++ * analog playback after rebooting into Linux. Restore the bit ++ * during codec initialization. ++ */ ++ if (action == HDA_FIXUP_ACT_INIT) ++ alc_update_coef_idx(codec, 0x1b, 0x0400, 0x0400); ++} ++ + static const struct hda_fixup alc269_fixups[] = { + [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { + .type = HDA_FIXUP_PINS, +@@ -5411,6 +5482,10 @@ static const struct hda_fixup alc269_fixups[] = { + {} + } + }, ++ [ALC700_FIXUP_INTEL_HADES_CANYON] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc700_fixup_intel_hades_canyon, ++ }, + [ALC274_FIXUP_DELL_BIND_DACS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc274_fixup_bind_dacs, +@@ -5913,6 +5988,10 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc236_fixup_hp_mute_led_micmute_gpio, + }, ++ [ALC236_FIXUP_HP_15_FD0XXX] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc236_fixup_hp_15_fd0xxx, ++ }, + [ALC236_FIXUP_LENOVO_INV_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, +@@ -7010,6 +7089,12 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_lenovo_dac_rename, + }, ++ [ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_fixup_speaker2_to_dac1, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_TXNW2781_I2C, ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -7448,6 +7533,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), + SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), +@@ -8031,6 +8117,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), ++ SND_PCI_QUIRK(0x17aa, 0x3846, "Lenovo Yoga Pro 9 16IAH10", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), + /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; + * use codec SSID to distinguish them + */ +@@ -8044,14 +8131,15 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), ++ SND_PCI_QUIRK(0x17aa, 0x3862, "Lenovo IdeaPad Slim 3 15ABR8", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x3865, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3866, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), +- HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ ++ HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), + SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), +@@ -8231,6 +8319,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), + SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), ++ SND_PCI_QUIRK(0x8086, 0x2073, "Intel NUC 8 Hades Canyon", ++ ALC700_FIXUP_INTEL_HADES_CANYON), + SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC), + SND_PCI_QUIRK(0x8086, 0x2080, "Intel NUC 8 Rugged", ALC256_FIXUP_INTEL_NUC8_RUGGED), + SND_PCI_QUIRK(0x8086, 0x2081, "Intel NUC 10", ALC256_FIXUP_INTEL_NUC10), +diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c +--- a/sound/pci/ice1712/ice1712.c ++++ b/sound/pci/ice1712/ice1712.c +@@ -2523,7 +2523,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) + { + static int dev; +- struct snd_card *card; ++ struct snd_card *card __free(snd_card_unref) = NULL; + struct snd_ice1712 *ice; + int pcm_dev = 0, err; + const struct snd_ice1712_card_info * const *tbl, *c; +@@ -2535,8 +2535,8 @@ static int snd_ice1712_probe(struct pci_dev *pci, + return -ENOENT; + } + +- err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, +- sizeof(*ice), &card); ++ err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, ++ sizeof(*ice), &card); + if (err < 0) + return err; + ice = card->private_data; +@@ -2640,6 +2640,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, + if (err < 0) + return err; + pci_set_drvdata(pci, card); ++ card = NULL; /* probe succeeded, don't release as error */ + dev++; + return 0; + } +diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c +--- a/sound/soc/amd/acp-config.c ++++ b/sound/soc/amd/acp-config.c +@@ -54,7 +54,7 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC"), +- DMI_MATCH(DMI_PRODUCT_NAME, "TUF Gaming A14 FA401EA"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "FA401EA"), + }, + }, + { +@@ -70,6 +70,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_BOARD_NAME, "8EA1"), + }, + }, ++ { ++ /* HP OmniBook X Flip 16-cc0xxx */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "HP"), ++ DMI_MATCH(DMI_BOARD_NAME, "8EA2"), ++ }, ++ }, + {} + }; + +@@ -209,6 +216,20 @@ static const struct config_entry config_table[] = { + {} + }, + }, ++ { ++ .flags = FLAG_AMD_LEGACY, ++ .device = ACP_PCI_DEV_ID, ++ .dmi_table = (const struct dmi_system_id []) { ++ { ++ .matches = { ++ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1060"), ++ }, ++ }, ++ {} ++ }, ++ }, + }; + + static int snd_amd_acp_acpi_find_config(struct pci_dev *pci) +diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +--- a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c ++++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +@@ -382,6 +382,14 @@ static const struct dmi_system_id acp3x_es83xx_dmi_table[] = { + }, + .driver_data = (void *)(ES83XX_ENABLE_DMIC), + }, ++ { ++ .matches = { ++ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1060"), ++ }, ++ .driver_data = (void *)(ES83XX_ENABLE_DMIC), ++ }, + {} + }; + +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -570,6 +570,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Alienware"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R1 AMD"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +diff --git a/sound/soc/codecs/max98926.c b/sound/soc/codecs/max98926.c +--- a/sound/soc/codecs/max98926.c ++++ b/sound/soc/codecs/max98926.c +@@ -360,7 +360,7 @@ static int max98926_dai_set_fmt(struct snd_soc_dai *codec_dai, + regmap_write(max98926->regmap, + MAX98926_FORMAT, MAX98926_DAI_DLY_MASK); + regmap_update_bits(max98926->regmap, MAX98926_FORMAT, +- MAX98926_DAI_BCI_MASK, invert); ++ MAX98926_DAI_BCI_MASK | MAX98926_DAI_WCI_MASK, invert); + return 0; + } + +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -1779,6 +1779,7 @@ static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) + dev_dbg(dev, "%s jack/mic/amp func_status=0x%x, 0x%x, 0x%x\n", + __func__, jack_func_status, mic_func_status, amp_func_status); + ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_JD_CTL3, 0x7778); + /* DMIC */ + if ((mic_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt712->first_hw_init)) { + rt712_sdca_index_write(rt712, RT712_VENDOR_HDA_CTL, RT712_DMIC2_FU_IT_FLOAT_CTL, 0x1526); +diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h +--- a/sound/soc/codecs/rt712-sdca.h ++++ b/sound/soc/codecs/rt712-sdca.h +@@ -74,6 +74,7 @@ struct rt712_dmic_kctrl_priv { + #define RT712_JD_PRODUCT_NUM 0x00 + #define RT712_ANALOG_BIAS_CTL3 0x04 + #define RT712_JD_CTL1 0x09 ++#define RT712_JD_CTL3 0x0b + #define RT712_IO_CTL 0x0c + #define RT712_LDO2_3_CTL1 0x0e + #define RT712_PARA_VERB_CTL 0x1a +diff --git a/sound/soc/codecs/rt766-sdca-sdw.c b/sound/soc/codecs/rt766-sdca-sdw.c +--- a/sound/soc/codecs/rt766-sdca-sdw.c ++++ b/sound/soc/codecs/rt766-sdca-sdw.c +@@ -233,6 +233,8 @@ static void rt766_sdca_sdw_remove(struct sdw_slave *slave) + static const struct sdw_device_id rt766_sdca_id[] = { + SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x3, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x3, 0x1, 0), ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x4, 0x1, 0), ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x4, 0x1, 0), + {}, + }; + MODULE_DEVICE_TABLE(sdw, rt766_sdca_id); +diff --git a/sound/soc/codecs/tac5xx2-sdw.c b/sound/soc/codecs/tac5xx2-sdw.c +--- a/sound/soc/codecs/tac5xx2-sdw.c ++++ b/sound/soc/codecs/tac5xx2-sdw.c +@@ -55,6 +55,12 @@ + #define TAC_XU_BYPASS_REG(func, xu) \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##xu, \ + TAC_SDCA_CTL_XU_BYPASS, 0) ++#define TAC_VOLUME_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##fu, \ ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_##ch) ++#define TAC_GAIN_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##fu, \ ++ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_##ch) + + /* mute registers */ + #define FU21_L_MUTE_REG TAC_MUTE_REG(SA, FU21, LEFT) +@@ -123,9 +129,9 @@ static const DECLARE_TLV_DB_SCALE(tac5xx2_dvc_tlv, -7200, 50, 0); + #define TAC_DOUBLE_Q78_TLV(name, func_id, ent_id) \ + SDCA_DOUBLE_Q78_TLV(name, \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func_id, TAC_SDCA_ENT_##ent_id, \ +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), \ ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func_id, TAC_SDCA_ENT_##ent_id, \ +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), \ ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), \ + TAC_DVC_MIN, TAC_DVC_MAX, TAC_DVC_STEP, tac5xx2_dvc_tlv) + + struct tac5xx2_prv { +@@ -381,37 +387,53 @@ static const struct reg_default tac_reg_default[] = { + {TAC_REG_SDW(0, 1, 0x71), 0x5}, + {TAC_REG_SDW(0, 1, 0x72), 0x6}, + {TAC_REG_SDW(0, 1, 0x73), 0x7}, ++ /* SA */ ++ {TAC_MUTE_REG(SA, FU21, LEFT), 0x1}, ++ {TAC_MUTE_REG(SA, FU21, RIGHT), 0x1}, ++ {TAC_VOLUME_REG(SA, FU21, LEFT), 0x9c00}, ++ {TAC_VOLUME_REG(SA, FU21, RIGHT), 0x9c00}, ++ {TAC_MUTE_REG(SA, FU23, LEFT), 0x1}, ++ {TAC_MUTE_REG(SA, FU23, RIGHT), 0x1}, ++ {TAC_GAIN_REG(SA, FU23, LEFT), 0x0}, ++ {TAC_GAIN_REG(SA, FU23, RIGHT), 0x0}, ++ /* SM */ ++ {TAC_USAGE_REG(SM, IT11), 0x0}, ++ {TAC_USAGE_REG(SM, OT113), 0x0}, ++ {TAC_MUTE_REG(SM, FU113, LEFT), 0x1}, ++ {TAC_MUTE_REG(SM, FU113, RIGHT), 0x1}, ++ {TAC_GAIN_REG(SM, FU113, LEFT), 0x0}, ++ {TAC_GAIN_REG(SM, FU113, RIGHT), 0x0}, ++ {TAC_MUTE_REG(SM, FU11, LEFT), 0x1}, ++ {TAC_MUTE_REG(SM, FU11, RIGHT), 0x1}, ++ {TAC_GAIN_REG(SM, FU11, LEFT), 0x0}, ++ {TAC_GAIN_REG(SM, FU11, RIGHT), 0x0}, ++ {TAC_XU_BYPASS_REG(SM, XU12), 0x1}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_CS113, ++ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, ++ /* UAJ */ ++ {TAC_USAGE_REG(UAJ, IT33), 0x0}, ++ {TAC_USAGE_REG(UAJ, IT41), 0x0}, ++ {TAC_USAGE_REG(UAJ, OT36), 0x0}, ++ {TAC_USAGE_REG(UAJ, OT45), 0x0}, ++ {TAC_MUTE_REG(UAJ, FU41, LEFT), 0x1}, ++ {TAC_MUTE_REG(UAJ, FU41, RIGHT), 0x1}, ++ {TAC_VOLUME_REG(UAJ, FU41, LEFT), 0x0}, ++ {TAC_VOLUME_REG(UAJ, FU41, RIGHT), 0x0}, ++ {TAC_MUTE_REG(UAJ, FU36, RIGHT), 0x1}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, ++ TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), 0x0}, ++ {TAC_XU_BYPASS_REG(UAJ, XU42), 0x0}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_CS36, ++ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_CS41, ++ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, + }; + + static const struct reg_sequence tac_spk_seq[] = { + REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), + REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +-}; +- +-static const struct reg_sequence tac_sm_seq[] = { +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +-}; +- +-static const struct reg_sequence tac_uaj_seq[] = { +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, +- TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS), 0), ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), + }; + + static bool tac_volatile_reg(struct device *dev, unsigned int reg) +@@ -468,25 +490,25 @@ static int tac_sdca_mbq_size(struct device *dev, unsigned int reg) + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_MASTER_GAIN, 0): ++ TAC_SDCA_CHANNEL_GAIN, 0): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, +- TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS): + return 2; + + default: +@@ -531,7 +553,7 @@ static const struct snd_kcontrol_new tac_uaj_controls[] = { + TAC_DOUBLE_Q78_TLV("UAJ Playback Volume", UAJ, FU41), + SDCA_SINGLE_Q78_TLV("UAJ Capture Volume", + SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, +- TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS), ++ TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), + TAC_DVC_MIN, TAC_DVC_MAX, TAC_DVC_STEP, tac5xx2_dvc_tlv), + }; + +@@ -1761,16 +1783,6 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) + goto io_init_err; + } + dev_dbg(dev, "smartmic init done\n"); +- +- if (first) { +- ret = regmap_multi_reg_write(tac_dev->regmap, tac_sm_seq, +- ARRAY_SIZE(tac_sm_seq)); +- if (ret) { +- dev_err(tac_dev->dev, +- "init writes failed, err=%d", ret); +- goto io_init_err; +- } +- } + } + + if (tac_dev->uaj_func_data) { +@@ -1783,14 +1795,6 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) + dev_dbg(dev, "uaj init done\n"); + + if (first) { +- ret = regmap_multi_reg_write(tac_dev->regmap, tac_uaj_seq, +- ARRAY_SIZE(tac_uaj_seq)); +- if (ret) { +- dev_err(tac_dev->dev, +- "init writes failed, err=%d", ret); +- goto io_init_err; +- } +- + if (tac_dev->hs_jack) { + ret = tac5xx2_jack_init(tac_dev); + if (ret) { +diff --git a/sound/soc/codecs/tac5xx2.h b/sound/soc/codecs/tac5xx2.h +--- a/sound/soc/codecs/tac5xx2.h ++++ b/sound/soc/codecs/tac5xx2.h +@@ -174,14 +174,13 @@ + #define TAC_SDCA_ENT_SAPU29 0x35 + + /* Control selector definitions */ +-#define TAC_SDCA_MASTER_GAIN 0x0B + #define TAC_SDCA_MASTER_MUTE 0x01 + #define TAC_SDCA_CHANNEL_MUTE 0x01 +-#define TAC_SDCA_CHANNEL_GAIN 0x02 ++#define TAC_SDCA_CHANNEL_VOLUME 0x02 + #define TAC_SDCA_POSTURENUMBER 0x10 + #define TAC_SDCA_REQUESTED_PS 0x01 + #define TAC_SDCA_ACTUAL_PS 0x10 +-#define TAC_SDCA_CHANNEL_VOLUME 0x02 ++#define TAC_SDCA_CHANNEL_GAIN 0x0B + + /* 2. smart mic function */ + #define TAC_FUNCTION_ID_SM 0x2 +diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c +--- a/sound/soc/codecs/tas2783-sdw.c ++++ b/sound/soc/codecs/tas2783-sdw.c +@@ -1310,6 +1310,7 @@ static int tas_port_prep(struct sdw_slave *slave, struct sdw_prepare_ch *prep_ch + enum sdw_port_prep_ops pre_ops) + { + struct device *dev = &slave->dev; ++ struct tas2783_prv *tas_dev = dev_get_drvdata(dev); + struct sdw_dpn_prop *dpn_prop; + u32 addr; + int ret; +@@ -1321,6 +1322,25 @@ static int tas_port_prep(struct sdw_slave *slave, struct sdw_prepare_ch *prep_ch + addr = SDW_DPN_PREPARECTRL(prep_ch->num); + switch (pre_ops) { + case SDW_OPS_PORT_PRE_PREP: ++ /* ++ * The Function has to be powered before the port can complete ++ * channel preparation. hw_params() does that when a stream is ++ * set up, but a stream that is only re-prepared - as it is ++ * after the peripheral lost power in S0i3 - does not go ++ * through hw_params() again, and the peripheral is back at its ++ * PS3 reset default. Power it up here, where it is needed. ++ */ ++ scoped_guard(mutex, &tas_dev->pde_lock) ++ ret = regmap_write(tas_dev->regmap, ++ SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, ++ TAS2783_SDCA_CTL_REQ_POW_STATE, 0), ++ TAS2783_SDCA_POW_STATE_ON); ++ if (ret) { ++ dev_err(dev, "power up failed for port %d, err=%d\n", ++ prep_ch->num, ret); ++ return ret; ++ } ++ + ret = sdw_write_no_pm(slave, addr, prep_ch->ch_mask); + if (ret) + dev_err(dev, "prep failed for port %d, err=%d\n", +diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig +--- a/sound/soc/fsl/Kconfig ++++ b/sound/soc/fsl/Kconfig +@@ -244,7 +244,7 @@ config SND_SOC_MPC5200_AC97 + config SND_MPC52xx_SOC_PCM030 + tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712" + depends on PPC_MPC5200_SIMPLE +- select SND_SOC_MPC5200_AC97 ++ depends on SND_SOC_MPC5200_AC97 + select SND_SOC_WM9712 + help + Say Y if you want to add support for sound on the Phytec pcm030 +@@ -253,7 +253,7 @@ config SND_MPC52xx_SOC_PCM030 + config SND_MPC52xx_SOC_EFIKA + tristate "SoC AC97 Audio support for bbplan Efika and STAC9766" + depends on PPC_EFIKA +- select SND_SOC_MPC5200_AC97 ++ depends on SND_SOC_MPC5200_AC97 + select SND_SOC_STAC9766 + help + Say Y if you want to add support for sound on the Efika. +diff --git a/sound/soc/fsl/efika-audio-fabric.c b/sound/soc/fsl/efika-audio-fabric.c +--- a/sound/soc/fsl/efika-audio-fabric.c ++++ b/sound/soc/fsl/efika-audio-fabric.c +@@ -22,8 +22,6 @@ + #include + #include + +-#include "mpc5200_dma.h" +- + #define DRV_NAME "efika-audio-fabric" + + SND_SOC_DAILINK_DEFS(analog, +diff --git a/sound/soc/fsl/pcm030-audio-fabric.c b/sound/soc/fsl/pcm030-audio-fabric.c +--- a/sound/soc/fsl/pcm030-audio-fabric.c ++++ b/sound/soc/fsl/pcm030-audio-fabric.c +@@ -13,8 +13,6 @@ + + #include + +-#include "mpc5200_dma.h" +- + #define DRV_NAME "pcm030-audio-fabric" + + struct pcm030_audio_data { +diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c +--- a/sound/soc/loongson/loongson_card.c ++++ b/sound/soc/loongson/loongson_card.c +@@ -55,8 +55,8 @@ static const struct loongson_card_config ls2k0300_dl2k0300b_card_config = { + .add_dapm_routes = true, + }; + +-static int tegra_machine_event(struct snd_soc_dapm_widget *w, +- struct snd_kcontrol *k, int event) ++static int loongson_asoc_machine_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *k, int event) + { + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct loongson_card_data *priv = snd_soc_card_get_drvdata(card); +@@ -72,9 +72,9 @@ static int tegra_machine_event(struct snd_soc_dapm_widget *w, + return 0; + } + +-static const struct snd_soc_dapm_widget loongson_aosc_dapm_widgets[] = { +- SND_SOC_DAPM_HP("Headphone", tegra_machine_event), +- SND_SOC_DAPM_SPK("Speaker", tegra_machine_event), ++static const struct snd_soc_dapm_widget loongson_asoc_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone", loongson_asoc_machine_event), ++ SND_SOC_DAPM_SPK("Speaker", loongson_asoc_machine_event), + }; + + /* Headphones Jack */ +@@ -314,8 +314,8 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) + card->num_links = ARRAY_SIZE(loongson_dai_links); + + if (ls_priv->cfg->add_dapm_widgets) { +- card->dapm_widgets = loongson_aosc_dapm_widgets; +- card->num_dapm_widgets = ARRAY_SIZE(loongson_aosc_dapm_widgets); ++ card->dapm_widgets = loongson_asoc_dapm_widgets; ++ card->num_dapm_widgets = ARRAY_SIZE(loongson_asoc_dapm_widgets); + } + + snd_soc_card_set_drvdata(card, ls_priv); +diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c +--- a/sound/soc/qcom/common.c ++++ b/sound/soc/qcom/common.c +@@ -103,14 +103,14 @@ static int qcom_snd_parse_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, + int ret; + + if (!link_np) +- return -EINVAL; ++ return -ENOENT; + + struct device_node *cpu_np __free(device_node) = + of_get_child_by_name(link_np, "cpu"); + struct device_node *codec_np __free(device_node) = + of_get_child_by_name(link_np, "codec"); + if (!cpu_np || !codec_np) +- return -EINVAL; ++ return -ENOENT; + + ret = qcom_snd_parse_tdm_slot(cpu_np, cpu_cfg); + if (ret) +@@ -172,7 +172,7 @@ int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd) + + ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); + if (ret) +- return ret == -EINVAL ? 0 : ret; ++ return ret == -ENOENT ? 0 : ret; + + return qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); + } +diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c +--- a/sound/soc/qcom/qdsp6/q6apm-dai.c ++++ b/sound/soc/qcom/qdsp6/q6apm-dai.c +@@ -216,7 +216,7 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, + { + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; +- struct audioreach_module_config cfg; ++ struct audioreach_module_config cfg = {}; + struct device *dev = component->dev; + struct q6apm_dai_data *pdata; + int ret; +@@ -814,7 +814,7 @@ static int q6apm_dai_compr_set_params(struct snd_soc_component *component, + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + struct q6apm_dai_data *pdata; +- struct audioreach_module_config cfg; ++ struct audioreach_module_config cfg = {}; + struct snd_codec *codec = ¶ms->codec; + int dir = stream->direction; + int ret; +diff --git a/sound/soc/qcom/qdsp6/q6prm-clocks.c b/sound/soc/qcom/qdsp6/q6prm-clocks.c +--- a/sound/soc/qcom/qdsp6/q6prm-clocks.c ++++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c +@@ -64,34 +64,34 @@ static const struct q6dsp_clk_init q6prm_clks[] = { + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_2X_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_EBIT), + Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6PRM_HW_CORE_ID_LPASS, + "LPASS_HW_MACRO"), + Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6PRM_HW_CORE_ID_DCODEC, +diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h +--- a/sound/soc/qcom/qdsp6/q6prm.h ++++ b/sound/soc/qcom/qdsp6/q6prm.h +@@ -97,34 +97,34 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 0x318 + +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_IBIT 0x500 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_EBIT 0x501 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_IBIT 0x502 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_EBIT 0x503 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_IBIT 0x504 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_EBIT 0x505 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_IBIT 0x506 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_EBIT 0x507 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_IBIT 0x508 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_EBIT 0x509 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_IBIT 0x50A +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_EBIT 0x50B +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_IBIT 0x50C +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_EBIT 0x50D +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_IBIT 0x50E +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_EBIT 0x50F +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_IBIT 0x510 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_EBIT 0x511 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_IBIT 0x512 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_EBIT 0x513 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_IBIT 0x514 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_EBIT 0x515 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_IBIT 0x516 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_EBIT 0x517 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_IBIT 0x518 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_EBIT 0x519 +-#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 +-#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_IBIT 0x500 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_EBIT 0x501 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_IBIT 0x502 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_EBIT 0x503 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_IBIT 0x504 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_EBIT 0x505 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_IBIT 0x506 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_EBIT 0x507 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_IBIT 0x508 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_EBIT 0x509 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_IBIT 0x50A ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_EBIT 0x50B ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_IBIT 0x50C ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_EBIT 0x50D ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_IBIT 0x50E ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_EBIT 0x50F ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_IBIT 0x510 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_EBIT 0x511 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_IBIT 0x512 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_EBIT 0x513 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_IBIT 0x514 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_EBIT 0x515 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_IBIT 0x516 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_EBIT 0x517 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_IBIT 0x518 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_EBIT 0x519 ++#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 ++#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 + + #define Q6PRM_LPASS_CLK_SRC_INTERNAL 1 + #define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0 +diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c +--- a/sound/soc/qcom/sc8280xp.c ++++ b/sound/soc/qcom/sc8280xp.c +@@ -115,33 +115,42 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai; + struct qcom_snd_tdm_slot_cfg cpu_cfg; + struct qcom_snd_tdm_slot_cfg codec_cfg; +- unsigned int bclk_freq; ++ int bclk_freq; + int ret; + int i; + + ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); + if (ret) +- return ret == -EINVAL ? 0 : ret; ++ return ret == -ENOENT ? 0 : ret; + + if (!cpu_cfg.slots) + return 0; + + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); +- if (ret) ++ if (ret && ret != -ENOTSUPP) + return ret; + ++ if (data->priv->codec_dai_fmt) { ++ for_each_rtd_codec_dais(rtd, i, codec_dai) { ++ ret = snd_soc_dai_set_fmt(codec_dai, ++ data->priv->codec_dai_fmt); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ } ++ } ++ + ret = qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); + if (ret) + return ret; + + bclk_freq = snd_soc_tdm_params_to_bclk(params, cpu_cfg.slot_width, cpu_cfg.slots, 1); +- if (!bclk_freq) ++ if (bclk_freq <= 0) + return -EINVAL; + + if (data->priv->mi2s_bclk_enable) { + ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, + SND_SOC_CLOCK_IN); +- if (ret) { ++ if (ret && ret != -ENOTSUPP) { + dev_err(rtd->dev, "%s: failed to set cpu sysclk: %d\n", + __func__, ret); + return ret; +@@ -152,7 +161,7 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, + for_each_rtd_codec_dais(rtd, i, codec_dai) { + ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq, + SND_SOC_CLOCK_IN); +- if (ret) { ++ if (ret && ret != -ENOTSUPP) { + dev_err(rtd->dev, "%s: failed to set codec sysclk on %s: %d\n", + __func__, codec_dai->name, ret); + return ret; +diff --git a/sound/soc/sdw_utils/soc_sdw_rt_amp.c b/sound/soc/sdw_utils/soc_sdw_rt_amp.c +--- a/sound/soc/sdw_utils/soc_sdw_rt_amp.c ++++ b/sound/soc/sdw_utils/soc_sdw_rt_amp.c +@@ -252,11 +252,13 @@ int asoc_sdw_rt_amp_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai + if (ctx->amp_dev1) { + device_remove_software_node(ctx->amp_dev1); + put_device(ctx->amp_dev1); ++ ctx->amp_dev1 = NULL; + } + + if (ctx->amp_dev2) { + device_remove_software_node(ctx->amp_dev2); + put_device(ctx->amp_dev2); ++ ctx->amp_dev2 = NULL; + } + + return 0; +diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c +--- a/sound/soc/sdw_utils/soc_sdw_utils.c ++++ b/sound/soc/sdw_utils/soc_sdw_utils.c +@@ -81,6 +81,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* speaker */ + .direction = {true, false}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tac5572", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -94,6 +95,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tac5572", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -102,6 +104,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* UAJ */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif3", ++ .component_name = "tac5572", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .controls = generic_jack_controls, +@@ -122,6 +125,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* speaker with IV sense feedback */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tac5672", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -135,6 +139,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tac5672", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -143,6 +148,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* UAJ */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif3", ++ .component_name = "tac5672", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .controls = generic_jack_controls, +@@ -163,6 +169,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* speaker with echo reference feedback */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tac5682", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -176,6 +183,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tac5682", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -184,6 +192,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* UAJ */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif3", ++ .component_name = "tac5682", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .controls = generic_jack_controls, +@@ -203,6 +212,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + { + .direction = {true, false}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tas2883", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -216,6 +226,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tas2883", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -1510,9 +1521,24 @@ int asoc_sdw_trigger(struct snd_pcm_substream *substream, int cmd) + } + + switch (cmd) { ++ case SNDRV_PCM_TRIGGER_RESUME: ++ /* ++ * The peripherals lose their port configuration when the ++ * controller is power-gated during system suspend, and an ++ * application that restarts the stream with ++ * SNDRV_PCM_IOCTL_RESUME - which platforms advertising ++ * SNDRV_PCM_INFO_RESUME allow - never goes through ++ * .prepare() again. Prepare the stream here so that the ++ * ports are reprogrammed before they are enabled; ++ * sdw_prepare_stream() reapplies the parameters without ++ * recomputing them when the stream is disabled. ++ */ ++ ret = sdw_prepare_stream(sdw_stream); ++ if (ret) ++ break; ++ fallthrough; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +- case SNDRV_PCM_TRIGGER_RESUME: + ret = sdw_enable_stream(sdw_stream); + break; + +diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c +--- a/sound/soc/soc-dapm.c ++++ b/sound/soc/soc-dapm.c +@@ -3614,7 +3614,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, + val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; + mask = e->mask << e->shift_l; + if (e->shift_l != e->shift_r) { +- if (item[1] > e->items) ++ if (item[1] >= e->items) + return -EINVAL; + val |= snd_soc_enum_item_to_val(e, item[1]) << e->shift_r; + mask |= e->mask << e->shift_r; +diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c +--- a/sound/soc/soc-generic-dmaengine-pcm.c ++++ b/sound/soc/soc-generic-dmaengine-pcm.c +@@ -397,24 +397,8 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + if (PTR_ERR(chan) == -EPROBE_DEFER) + return -EPROBE_DEFER; + +- bool has_fw_node = dev->of_node || is_acpi_device_node(dev->fwnode); +- bool name_exists_in_fw = false; +- +- if (has_fw_node) +- name_exists_in_fw = device_property_match_string(dev, +- "dma-names", +- name) >= 0; +- +- if (has_fw_node && name_exists_in_fw) +- dev_warn(dev, "DTS/ACPI DMA channel '%s' request failed (%ld)\n", +- name, PTR_ERR(chan)); +- +- if (has_fw_node && !name_exists_in_fw) +- dev_warn(dev, "DTS/ACPI name '%s' not found, legacy failed (%ld)\n", +- name, PTR_ERR(chan)); +- +- if (!has_fw_node) +- dev_warn(dev, "Legacy DMA channel '%s' request failed (%ld)\n", ++ if (device_property_match_string(dev, "dma-names", name) >= 0) ++ dev_warn(dev, "dma-names has '%s' but request failed (%ld)\n", + name, PTR_ERR(chan)); + + pcm->chan[i] = NULL; +diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c +--- a/sound/usb/mixer.c ++++ b/sound/usb/mixer.c +@@ -1237,21 +1237,27 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) + } + + /* +- * Additional checks for sticky mixers ++ * Additional checks for sticky GET_CUR + * +- * Some devices' volume control mixers are sticky, which accept SET_CUR but +- * do absolutely nothing. ++ * Some devices' volume control mixers have sticky GET_CUR, which implies either ++ * stubbed SET_CUR or broken GET_CUR. For the former case, the mixer accepts ++ * SET_CUR but do absolutely nothing, so falling back to soft mixer is the only ++ * way to control the volume. For the latter case, the mixer has effective ++ * SET_CUR despite GET_CUR being constant, and the mixer is usable as long as we ++ * always provide mixer value from the ceche. + * + * Check the return values of GET_CUR with different SET_CUR values. Consider +- * the mixer as sticky if GET_CUR always returns a constant value. ++ * GET_CUR as sticky if GET_CUR always returns a constant value. + * +- * Some devices have effective SET_CUR despite GET_CUR being constant. Do not +- * consider the mixer as sticky if a quirk flag indicates that. ++ * Unfortunately, we can't distinguish between stubbed SET_CUR and broken ++ * GET_CUR with simple read-back tests. Disabling the mixer regardless and ++ * forcing userspace to use soft mixer instead can lead to audible distortion at ++ * low volume on some wireless headphones, probably due to their poorly- ++ * performed lossy codec. + * +- * Gate the registration of sticky mixers to prevent confusing userspace, so +- * that they won't cause ineffective volume control. However, for mixers with +- * effective SET_CUR but broken GET_CUR, the registration can continue normally +- * but further GET_CUR requests will be gated. ++ * Instead, mark GET_CUR as broken regardless and only provide mixer value from ++ * the cache. Users may opt into soft mixer in userspace audio stack if they ++ * need it. + */ + static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, + int channel, int saved) +@@ -1271,24 +1277,13 @@ static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, + return 0; + } + +- if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_BROKEN) { +- usb_audio_info(cval->head.mixer->chip, +- "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", +- cval->head.id, mixer_ctrl_intf(cval->head.mixer), +- cval->min, cval->max, cval->res, saved); +- +- cval->get_cur_broken = 1; +- return -ENXIO; +- } +- +- usb_audio_err(cval->head.mixer->chip, +- "%d:%d: sticky mixer values (%d/%d/%d => %d), disabling\n", +- cval->head.id, mixer_ctrl_intf(cval->head.mixer), +- cval->min, cval->max, cval->res, saved); + usb_audio_info(cval->head.mixer->chip, +- "check MIXER_GET_CUR_BROKEN if you believe the mixer is non-sticky"); ++ "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", ++ cval->head.id, mixer_ctrl_intf(cval->head.mixer), ++ cval->min, cval->max, cval->res, saved); + +- return -ENODEV; ++ cval->get_cur_broken = 1; ++ return -ENXIO; + } + + /* +@@ -1385,8 +1380,6 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, + goto no_checks; + + ret = check_sticky_volume_control(cval, minchn, saved); +- if (ret == -ENODEV) +- goto sticky; + if (ret) + goto no_checks; + +@@ -1454,34 +1447,15 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, + } + } + +- return 0; +- +-sticky: + /* +- * It makes no sense to restore the saved value for a sticky mixer, +- * since setting any value is a no-op. +- * +- * However, in some rare cases, SET_CUR is effective despite GET_CUR +- * always returns a constant value. These mixers are not sticky, but +- * there's no way to distinguish them. Without any additional +- * information, the best thing we can do is to set the mixer value to +- * the maximum before bailing out, so that a soft mixer can still reach +- * the maximum hardware volume if the mixer turns out to be non-sticky. +- * Meanwhile, all channels must be synchronized to prevent imbalance +- * volume. ++ * When GET_CUR is sticky, the saved value is bogus, so mixer values set ++ * by the sanity checks must be discarded through init_cur_mix_raw(). ++ * After that, we can clear the flag as per QUIRK_FLAG_MIXER_GET_CUR_OK. + */ +- if (!cval->cmask) { +- snd_usb_set_cur_mix_value(cval, 0, 0, cval->max); +- } else { +- idx = 0; +- for (i = 0; i < MAX_CHANNELS; i++) { +- if (cval->cmask & BIT(i)) { +- snd_usb_set_cur_mix_value(cval, i + 1, idx, cval->max); +- idx++; +- } +- } +- } +- return ret; ++ if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_OK) ++ cval->get_cur_broken = 0; ++ ++ return 0; + } + + #define get_min_max(cval, def) get_min_max_with_quirks(cval, def, NULL) +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -505,13 +505,13 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { + {} + }; + +-/* Audient iD14: FU 12 advertises Volume on only 4 of its 6 logical channels +- * and sits on the monitor mixer branch, but it is traced through to the +- * Speaker output terminal and gets named "Speaker Playback Volume". Userspace +- * then adopts it as the stream's hardware volume, and any setting below 0 dB +- * attenuates some channels but not others (20 dB imbalance at 80%). Give it a +- * non-standard name so that it is no longer taken for the stream's master +- * volume, while remaining reachable for anyone who wants the monitor gain. ++/* Audient iD14 MkI and MkII: FU 12 sits on the monitor mixer branch but is ++ * traced through to the Speaker output terminal, so it is named "Speaker ++ * Playback Volume". On MkII it controls only 4 of 6 playback channels. MkI ++ * testing found asymmetric attenuation within the main stereo pair. Userspace ++ * adopts this control as the stream's hardware volume, causing imbalance below ++ * 0 dB. Give it a non-standard name so that userspace no longer treats it as ++ * the stream master, while keeping the monitor gain reachable. + */ + static const struct usbmix_name_map audient_id14_map[] = { + { 12, "Monitor Mix Playback" }, /* FU, partial coverage */ +@@ -602,7 +602,12 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .map = maya44_map, + }, + { +- /* Audient iD14 */ ++ /* Audient iD14 MkI */ ++ .id = USB_ID(0x2708, 0x0002), ++ .map = audient_id14_map, ++ }, ++ { ++ /* Audient iD14 MkII */ + .id = USB_ID(0x2708, 0x0008), + .map = audient_id14_map, + }, +diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h +--- a/sound/usb/quirks-table.h ++++ b/sound/usb/quirks-table.h +@@ -1814,6 +1814,28 @@ YAMAHA_DEVICE(0x7010, "UB99"), + } + } + }, ++{ ++ /* ++ * M-Audio Venom ++ * ++ * The AudioControl interface times out on every GET_CUR request, ++ * which adds around 47 seconds to the card registration and ++ * freezes the device, blocking streaming. ++ * Using an explicit composite quirk to skip the mixer entirely. ++ */ ++ USB_DEVICE_VENDOR_SPEC(0x0763, 0x2084), ++ QUIRK_DRIVER_INFO { ++ .vendor_name = "M-Audio", ++ .product_name = "Venom", ++ QUIRK_DATA_COMPOSITE { ++ { QUIRK_DATA_IGNORE(0) }, ++ { QUIRK_DATA_STANDARD_AUDIO(1) }, ++ { QUIRK_DATA_STANDARD_AUDIO(2) }, ++ { QUIRK_DATA_STANDARD_MIDI(3) }, ++ QUIRK_COMPOSITE_END ++ } ++ } ++}, + + /* Casio devices */ + { +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -2215,10 +2215,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), +- DEVICE_FLG(0x040b, 0x0897, /* Weltrend Semiconductor, sold as Redragon H510-PRO Wireless headset */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */ + QUIRK_FLAG_IGNORE_CTL_ERROR), ++ DEVICE_FLG(0x041e, 0x324d, /* Creative Sound Blaster Play! 3 */ ++ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x041e, 0x4080, /* Creative Live Cam VF0610 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x045e, 0x083c, /* MS USB Link headset */ +@@ -2256,8 +2256,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x046d, 0x0a8f, /* Logitech H390 headset */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x046d, 0x0af7, /* Logitech PRO X 2 LIGHTSPEED */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), ++ DEVICE_FLG(0x046d, 0x0aba, /* Logitech PRO X Wireless */ ++ QUIRK_FLAG_MIXER_GET_CUR_OK | ++ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x0499, 0x1506, /* Yamaha THR5 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0499, 0x1509, /* Steinberg UR22 */ +@@ -2329,9 +2330,11 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x0763, 0x2031, /* M-Audio Fast Track C600 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0763, 0x2080, /* M-Audio Fast Track Ultra */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0763, 0x2081, /* M-Audio Fast Track Ultra */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ DEVICE_FLG(0x0763, 0x2084, /* M-Audio Venom */ ++ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_DISABLE_AUTOSUSPEND), + DEVICE_FLG(0x07fd, 0x000b, /* MOTU M Series 2nd hardware revision */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x08bb, 0x2702, /* LineX FM Transmitter */ +@@ -2370,8 +2373,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x1038, 0x1294, /* SteelSeries Arctis Pro Wireless */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x1038, 0x2232, /* SteelSeries Arctis Nova 5 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */ +@@ -2379,8 +2380,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x1224, 0x2a25, /* Jieli Technology USB PHY 2.0 */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), +- DEVICE_FLG(0x1377, 0x6004, /* Sennheiser MOMENTUM 3 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x1395, 0x740a, /* Sennheiser DECT */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x1397, 0x0507, /* Behringer UMC202HD */ +@@ -2393,6 +2392,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_IFB_SILENCE_ON_EMPTY), + DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ + QUIRK_FLAG_IGNORE_CTL_ERROR), ++ DEVICE_FLG(0x152a, 0x85dd, /* SMSL USB DAC */ ++ QUIRK_FLAG_DSD_RAW | QUIRK_FLAG_DISABLE_AUTOSUSPEND | ++ QUIRK_FLAG_SKIP_IFACE_SETUP), + DEVICE_FLG(0x152a, 0x880a, /* NeuralDSP Quad Cortex */ + 0), /* Doesn't have the vendor quirk which would otherwise apply */ + DEVICE_FLG(0x1532, 0x055e, /* Razer Nommo V2 X */ +@@ -2505,8 +2507,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x2d99, 0x0026, /* HECATE G2 GAMING HEADSET */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x2d99, 0xa024, /* Edifier MF200 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x2fc6, 0xf06b, /* MOONDROP Moonriver2 Ti */ + QUIRK_FLAG_CTL_MSG_DELAY), + DEVICE_FLG(0x2fc6, 0xf0b5, /* iBasso DC-Elite */ +@@ -2647,7 +2647,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { + QUIRK_STRING_ENTRY(MIXER_PLAYBACK_LINEAR_VOL), + QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), + QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY), +- QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN), ++ QUIRK_STRING_ENTRY(MIXER_GET_CUR_OK), + QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), + QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + NULL +diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h +--- a/sound/usb/usbaudio.h ++++ b/sound/usb/usbaudio.h +@@ -243,24 +243,18 @@ extern bool snd_usb_skip_validation; + * from snd_usb_handle_sync_urb. Instead fall through and enqueue a + * packet_info containing only size-0 packets, so the OUT ring keeps + * moving (emits silence). Needed by Behringer Flow 8 (1397:050c). +- * QUIRK_FLAG_MIXER_GET_CUR_BROKEN +- * Some mixers are sticky, which means that setting their current volume is a +- * no-op, and reading the current volume returns a constant value. The sticky +- * check disables these mixers to prevent confusing userspace. However, some +- * devices do have a tunable volume despite the reported current volume being +- * constant. As the sticky check can't distinguish between the two categories, +- * setting this flag tells that the device should fall into the second +- * category when GET_CUR returns a constant value, resulting in the sticky +- * check being non-fatal and only disabling GET_CUR instead of the whole mixer. +- * The current volume will then be provided by the internal cache that stores +- * the last set volume ++ * QUIRK_FLAG_MIXER_GET_CUR_OK ++ * On some devices, whether their GET_CUR being sticky depends on whether ++ * hotpluggable components are present. When the hotpluggable components are ++ * missing on probe, their GET_CUR behavior is classified as broken. Set the ++ * flag to prevent the heuristics from gating GET_CUR. + * QUIRK_FLAG_PLAYBACK_URB_FIXUP + * Set URB_ISO_ASAP flag for isochronous URBs and force nurbs to MAX_URBS. + * This is needed for devices that exhibit boot-time audio stuttering due + * to insufficient buffer depth combined with xHCI scheduling variability. + * The larger buffer (MAX_URBS = 12, ~64ms) absorbs system scheduling + * jitter during boot, while URB_ISO_ASAP ensures consistent xHCI scheduling. +- * QUIRK_FLAG_ALWAYS_SET_RATE: ++ * QUIRK_FLAG_ALWAYS_SET_RATE + * Issue SET_CUR for the sample rate even when the clock already reports the + * requested rate. A device advertising a single rate is otherwise never sent + * the request at all, and some require it before streaming will start. +@@ -297,10 +291,10 @@ enum { + QUIRK_TYPE_MIXER_PLAYBACK_LINEAR_VOL = 27, + QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, + QUIRK_TYPE_IFB_SILENCE_ON_EMPTY = 29, +- QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, ++ QUIRK_TYPE_MIXER_GET_CUR_OK = 30, + QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, + QUIRK_TYPE_ALWAYS_SET_RATE = 32, +-/* Please also edit snd_usb_audio_quirk_flag_names */ ++/* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */ + }; + + #define QUIRK_FLAG(x) BIT_U64(QUIRK_TYPE_ ## x) +@@ -335,7 +329,7 @@ enum { + #define QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL QUIRK_FLAG(MIXER_PLAYBACK_LINEAR_VOL) + #define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) + #define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY QUIRK_FLAG(IFB_SILENCE_ON_EMPTY) +-#define QUIRK_FLAG_MIXER_GET_CUR_BROKEN QUIRK_FLAG(MIXER_GET_CUR_BROKEN) ++#define QUIRK_FLAG_MIXER_GET_CUR_OK QUIRK_FLAG(MIXER_GET_CUR_OK) + #define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) + #define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) + diff --git a/pkgbuilds/linux-omarchy-bore/0512-sound-fixes.patch b/pkgbuilds/linux-omarchy-bore/0512-sound-fixes.patch new file mode 100644 index 0000000..9566376 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0512-sound-fixes.patch @@ -0,0 +1,507 @@ +diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c +--- a/sound/core/pcm_native.c ++++ b/sound/core/pcm_native.c +@@ -4023,20 +4023,33 @@ int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file, + return -EINVAL; + } + runtime = substream->runtime; +- if (runtime->state == SNDRV_PCM_STATE_OPEN) +- return -EBADFD; +- if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) +- return -ENXIO; ++ /* don't race with buffer reallocation in hw_params/hw_free */ ++ if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) ++ return -EBUSY; ++ if (runtime->state == SNDRV_PCM_STATE_OPEN) { ++ err = -EBADFD; ++ goto out; ++ } ++ if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) { ++ err = -ENXIO; ++ goto out; ++ } + if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || +- runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) +- return -EINVAL; ++ runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) { ++ err = -EINVAL; ++ goto out; ++ } + size = area->vm_end - area->vm_start; + offset = area->vm_pgoff << PAGE_SHIFT; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); +- if ((size_t)size > dma_bytes) +- return -EINVAL; +- if (offset > dma_bytes - size) +- return -EINVAL; ++ if ((size_t)size > dma_bytes) { ++ err = -EINVAL; ++ goto out; ++ } ++ if (offset > dma_bytes - size) { ++ err = -EINVAL; ++ goto out; ++ } + + area->vm_ops = &snd_pcm_vm_ops_data; + area->vm_private_data = substream; +@@ -4046,6 +4059,8 @@ int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file, + err = snd_pcm_lib_default_mmap(substream, area); + if (!err) + atomic_inc(&substream->mmap_count); ++out: ++ atomic_dec(&runtime->buffer_accessing); + return err; + } + EXPORT_SYMBOL(snd_pcm_mmap_data); +diff --git a/sound/core/ump.c b/sound/core/ump.c +--- a/sound/core/ump.c ++++ b/sound/core/ump.c +@@ -1335,6 +1335,8 @@ static void update_legacy_names(struct snd_ump_endpoint *ump) + { + struct snd_rawmidi *rmidi = ump->legacy_rmidi; + ++ if (!rmidi) ++ return; + update_legacy_substreams(ump, rmidi, SNDRV_RAWMIDI_STREAM_INPUT); + update_legacy_substreams(ump, rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT); + } +@@ -1343,6 +1345,8 @@ static void ump_legacy_set_rawmidi_name(struct snd_ump_endpoint *ump) + { + struct snd_rawmidi *rmidi = ump->legacy_rmidi; + ++ if (!rmidi) ++ return; + snprintf(rmidi->name, sizeof(rmidi->name), "%.68s (MIDI 1.0)", + ump->core.name); + } +diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c +--- a/sound/drivers/dummy.c ++++ b/sound/drivers/dummy.c +@@ -807,7 +807,7 @@ static int snd_dummy_capsrc_put(struct snd_kcontrol *kcontrol, struct snd_ctl_el + left = ucontrol->value.integer.value[0] & 1; + right = ucontrol->value.integer.value[1] & 1; + guard(spinlock_irq)(&dummy->mixer_lock); +- change = dummy->capture_source[addr][0] != left && ++ change = dummy->capture_source[addr][0] != left || + dummy->capture_source[addr][1] != right; + dummy->capture_source[addr][0] = left; + dummy->capture_source[addr][1] = right; +diff --git a/sound/hda/codecs/cirrus/cs420x.c b/sound/hda/codecs/cirrus/cs420x.c +--- a/sound/hda/codecs/cirrus/cs420x.c ++++ b/sound/hda/codecs/cirrus/cs420x.c +@@ -571,6 +571,7 @@ static const struct hda_model_fixup cs4208_models[] = { + + static const struct hda_quirk cs4208_fixup_tbl[] = { + SND_PCI_QUIRK_VENDOR(0x106b, "Apple", CS4208_MAC_AUTO), ++ SND_PCI_QUIRK(0x8086, 0x7270, "MacBookAir 7,2", CS4208_MAC_AUTO), + {} /* terminator */ + }; + +@@ -583,6 +584,7 @@ static const struct hda_quirk cs4208_mac_fixup_tbl[] = { + SND_PCI_QUIRK(0x106b, 0x7800, "MacPro 6,1", CS4208_MACMINI), + SND_PCI_QUIRK(0x106b, 0x7b00, "MacBookPro 12,1", CS4208_MBP11), + SND_PCI_QUIRK(0x106b, 0x7f00, "iMac 16,1", CS4208_MBP11), ++ SND_PCI_QUIRK(0x8086, 0x7270, "MacBookAir 7,2", CS4208_MBA6), + {} /* terminator */ + }; + +diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c +--- a/sound/hda/codecs/conexant.c ++++ b/sound/hda/codecs/conexant.c +@@ -249,6 +249,25 @@ static void cx_update_headset_mic_vref(struct hda_codec *codec, struct hda_jack_ + } + } + ++#define SN6140_S3_AFG_D0_DELAY_MS 1000 ++ ++static void cx_set_power_state(struct hda_codec *codec, hda_nid_t fg, ++ unsigned int power_state) ++{ ++ snd_hda_codec_write_sync(codec, fg, 0, AC_VERB_SET_POWER_STATE, power_state); ++ ++ /* ++ * SN6140 may not respond to AFG D0 immediately after S3. ++ * Wait before the D0 verb so the power-state command itself succeeds. ++ */ ++ if (codec->core.vendor_id == 0x14f11f87 && ++ power_state == AC_PWRST_D0 && ++ codec->core.dev.power.power_state.event == PM_EVENT_RESUME) ++ msleep(SN6140_S3_AFG_D0_DELAY_MS); ++ ++ snd_hda_codec_set_power_to_all(codec, fg, power_state); ++} ++ + static int cx_suspend(struct hda_codec *codec) + { + cx_auto_shutdown(codec); +@@ -1308,6 +1327,7 @@ static const struct hda_codec_ops cx_codec_ops = { + .init = cx_init, + .unsol_event = snd_hda_jack_unsol_event, + .suspend = cx_suspend, ++ .set_power_state = cx_set_power_state, + .check_power_status = snd_hda_gen_check_power_status, + .stream_pm = snd_hda_gen_stream_pm, + }; +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -2379,6 +2379,33 @@ static void alc_fixup_headset_mode_alc255_no_hp_mic(struct hda_codec *codec, + } + } + ++/* ++ * On the Acer Aspire A515-57G (and possibly other models sharing this ++ * board), if headphones are already inserted into the combo jack before ++ * the codec powers up (cold boot), the impedance-based headset-type ++ * sensing races and misclassifies the jack, driving the wrong output ++ * configuration (audible as missing center-panned/vocal content). A ++ * genuine physical unplug/replug after boot fixes it by forcing a fresh ++ * sense transient. Mirror that here on cold boot only: give the sense ++ * hardware time to settle, then force a fresh classification. ++ */ ++static void alc_fixup_headset_mode_acer_coldboot(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ struct alc_spec *spec = codec->spec; ++ ++ alc_fixup_headset_mode(codec, fix, action); ++ ++ if (action == HDA_FIXUP_ACT_INIT && ++ !is_s3_resume(codec) && !is_s4_resume(codec) && ++ spec->current_headset_mode != ALC_HEADSET_MODE_UNPLUGGED) { ++ msleep(500); ++ spec->current_headset_mode = ALC_HEADSET_MODE_UNKNOWN; ++ spec->current_headset_type = ALC_HEADSET_TYPE_UNKNOWN; ++ alc_fixup_headset_mode(codec, fix, action); ++ } ++} ++ + static void alc288_update_headset_jack_cb(struct hda_codec *codec, + struct hda_jack_callback *jack) + { +@@ -4248,6 +4275,7 @@ enum { + ALC282_FIXUP_ACER_DISABLE_LINEOUT, + ALC255_FIXUP_ACER_LIMIT_INT_MIC_BOOST, + ALC256_FIXUP_ACER_HEADSET_MIC, ++ ALC256_FIXUP_ACER_COLDBOOT, + ALC285_FIXUP_IDEAPAD_S740_COEF, + ALC285_FIXUP_HP_LIMIT_INT_MIC_BOOST, + ALC295_FIXUP_ASUS_DACS, +@@ -6311,6 +6339,12 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, ++ [ALC256_FIXUP_ACER_COLDBOOT] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_headset_mode_acer_coldboot, ++ .chained = true, ++ .chain_id = ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, ++ }, + [ALC285_FIXUP_IDEAPAD_S740_COEF] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_ideapad_s740_coef, +@@ -7148,13 +7182,14 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1597, "Acer Nitro 5 AN517-55", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x159e, "Acer Nitro 5 AN515-46", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), +- SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_COLDBOOT), + SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x1731, "Acer Predator PHN16-72", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x173a, "Acer Swift SFG14-73", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1758, "Acer Nitro ANV15-41", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), +@@ -8100,6 +8135,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3801, "Lenovo Yoga9 14IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x3802, "DuetITL 2021", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3802, "Lenovo Yoga Pro 9 14IRP8", ALC287_FIXUP_TAS2781_I2C), ++ SND_PCI_QUIRK(0x17aa, 0x380b, "Lenovo Yoga Slim 9 14ILL10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + /* Yoga Pro 9 16IMH9 and Legion 7 16ITHG6 share PCI SSID 17aa:3811 + * with Legion S7 15IMH05; use codec SSID to distinguish them + */ +@@ -8287,6 +8323,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1d05, 0x3034, "TongFang X6KK45xU", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1d05, 0x30ba, "TongFang XxAF5xxx", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1d17, 0x3288, "Haier Boyue G42", ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS), ++ SND_PCI_QUIRK(0x1d19, 0x0006, "VAIO VJS131", ALC233_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1d72, 0x1602, "RedmiBook", ALC255_FIXUP_XIAOMI_HEADSET_MIC), + SND_PCI_QUIRK(0x1d72, 0x1701, "XiaomiNotebook Pro", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1d72, 0x1901, "RedmiBook 14", ALC256_FIXUP_ASUS_HEADSET_MIC), +diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c +--- a/sound/hda/core/device.c ++++ b/sound/hda/core/device.c +@@ -404,6 +404,7 @@ static void setup_fg_nodes(struct hdac_device *codec) + */ + int snd_hdac_refresh_widgets(struct hdac_device *codec) + { ++ hda_nid_t fg = codec->afg ? codec->afg : codec->mfg; + hda_nid_t start_nid; + int nums, err = 0; + +@@ -412,10 +413,10 @@ int snd_hdac_refresh_widgets(struct hdac_device *codec) + * widgets array. + */ + guard(mutex)(&codec->widget_lock); +- nums = snd_hdac_get_sub_nodes(codec, codec->afg, &start_nid); ++ nums = snd_hdac_get_sub_nodes(codec, fg, &start_nid); + if (!start_nid || nums <= 0 || nums >= 0xff) { + dev_err(&codec->dev, "cannot read sub nodes for FG 0x%02x\n", +- codec->afg); ++ fg); + return -EINVAL; + } + +diff --git a/sound/usb/caiaq/audio.c b/sound/usb/caiaq/audio.c +--- a/sound/usb/caiaq/audio.c ++++ b/sound/usb/caiaq/audio.c +@@ -828,16 +828,13 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev) + + cdev->data_urbs_in = alloc_urbs(cdev, SNDRV_PCM_STREAM_CAPTURE, &ret); + if (ret < 0) { +- kfree(cdev->data_cb_info); +- free_urbs(cdev->data_urbs_in); ++ snd_usb_caiaq_audio_free(cdev); + return ret; + } + + cdev->data_urbs_out = alloc_urbs(cdev, SNDRV_PCM_STREAM_PLAYBACK, &ret); + if (ret < 0) { +- kfree(cdev->data_cb_info); +- free_urbs(cdev->data_urbs_in); +- free_urbs(cdev->data_urbs_out); ++ snd_usb_caiaq_audio_free(cdev); + return ret; + } + +@@ -858,6 +855,9 @@ void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev) + + dev_dbg(dev, "%s(%p)\n", __func__, cdev); + free_urbs(cdev->data_urbs_in); ++ cdev->data_urbs_in = NULL; + free_urbs(cdev->data_urbs_out); ++ cdev->data_urbs_out = NULL; + kfree(cdev->data_cb_info); ++ cdev->data_cb_info = NULL; + } +diff --git a/sound/usb/fcp.c b/sound/usb/fcp.c +--- a/sound/usb/fcp.c ++++ b/sound/usb/fcp.c +@@ -191,6 +191,10 @@ static int fcp_usb(struct usb_mixer_interface *mixer, u32 opcode, + const int max_retries = 5; + int err; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + if (!private->urb) + return -ENODEV; + +@@ -1026,6 +1030,10 @@ static int fcp_init(struct usb_mixer_interface *mixer, + struct usb_device *dev = mixer->chip->dev; + int err; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + FCP_USB_REQ_STEP0, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -518,6 +518,19 @@ static const struct usbmix_name_map audient_id14_map[] = { + {} + }; + ++/* ++ * Audient iD24: feature unit 12 ("Speaker Playback Volume") sits in the ++ * monitor-mixer branch and does not apply volume to all of its channels; ++ * when userspace adopts it as the master playback volume, the left main ++ * output stays at 0 dB while the right one is attenuated, producing a ++ * stereo imbalance. Rename it so that it is not picked up as the ++ * stream's master volume control. ++ */ ++static const struct usbmix_name_map audient_id24_map[] = { ++ { 12, "Monitor Mix Playback" }, /* FU, partial channel coverage */ ++ {} ++}; ++ + /* + * Control map entries + */ +@@ -611,6 +624,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x2708, 0x0008), + .map = audient_id14_map, + }, ++ { ++ /* Audient iD24 */ ++ .id = USB_ID(0x2708, 0x000d), ++ .map = audient_id24_map, ++ }, + { + /* KEF X300A */ + .id = USB_ID(0x27ac, 0x1000), +diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c +--- a/sound/usb/mixer_quirks.c ++++ b/sound/usb/mixer_quirks.c +@@ -3480,6 +3480,10 @@ static int snd_rme_digiface_write_reg(struct snd_kcontrol *kcontrol, int item, u + struct usb_device *dev = chip->dev; + int err; + ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + item, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, +@@ -3499,6 +3503,10 @@ static int snd_rme_digiface_read_status(struct snd_kcontrol *kcontrol, u32 statu + __le32 buf[4] = {}; + int err; + ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + RME_DIGIFACE_READ_STATUS, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, +diff --git a/sound/usb/mixer_s1810c.c b/sound/usb/mixer_s1810c.c +--- a/sound/usb/mixer_s1810c.c ++++ b/sound/usb/mixer_s1810c.c +@@ -474,6 +474,10 @@ snd_s1810c_switch_get(struct snd_kcontrol *kctl, + u32 state = 0; + int ret; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + guard(mutex)(&private->data_mutex); + ret = snd_s1810c_get_switch_state(mixer, kctl, &state); + if (ret < 0) +@@ -504,6 +508,10 @@ snd_s1810c_switch_set(struct snd_kcontrol *kctl, + u32 newval = 0; + int ret = 0; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + guard(mutex)(&private->data_mutex); + ret = snd_s1810c_get_switch_state(mixer, kctl, &curval); + if (ret < 0) +diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c +--- a/sound/usb/mixer_scarlett.c ++++ b/sound/usb/mixer_scarlett.c +@@ -707,6 +707,10 @@ static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, + int idx = snd_usb_ctrl_intf(elem->head.mixer->hostif) | (elem->head.id << 8); + int err; + ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_MEM, +diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c +--- a/sound/usb/mixer_scarlett2.c ++++ b/sound/usb/mixer_scarlett2.c +@@ -2603,9 +2603,9 @@ static int scarlett2_usb_rx(struct usb_device *dev, int interface, + } + + /* Send a proprietary format request to the Scarlett interface */ +-static int scarlett2_usb( +- struct usb_mixer_interface *mixer, u32 cmd, +- void *req_data, u16 req_size, void *resp_data, u16 resp_size) ++static int scarlett2_usb_nopm(struct usb_mixer_interface *mixer, u32 cmd, ++ void *req_data, u16 req_size, ++ void *resp_data, u16 resp_size) + { + struct scarlett2_data *private = mixer->private_data; + struct usb_device *dev = mixer->chip->dev; +@@ -2713,6 +2713,18 @@ static int scarlett2_usb( + return err; + } + ++static int scarlett2_usb(struct usb_mixer_interface *mixer, u32 cmd, ++ void *req_data, u16 req_size, ++ void *resp_data, u16 resp_size) ++{ ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ ++ return scarlett2_usb_nopm(mixer, cmd, req_data, req_size, ++ resp_data, resp_size); ++} ++ + /* Send a USB message to get data; result placed in *buf */ + static int scarlett2_usb_get( + struct usb_mixer_interface *mixer, +@@ -3020,9 +3032,21 @@ static int scarlett2_usb_set_config_buf( + /* Send SCARLETT2_USB_DATA_CMD SCARLETT2_USB_CONFIG_SAVE */ + static void scarlett2_config_save(struct usb_mixer_interface *mixer) + { +- int err; ++ __le32 req = cpu_to_le32(SCARLETT2_USB_CONFIG_SAVE); ++ int err = scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD, ++ &req, sizeof(req), NULL, 0); ++ ++ if (err < 0) ++ usb_audio_err(mixer->chip, "config save failed: %d\n", err); ++} ++ ++/* The USB suspend callback must not acquire another PM reference. */ ++static void scarlett2_config_save_nopm(struct usb_mixer_interface *mixer) ++{ ++ __le32 req = cpu_to_le32(SCARLETT2_USB_CONFIG_SAVE); ++ int err = scarlett2_usb_nopm(mixer, SCARLETT2_USB_DATA_CMD, ++ &req, sizeof(req), NULL, 0); + +- err = scarlett2_usb_activate_config(mixer, SCARLETT2_USB_CONFIG_SAVE); + if (err < 0) + usb_audio_err(mixer->chip, "config save failed: %d\n", err); + } +@@ -8639,7 +8663,7 @@ static void scarlett2_private_suspend(struct usb_mixer_interface *mixer) + struct scarlett2_data *private = mixer->private_data; + + if (cancel_delayed_work_sync(&private->work)) +- scarlett2_config_save(private->mixer); ++ scarlett2_config_save_nopm(private->mixer); + + scarlett2_cleanup_urb(mixer); + } +diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c +--- a/sound/usb/mixer_us16x08.c ++++ b/sound/usb/mixer_us16x08.c +@@ -151,6 +151,9 @@ static const char *const route_names[] = { + static int snd_us16x08_recv_urb(struct snd_usb_audio *chip, + unsigned char *buf, int size) + { ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; + + guard(mutex)(&chip->mutex); + snd_usb_ctl_msg(chip->dev, +@@ -165,6 +168,10 @@ static int snd_us16x08_recv_urb(struct snd_usb_audio *chip, + */ + static int snd_us16x08_send_urb(struct snd_usb_audio *chip, char *buf, int size) + { ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + return snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), + SND_US16X08_URB_REQUEST, SND_US16X08_URB_REQUESTTYPE, + 0, 0, buf, size); diff --git a/pkgbuilds/linux-omarchy-bore/0513-xps13-sof-quirk.patch b/pkgbuilds/linux-omarchy-bore/0513-xps13-sof-quirk.patch new file mode 100644 index 0000000..6a5a07c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0513-xps13-sof-quirk.patch @@ -0,0 +1,19 @@ +diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c +--- a/sound/soc/intel/boards/sof_sdw.c ++++ b/sound/soc/intel/boards/sof_sdw.c +@@ -845,6 +845,15 @@ static const struct dmi_system_id sof_sdw_quirk_table[] = { + }, + .driver_data = (void *)(SOC_SDW_PCH_DMIC), + }, ++ { ++ /* Dell XPS 13 DX13260 (Wildcat Lake), CS42L43 + 2x CS35L63 sidecar amps */ ++ .callback = sof_sdw_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0E53") ++ }, ++ .driver_data = (void *)(SOC_SDW_SIDECAR_AMPS), ++ }, + {} + }; + diff --git a/pkgbuilds/linux-omarchy-bore/0514-rt766-stream-config-type.patch b/pkgbuilds/linux-omarchy-bore/0514-rt766-stream-config-type.patch new file mode 100644 index 0000000..25fee81 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0514-rt766-stream-config-type.patch @@ -0,0 +1,30 @@ +diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c +--- a/sound/soc/codecs/rt766-sdca.c ++++ b/sound/soc/codecs/rt766-sdca.c +@@ -936,9 +936,8 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; + unsigned int sampling_rate; + int retval, port; +@@ -957,7 +956,6 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + + /* SoundWire specific configuration */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; + if (dai->id == RT766_AIF1) + port = 3; + else if (dai->id == RT766_AIF2) +@@ -965,7 +963,6 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } else { +- direction = SDW_DATA_DIR_TX; + if (dai->id == RT766_AIF1) + port = 12; + else if (dai->id == RT766_AIF3) diff --git a/pkgbuilds/linux-omarchy-bore/0516-hda-realtek-rog-strix-g733zw-speakers.patch b/pkgbuilds/linux-omarchy-bore/0516-hda-realtek-rog-strix-g733zw-speakers.patch new file mode 100644 index 0000000..47dae22 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0516-hda-realtek-rog-strix-g733zw-speakers.patch @@ -0,0 +1,39 @@ +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -4226,6 +4226,7 @@ enum { + ALC294_FIXUP_ASUS_GU502_VERBS, + ALC294_FIXUP_ASUS_G513_PINS, + ALC285_FIXUP_ASUS_G533Z_PINS, ++ ALC285_FIXUP_ASUS_G733Z_VERBS, + ALC285_FIXUP_HP_GPIO_LED, + ALC285_FIXUP_HP_MUTE_LED, + ALC285_FIXUP_HP_SPECTRE_X360_MUTE_LED, +@@ -7129,6 +7130,19 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC287_FIXUP_TXNW2781_I2C, + }, ++ [ALC285_FIXUP_ASUS_G733Z_VERBS] = { ++ .type = HDA_FIXUP_VERBS, ++ .v.verbs = (const struct hda_verb[]) { ++ /* Enables internal speaker */ ++ {0x17, AC_VERB_SET_CONNECT_SEL, 0x00}, ++ {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, ++ {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, ++ {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, ++ {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00}, ++ {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00}, ++ { } ++ } ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -7775,6 +7789,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x12a3, "Asus N7691ZM", ALC269_FIXUP_ASUS_N7601ZM), + SND_PCI_QUIRK(0x1043, 0x12af, "ASUS UX582ZS", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x12b4, "ASUS B3405CCA / P3405CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), ++ SND_PCI_QUIRK(0x1043, 0x12bf, "ASUS ROG Strix G733ZW", ALC285_FIXUP_ASUS_G733Z_VERBS), + SND_PCI_QUIRK(0x1043, 0x12e0, "ASUS X541SA", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x12f0, "ASUS X541UV", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1313, "Asus K42JZ", ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE), diff --git a/pkgbuilds/linux-omarchy-bore/0517-asoc-amd-yc-acer-aspire-a314-23p.patch b/pkgbuilds/linux-omarchy-bore/0517-asoc-amd-yc-acer-aspire-a314-23p.patch new file mode 100644 index 0000000..0d8e619 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0517-asoc-amd-yc-acer-aspire-a314-23p.patch @@ -0,0 +1,17 @@ +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -619,6 +619,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFA16-41"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "MDU"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A314-23P"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { diff --git a/pkgbuilds/linux-omarchy-bore/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch b/pkgbuilds/linux-omarchy-bore/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch new file mode 100644 index 0000000..3762b2e --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch @@ -0,0 +1,41 @@ +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -173,6 +173,19 @@ static const struct acpi_device_id ivsc_acpi_ids[] = { + { "INTC10E1" }, /* PTL */ + }; + ++/* ++ * The subset of ivsc_acpi_ids[] which are IVSC, rather than CVS, devices. The ++ * CVS IDs are deliberately not listed here: new ones keep being added, whereas ++ * this list is complete. ++ */ ++static const struct acpi_device_id ivsc_only_acpi_ids[] = { ++ { "INTC1059" }, ++ { "INTC1095" }, ++ { "INTC100A" }, ++ { "INTC10CF" }, ++ { } ++}; ++ + static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev) + { + unsigned int i; +@@ -224,6 +237,17 @@ static struct device *ipu_bridge_get_ivsc_csi_dev(struct acpi_device *adev) + return csi_dev; + } + ++ /* ++ * The lookups below match on the ACPI companion alone. That is fine for ++ * CVS, which binds a driver to that very device, but not for IVSC: there ++ * the ACPI device also has a driverless platform device, which would be ++ * returned instead of the mei-csi client. Return NULL for IVSC so that ++ * the caller fails and the probe is retried once the IVSC device shows ++ * up. ++ */ ++ if (!acpi_match_device_ids(adev, ivsc_only_acpi_ids)) ++ return NULL; ++ + /* Try to locate CVS device on the I2C bus */ + csi_dev = bus_find_device_by_acpi_dev(&i2c_bus_type, adev); + if (csi_dev) diff --git a/pkgbuilds/linux-omarchy-bore/0541-cvs-nova-lake-acpi-id.patch b/pkgbuilds/linux-omarchy-bore/0541-cvs-nova-lake-acpi-id.patch new file mode 100644 index 0000000..39ccf4d --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0541-cvs-nova-lake-acpi-id.patch @@ -0,0 +1,33 @@ +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -862,6 +862,7 @@ static const char * const acpi_honor_dep_ids[] = { + "INTC10DE", /* CVS (LNL) driver must be loaded to allow camera streaming */ + "INTC10E0", /* CVS (ARL) driver must be loaded to allow camera streaming */ + "INTC10E1", /* CVS (PTL) driver must be loaded to allow camera streaming */ ++ "INTC10FA", /* CVS (NVL) driver must be loaded to allow camera streaming */ + "RSCV0001", /* RISC-V PLIC */ + "RSCV0002", /* RISC-V APLIC */ + "RSCV0005", /* RISC-V SBI MPXY MBOX */ +diff --git a/drivers/media/i2c/cvs/core.c b/drivers/media/i2c/cvs/core.c +--- a/drivers/media/i2c/cvs/core.c ++++ b/drivers/media/i2c/cvs/core.c +@@ -962,6 +962,7 @@ static const struct acpi_device_id intel_cvs_acpi_match[] = { + { "INTC10DE" }, /* LNL */ + { "INTC10E0" }, /* ARL */ + { "INTC10E1" }, /* PTL */ ++ { "INTC10FA" }, /* NVL */ + { } + }; + MODULE_DEVICE_TABLE(acpi, intel_cvs_acpi_match); +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -171,6 +171,7 @@ static const struct acpi_device_id ivsc_acpi_ids[] = { + { "INTC10DE" }, /* LNL */ + { "INTC10E0" }, /* ARL */ + { "INTC10E1" }, /* PTL */ ++ { "INTC10FA" }, /* NVL */ + }; + + /* diff --git a/pkgbuilds/linux-omarchy-bore/0542-media-cvs-wake-irq-without-claiming-gpio.patch b/pkgbuilds/linux-omarchy-bore/0542-media-cvs-wake-irq-without-claiming-gpio.patch new file mode 100644 index 0000000..6a78d2e --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0542-media-cvs-wake-irq-without-claiming-gpio.patch @@ -0,0 +1,33 @@ +diff --git a/drivers/media/i2c/cvs/core.c b/drivers/media/i2c/cvs/core.c +--- a/drivers/media/i2c/cvs/core.c ++++ b/drivers/media/i2c/cvs/core.c +@@ -723,8 +723,6 @@ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c) + } + + if (ctx->res == ICVS_FULLCAP) { +- struct gpio_desc *wake; +- + ctx->rst = devm_gpiod_get(dev, "rst", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->rst)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->rst), +@@ -732,14 +730,12 @@ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c) + goto err_put_ipu; + } + +- wake = devm_gpiod_get(dev, "wake", GPIOD_IN); +- if (IS_ERR(wake)) { +- ret = dev_err_probe(dev, PTR_ERR(wake), +- "failed to get wake GPIO\n"); +- goto err_put_ipu; +- } +- +- ctx->irq = gpiod_to_irq(wake); ++ /* ++ * Do not request the line: another device's _CRS may list ++ * the same pin, and its driver would then fail with -EBUSY. ++ */ ++ ctx->irq = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(dev), ++ "wake", 0); + if (ctx->irq < 0) { + ret = dev_err_probe(dev, ctx->irq, + "failed to get wake IRQ\n"); diff --git a/pkgbuilds/linux-omarchy-bore/0560-input.patch b/pkgbuilds/linux-omarchy-bore/0560-input.patch new file mode 100644 index 0000000..0d58569 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0560-input.patch @@ -0,0 +1,76 @@ +diff --git a/drivers/input/mouse/focaltech.c b/drivers/input/mouse/focaltech.c +--- a/drivers/input/mouse/focaltech.c ++++ b/drivers/input/mouse/focaltech.c +@@ -78,8 +78,8 @@ struct focaltech_finger_state { + * Absolute position (from the bottom left corner) of the + * finger. + */ +- unsigned int x; +- unsigned int y; ++ int x; ++ int y; + }; + + /* +@@ -108,7 +108,7 @@ struct focaltech_hw_state { + }; + + struct focaltech_data { +- unsigned int x_max, y_max; ++ int x_max, y_max; + struct focaltech_hw_state state; + }; + +@@ -126,17 +126,16 @@ static void focaltech_report_state(struct psmouse *psmouse) + input_mt_slot(dev, i); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, active); + if (active) { +- unsigned int clamped_x, clamped_y; + /* + * The touchpad might report invalid data, so we clamp + * the resulting values so that we do not confuse +- * userspace. ++ * userspace or accumulate coordinate wind-up. + */ +- clamped_x = clamp(finger->x, 0U, priv->x_max); +- clamped_y = clamp(finger->y, 0U, priv->y_max); +- input_report_abs(dev, ABS_MT_POSITION_X, clamped_x); ++ finger->x = clamp(finger->x, 0, priv->x_max); ++ finger->y = clamp(finger->y, 0, priv->y_max); ++ input_report_abs(dev, ABS_MT_POSITION_X, finger->x); + input_report_abs(dev, ABS_MT_POSITION_Y, +- priv->y_max - clamped_y); ++ priv->y_max - finger->y); + input_report_abs(dev, ABS_TOOL_WIDTH, state->width); + } + } +diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c +--- a/drivers/input/mouse/psmouse-base.c ++++ b/drivers/input/mouse/psmouse-base.c +@@ -267,7 +267,15 @@ void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) + */ + static int psmouse_handle_byte(struct psmouse *psmouse) + { +- psmouse_ret_t rc = psmouse->protocol_handler(psmouse); ++ psmouse_ret_t rc; ++ ++ /* protocol_handler is NULL when device is being disconnected */ ++ if (unlikely(!psmouse->protocol_handler)) { ++ psmouse->pktcnt = 0; ++ return 0; ++ } ++ ++ rc = psmouse->protocol_handler(psmouse); + + switch (rc) { + case PSMOUSE_BAD_DATA: +@@ -1466,6 +1474,9 @@ static void psmouse_disconnect(struct serio *serio) + psmouse_deactivate(parent); + } + ++ scoped_guard(serio_pause_rx, serio) ++ psmouse->protocol_handler = NULL; ++ + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + diff --git a/pkgbuilds/linux-omarchy-bore/0565-i2c-asue140d-touchpad-100khz.patch b/pkgbuilds/linux-omarchy-bore/0565-i2c-asue140d-touchpad-100khz.patch new file mode 100644 index 0000000..01748b9 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0565-i2c-asue140d-touchpad-100khz.patch @@ -0,0 +1,11 @@ +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -373,6 +373,7 @@ static const struct acpi_device_id i2c_acpi_force_100khz_device_ids[] = { + * the device works without issues on Windows at what is expected to be + * a 400KHz frequency. The root cause of the issue is not known. + */ ++ { "ASUE140D", 0 }, + { "DLL0945", 0 }, + { "ELAN0678", 0 }, + { "ELAN06FA", 0 }, diff --git a/pkgbuilds/linux-omarchy-bore/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch b/pkgbuilds/linux-omarchy-bore/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch new file mode 100644 index 0000000..e569f4c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch @@ -0,0 +1,24 @@ +diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c +--- a/drivers/hid/hid-asus.c ++++ b/drivers/hid/hid-asus.c +@@ -1294,12 +1294,14 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) + return ret; + } + +- for (int r = 0; r < ARRAY_SIZE(asus_report_id_init); r++) { +- if (asus_has_report_id(hdev, asus_report_id_init[r])) { +- ret = asus_kbd_init(hdev, asus_report_id_init[r]); +- if (ret < 0) +- hid_warn(hdev, "Failed to initialize 0x%x: %d.\n", +- asus_report_id_init[r], ret); ++ if (!drvdata->tp) { ++ for (int r = 0; r < ARRAY_SIZE(asus_report_id_init); r++) { ++ if (asus_has_report_id(hdev, asus_report_id_init[r])) { ++ ret = asus_kbd_init(hdev, asus_report_id_init[r]); ++ if (ret < 0) ++ hid_warn(hdev, "Failed to initialize 0x%x: %d.\n", ++ asus_report_id_init[r], ret); ++ } + } + } + diff --git a/pkgbuilds/linux-omarchy-bore/0600-usb4stream-fixes.patch b/pkgbuilds/linux-omarchy-bore/0600-usb4stream-fixes.patch new file mode 100644 index 0000000..5fe836e --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0600-usb4stream-fixes.patch @@ -0,0 +1,47 @@ +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +index 4cc86d8d6491..43d29d69dcdb 100644 +--- a/drivers/thunderbolt/stream.c ++++ b/drivers/thunderbolt/stream.c +@@ -512,8 +512,10 @@ tbstream_dev_alloc_tx(struct tbstream_dev *sdev, enum tbstream_frame_pdf pdf, + dma_sync_single_for_cpu(dma_dev, sf->frame.buffer_phy, size, + DMA_TO_DEVICE); + if (pdf == TBSTREAM_DATA) { +- if (copy_page_from_iter(sf->page, 0, size, from) != size) ++ if (copy_page_from_iter(sf->page, 0, size, from) != size) { ++ sdev->tx_ring.cons--; + return ERR_PTR(-EFAULT); ++ } + } else { + memset(page_address(sf->page), 0, size); + } +@@ -671,7 +673,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + } + + nbytes = 0; +- while (nbytes < iov_iter_count(to)) { ++ while (iov_iter_count(to)) { + struct tbstream_frame *sf; + size_t size, sf_size; + +@@ -693,7 +695,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + } + + sf_size = tb_ring_frame_size(&sf->frame); +- size = min(iov_iter_count(to) - nbytes, sf_size); ++ size = min(iov_iter_count(to), sf_size); + + if (copy_page_to_iter(sf->page, sf->offset, size, to) != size) { + ret = -EFAULT; +@@ -763,10 +765,10 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + } + + nbytes = 0; +- while (nbytes < iov_iter_count(from)) { ++ while (iov_iter_count(from)) { + size_t size; + +- size = min(iov_iter_count(from) - nbytes, TB_MAX_FRAME_SIZE); ++ size = min(iov_iter_count(from), TB_MAX_FRAME_SIZE); + ret = tbstream_dev_send_data(sdev, from, size); + if (ret) { + /* diff --git a/pkgbuilds/linux-omarchy-bore/0601-usb4stream-busy-poll.patch b/pkgbuilds/linux-omarchy-bore/0601-usb4stream-busy-poll.patch new file mode 100644 index 0000000..0cb7d2a --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0601-usb4stream-busy-poll.patch @@ -0,0 +1,477 @@ +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +index 35e3c119d5ee..a4816db5cacd 100644 +--- a/drivers/thunderbolt/nhi.c ++++ b/drivers/thunderbolt/nhi.c +@@ -235,6 +235,12 @@ static void ring_write_descriptors(struct tb_ring *ring) + { + struct ring_frame *frame, *n; + struct ring_desc *descriptor; ++ u32 flags; ++ ++ flags = RING_DESC_POSTED; ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ flags |= RING_DESC_INTERRUPT; ++ + list_for_each_entry_safe(frame, n, &ring->queue, list) { + if (ring_full(ring)) + break; +@@ -242,7 +248,7 @@ static void ring_write_descriptors(struct tb_ring *ring) + descriptor = &ring->descriptors[ring->head]; + descriptor->phys = frame->buffer_phy; + descriptor->time = 0; +- descriptor->flags = RING_DESC_POSTED | RING_DESC_INTERRUPT; ++ descriptor->flags = flags; + if (ring->is_tx) { + descriptor->length = frame->size; + descriptor->eof = frame->eof; +@@ -339,8 +345,9 @@ EXPORT_SYMBOL_GPL(__tb_ring_enqueue); + * @ring: Ring to poll + * + * 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. ++ * has been called or the ring is created with %RING_FLAG_NO_INTERRUPT. ++ * It will read one completed frame from the ring and return it to the ++ * caller. + * + * Return: Pointer to &struct ring_frame, %NULL if there is no more + * completed frames. +@@ -538,6 +545,12 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + dev_dbg(nhi->dev, "allocating %s ring %d of size %d\n", + transmit ? "TX" : "RX", hop, size); + ++ if ((flags & RING_FLAG_NO_INTERRUPT) && start_poll) { ++ dev_WARN(nhi->dev, ++ "start_poll() and NO_INTERRUPT cannot be used at the same time\n"); ++ return NULL; ++ } ++ + ring = kzalloc_obj(*ring); + if (!ring) + return NULL; +@@ -568,7 +581,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + if (!ring->descriptors) + goto err_free_ring; + +- if (nhi->ops->request_ring_irq) { ++ if (!(flags & RING_FLAG_NO_INTERRUPT) && nhi->ops->request_ring_irq) { + if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND)) + goto err_free_descs; + } +@@ -701,7 +714,8 @@ void tb_ring_start(struct tb_ring *ring) + ring_iowrite32options(ring, flags, 0); + } + +- ring_interrupt_active(ring, true); ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ ring_interrupt_active(ring, true); + ring->running = true; + err: + spin_unlock(&ring->lock); +@@ -761,7 +775,8 @@ void tb_ring_stop(struct tb_ring *ring) + RING_TYPE(ring), ring->hop); + goto err; + } +- ring_interrupt_active(ring, false); ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ ring_interrupt_active(ring, false); + + ring_iowrite32options(ring, 0, 0); + ring_iowrite64desc(ring, 0, 0); +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +index 43d29d69dcdb..a7d9fda66875 100644 +--- a/drivers/thunderbolt/stream.c ++++ b/drivers/thunderbolt/stream.c +@@ -9,10 +9,12 @@ + + #define pr_fmt(fmt) "tbstream: " fmt + ++#include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -128,6 +130,7 @@ struct tbstream_ring { + * @out_hopid: Out HopID + * @ring_size: Size of the rings + * @throttling: Interrupt throttling rate in ns ++ * @busy_poll: Instead of interrupts, busy poll the rings + * @users: Number of times @cdev has been opened + * @closed: CLOSE packet was received + * @removed: Userspace removed the ConfigFS group underneath. +@@ -147,6 +150,7 @@ struct tbstream_dev { + int out_hopid; + unsigned int ring_size; + unsigned int throttling; ++ bool busy_poll; + int users; + bool closed; + bool removed; +@@ -536,10 +540,39 @@ tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from, + return tb_ring_tx(sdev->tx_ring.ring, &sf->frame); + } + ++static void ++tbstream_dev_poll_ring(struct tbstream_dev *sdev, struct tbstream_ring *ring) ++{ ++ struct ring_frame *frame; ++ ++ if (!sdev->busy_poll) ++ return; ++ ++ while ((frame = tb_ring_poll(ring->ring))) ++ frame->callback(ring->ring, frame, false); ++} ++ + static int tbstream_dev_send_close(struct tbstream_dev *sdev) + { + struct tbstream_frame *sf; + ++ if (sdev->busy_poll) { ++ /* ++ * When busy polling it's the write(2) path that ++ * advances the completions so it is possible that the ++ * ring is full at this point. Advance the ring here so ++ * that there is room for the CLOSE packet to be sent. ++ */ ++ ktime_t timeout = ktime_add_ms(ktime_get(), 500); ++ ++ do { ++ if (tbstream_ring_available(&sdev->tx_ring)) ++ break; ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); ++ fsleep(15); ++ } while (ktime_before(ktime_get(), timeout)); ++ } ++ + sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_CLOSE, NULL, SZ_256); + if (IS_ERR(sf)) + return PTR_ERR(sf); +@@ -549,12 +582,15 @@ static int tbstream_dev_send_close(struct tbstream_dev *sdev) + static int tbstream_dev_start(struct tbstream_dev *sdev) + { + struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); ++ unsigned int flags = RING_FLAG_FRAME | RING_FLAG_E2E; + u16 sof_mask, eof_mask; + struct tb_ring *ring; + int ret, e2e_tx_hop; + +- ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, +- RING_FLAG_FRAME | RING_FLAG_E2E); ++ if (sdev->busy_poll) ++ flags |= RING_FLAG_NO_INTERRUPT; ++ ++ ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, flags); + if (!ring) + return -ENOMEM; + sdev->tx_ring.ring = ring; +@@ -567,9 +603,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) + sof_mask = BIT(TBSTREAM_FRAME_START); + eof_mask = BIT(TBSTREAM_DATA) | BIT(TBSTREAM_CLOSE); + +- ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, +- RING_FLAG_FRAME | RING_FLAG_E2E, e2e_tx_hop, +- sof_mask, eof_mask, NULL, NULL); ++ ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, flags, ++ e2e_tx_hop, sof_mask, eof_mask, NULL, NULL); + if (!ring) { + ret = -ENOMEM; + goto err_free_tx_buffers; +@@ -607,15 +642,43 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) + return ret; + } + ++static bool tbstream_dev_tx_drained(const struct tbstream_dev *sdev) ++{ ++ const struct tbstream_ring *ring = &sdev->tx_ring; ++ ++ /* ++ * Everything is completed when number of free TX slots is back ++ * to the maximum. ++ */ ++ return ring->prod - ring->cons == tb_ring_size(ring->ring) - 1; ++} ++ + static void tbstream_dev_stop(struct tbstream_dev *sdev) + { + struct tb_xdomain *xd; + +- /* Wait for the ring to complete any outstanding frames */ +- tb_ring_flush(sdev->tx_ring.ring, 500); +- tb_ring_stop(sdev->tx_ring.ring); +- tb_ring_flush(sdev->rx_ring.ring, 500); +- tb_ring_stop(sdev->rx_ring.ring); ++ if (sdev->busy_poll) { ++ /* ++ * When busy polling we must advance the ring ourselves ++ * to push all outstanding frames on the wire. ++ */ ++ ktime_t timeout = ktime_add_ms(ktime_get(), 500); ++ ++ do { ++ if (tbstream_dev_tx_drained(sdev)) ++ break; ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); ++ fsleep(15); ++ } while (ktime_before(ktime_get(), timeout)); ++ ++ tb_ring_stop(sdev->tx_ring.ring); ++ tb_ring_stop(sdev->rx_ring.ring); ++ } else { ++ tb_ring_flush(sdev->tx_ring.ring, 500); ++ tb_ring_stop(sdev->tx_ring.ring); ++ tb_ring_flush(sdev->rx_ring.ring, 500); ++ tb_ring_stop(sdev->rx_ring.ring); ++ } + + xd = tbstream_dev_xdomain(sdev); + if (xd) { +@@ -633,10 +696,24 @@ static void tbstream_dev_stop(struct tbstream_dev *sdev) + sdev->tx_ring.ring = NULL; + } + ++/* Use only with read_iter/write_iter() to handle nowait */ ++static int tbstream_dev_lock(struct tbstream_dev *sdev, bool nowait) ++{ ++ if (nowait) { ++ if (!mutex_trylock(&sdev->lock)) ++ return -EAGAIN; ++ } else { ++ if (mutex_lock_interruptible(&sdev->lock)) ++ return -ERESTARTSYS; ++ } ++ return 0; ++} ++ + static ssize_t + tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + { + struct file *file = kiocb->ki_filp; ++ bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; +@@ -645,31 +722,50 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + if (ret) + return ret; + +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; ++ ret = tbstream_dev_lock(sdev, nowait); ++ if (ret) ++ return ret; + +- while (!tbstream_ring_available(&sdev->rx_ring)) { +- mutex_unlock(&sdev->lock); +- +- if (file->f_flags & O_NONBLOCK) +- return -EAGAIN; +- ret = wait_event_interruptible(sdev->wait, +- tbstream_ring_available(&sdev->rx_ring) || +- tbstream_dev_valid(sdev) != 0 || +- tbstream_dev_closed(sdev) || +- tbstream_dev_removed(sdev)); +- if (ret) +- return ret; ++ for (;;) { ++ /* When busy polling, advance any completions manually */ ++ tbstream_dev_poll_ring(sdev, &sdev->rx_ring); + + ret = tbstream_dev_valid(sdev); ++ if (ret) { ++ mutex_unlock(&sdev->lock); ++ return ret; ++ } ++ ++ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { ++ mutex_unlock(&sdev->lock); ++ return 0; ++ } ++ ++ if (tbstream_ring_available(&sdev->rx_ring)) ++ break; ++ ++ mutex_unlock(&sdev->lock); ++ ++ if (nowait) ++ return -EAGAIN; ++ ++ if (sdev->busy_poll) { ++ if (signal_pending(current)) ++ return -ERESTARTSYS; ++ cond_resched(); ++ } else { ++ ret = wait_event_interruptible(sdev->wait, ++ tbstream_ring_available(&sdev->rx_ring) || ++ tbstream_dev_valid(sdev) != 0 || ++ tbstream_dev_closed(sdev) || ++ tbstream_dev_removed(sdev)); ++ if (ret) ++ return ret; ++ } ++ ++ ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; +- +- if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) +- return 0; +- +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; + } + + nbytes = 0; +@@ -729,6 +825,7 @@ static ssize_t + tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + { + struct file *file = kiocb->ki_filp; ++ bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; +@@ -737,31 +834,49 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + if (ret) + return ret; + +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; ++ ret = tbstream_dev_lock(sdev, nowait); ++ if (ret) ++ return ret; + +- while (!tbstream_ring_available(&sdev->tx_ring)) { +- mutex_unlock(&sdev->lock); +- +- if (file->f_flags & O_NONBLOCK) +- return -EAGAIN; +- ret = wait_event_interruptible(sdev->wait, +- tbstream_ring_available(&sdev->tx_ring) || +- tbstream_dev_valid(sdev) != 0 || +- tbstream_dev_closed(sdev) || +- tbstream_dev_removed(sdev)); +- if (ret) +- return ret; ++ for (;;) { ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); + + ret = tbstream_dev_valid(sdev); ++ if (ret) { ++ mutex_unlock(&sdev->lock); ++ return ret; ++ } ++ ++ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { ++ mutex_unlock(&sdev->lock); ++ return -ENXIO; ++ } ++ ++ if (tbstream_ring_available(&sdev->tx_ring)) ++ break; ++ ++ mutex_unlock(&sdev->lock); ++ ++ if (nowait) ++ return -EAGAIN; ++ ++ if (sdev->busy_poll) { ++ if (signal_pending(current)) ++ return -ERESTARTSYS; ++ cond_resched(); ++ } else { ++ ret = wait_event_interruptible(sdev->wait, ++ tbstream_ring_available(&sdev->tx_ring) || ++ tbstream_dev_valid(sdev) != 0 || ++ tbstream_dev_closed(sdev) || ++ tbstream_dev_removed(sdev)); ++ if (ret) ++ return ret; ++ } ++ ++ ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; +- +- if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) +- return -ENXIO; +- +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; + } + + nbytes = 0; +@@ -795,6 +910,13 @@ tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait) + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + __poll_t mask = 0; + ++ /* ++ * Without interrupts there is nothing that can wake us up so ++ * return failure instead. ++ */ ++ if (sdev->busy_poll) ++ return EPOLLERR; ++ + poll_wait(file, &sdev->wait, wait); + guard(mutex)(&sdev->lock); + if (tbstream_dev_valid(sdev) != 0) { +@@ -904,6 +1026,35 @@ tbstream_dev_from_group(struct config_group *group) + return container_of(group, struct tbstream_dev, group); + } + ++static ssize_t tbstream_dev_busy_poll_show(struct config_item *item, char *buf) ++{ ++ struct config_group *group = to_config_group(item); ++ struct tbstream_dev *sdev = tbstream_dev_from_group(group); ++ ++ return sysfs_emit(buf, "%u\n", sdev->busy_poll); ++} ++ ++static ssize_t ++tbstream_dev_busy_poll_store(struct config_item *item, const char *buf, ++ size_t count) ++{ ++ struct config_group *group = to_config_group(item); ++ struct tbstream_dev *sdev = tbstream_dev_from_group(group); ++ bool busy_poll; ++ int ret; ++ ++ ret = kstrtobool(buf, &busy_poll); ++ if (ret) ++ return ret; ++ ++ guard(mutex)(&sdev->lock); ++ if (sdev->users) ++ return -EBUSY; ++ sdev->busy_poll = busy_poll; ++ return count; ++} ++CONFIGFS_ATTR(tbstream_dev_, busy_poll); ++ + static ssize_t tbstream_dev_index_show(struct config_item *item, char *buf) + { + struct config_group *group = to_config_group(item); +@@ -1210,6 +1361,7 @@ tbstream_dev_throttling_store(struct config_item *item, const char *buf, + CONFIGFS_ATTR(tbstream_dev_, throttling); + + static struct configfs_attribute *tbstream_dev_attrs[] = { ++ &tbstream_dev_attr_busy_poll, + &tbstream_dev_attr_index, + &tbstream_dev_attr_in_hopid, + &tbstream_dev_attr_out_hopid, +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +index 557288c0274b..8367d106d81b 100644 +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -592,6 +592,8 @@ struct tb_ring { + #define RING_FLAG_FRAME BIT(1) + /* Enable end-to-end flow control */ + #define RING_FLAG_E2E BIT(2) ++/* Do not enable interrupt for the ring */ ++#define RING_FLAG_NO_INTERRUPT BIT(3) + + struct ring_frame; + typedef void (*ring_cb)(struct tb_ring *, struct ring_frame *, bool canceled); diff --git a/pkgbuilds/linux-omarchy-bore/0610-typec-cable-altmode-check.patch b/pkgbuilds/linux-omarchy-bore/0610-typec-cable-altmode-check.patch new file mode 100644 index 0000000..3db7549 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0610-typec-cable-altmode-check.patch @@ -0,0 +1,132 @@ +diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c +index 263a89c5f324..5ee33a69b9cf 100644 +--- a/drivers/usb/typec/altmodes/displayport.c ++++ b/drivers/usb/typec/altmodes/displayport.c +@@ -790,7 +790,6 @@ int dp_altmode_probe(struct typec_altmode *alt) + dp->alt = alt; + + alt->desc = "DisplayPort"; +- typec_altmode_set_ops(alt, &dp_altmode_ops); + + if (plug) { + plug->desc = "Displayport"; +@@ -811,6 +810,10 @@ int dp_altmode_probe(struct typec_altmode *alt) + if (plug) + typec_altmode_set_drvdata(plug, dp); + ++ if ((alt->vdo & DP_CAP_RECEPTACLE) && typec_cable_altmode_unsupported(alt)) ++ return 0; ++ ++ typec_altmode_set_ops(alt, &dp_altmode_ops); + if (!alt->mode_selection) { + dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER; + schedule_work(&dp->work); +diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c +index 32250b94262a..2eccdddf1b1f 100644 +--- a/drivers/usb/typec/altmodes/thunderbolt.c ++++ b/drivers/usb/typec/altmodes/thunderbolt.c +@@ -284,6 +284,10 @@ static int tbt_altmode_probe(struct typec_altmode *alt) + + alt->desc = "Thunderbolt3"; + typec_altmode_set_drvdata(alt, tbt); ++ ++ if (typec_cable_altmode_unsupported(alt)) ++ return 0; ++ + typec_altmode_set_ops(alt, &tbt_altmode_ops); + + if (!alt->mode_selection && tbt_ready(alt)) { +diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c +index 0595e8cb83aa..54c7810b563b 100644 +--- a/drivers/usb/typec/class.c ++++ b/drivers/usb/typec/class.c +@@ -1429,6 +1429,77 @@ int typec_cable_is_active(struct typec_cable *cable) + } + EXPORT_SYMBOL_GPL(typec_cable_is_active); + ++enum typec_cable_altmode_support { ++ CABLE_SUPPORT_UNKNOWN, ++ CABLE_SUPPORTED, ++ CABLE_NOT_SUPPORTED, ++}; ++ ++static enum typec_cable_altmode_support ++typec_cable_check_altmode_support(struct typec_cable *cable, ++ struct typec_altmode *alt) ++{ ++ struct typec_altmode *plug; ++ u32 speed; ++ ++ /* ++ * Check if the cable has an e-marker, supports modal operation, and the ++ * SOP' altmode nodes are created. ++ */ ++ plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); ++ if (plug) { ++ typec_altmode_put_plug(plug); ++ return CABLE_SUPPORTED; ++ } ++ ++ /* The identity is not specified */ ++ if (!cable->identity) ++ return CABLE_SUPPORT_UNKNOWN; ++ ++ /* Non-e-marked cable */ ++ if (!cable->identity->id_header) ++ return CABLE_NOT_SUPPORTED; ++ ++ switch (PD_IDH_PTYPE(cable->identity->id_header)) { ++ case IDH_PTYPE_PCABLE: ++ speed = VDO_TYPEC_CABLE_SPEED(cable->identity->vdo[0]); ++ if (speed == CABLE_USB2_ONLY) ++ return CABLE_NOT_SUPPORTED; ++ return CABLE_SUPPORTED; ++ case IDH_PTYPE_ACABLE: ++ /* ++ * Active cables must establish an SOP' communication ++ * node. Since that check failed at the beginning of ++ * this function, this active cable does not support ++ * this specific altmode. ++ */ ++ return CABLE_NOT_SUPPORTED; ++ } ++ ++ return CABLE_SUPPORT_UNKNOWN; ++} ++ ++/** ++ * typec_cable_altmode_unsupported - Check if a cable restricts altmode ++ * @alt: The Alternate Mode to evaluate ++ * ++ * Returns true if the connected cable is incapable of handling the altmode. ++ */ ++bool typec_cable_altmode_unsupported(struct typec_altmode *alt) ++{ ++ enum typec_cable_altmode_support support = CABLE_SUPPORT_UNKNOWN; ++ struct typec_cable *cable; ++ ++ cable = typec_cable_get(typec_altmode2port(alt)); ++ if (cable) { ++ support = typec_cable_check_altmode_support(cable, alt); ++ typec_cable_put(cable); ++ } ++ ++ return support == CABLE_NOT_SUPPORTED; ++} ++EXPORT_SYMBOL_GPL(typec_cable_altmode_unsupported); ++ + /** + * typec_cable_set_identity - Report result from Discover Identity command + * @cable: The cable updated identity values +diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h +index d61ec38216fa..10a783b738ef 100644 +--- a/include/linux/usb/typec.h ++++ b/include/linux/usb/typec.h +@@ -337,6 +337,7 @@ void typec_unregister_cable(struct typec_cable *cable); + struct typec_cable *typec_cable_get(struct typec_port *port); + void typec_cable_put(struct typec_cable *cable); + int typec_cable_is_active(struct typec_cable *cable); ++bool typec_cable_altmode_unsupported(struct typec_altmode *alt); + + struct typec_plug *typec_register_plug(struct typec_cable *cable, + struct typec_plug_desc *desc); diff --git a/pkgbuilds/linux-omarchy-bore/0620-usb-string-sanitize.patch b/pkgbuilds/linux-omarchy-bore/0620-usb-string-sanitize.patch new file mode 100644 index 0000000..d2ac981 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0620-usb-string-sanitize.patch @@ -0,0 +1,29 @@ +diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c +--- a/drivers/usb/core/message.c ++++ b/drivers/usb/core/message.c +@@ -1052,6 +1052,25 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size) + UTF16_LITTLE_ENDIAN, buf, size); + buf[err] = 0; + ++ /* ++ * Some devices report string descriptors with a declared length ++ * greater than the actual serial, leaving uninitialized firmware ++ * memory (often including C0 control characters) appended to the ++ * returned string. Truncate at the first control character so ++ * callers get a clean, well-formed string. ++ */ ++ { ++ int i; ++ for (i = 0; i < err; i++) { ++ unsigned char c = buf[i]; ++ if (c < 0x20 || c == 0x7f) { ++ buf[i] = 0; ++ err = i; ++ break; ++ } ++ } ++ } ++ + if (tbuf[1] != USB_DT_STRING) + dev_dbg(&dev->dev, + "wrong descriptor type %02x for string %d (\"%s\")\n", diff --git a/pkgbuilds/linux-omarchy-bore/0650-wireguard-tstamp-type.patch b/pkgbuilds/linux-omarchy-bore/0650-wireguard-tstamp-type.patch new file mode 100644 index 0000000..87743c9 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0650-wireguard-tstamp-type.patch @@ -0,0 +1,18 @@ +diff --git a/drivers/net/wireguard/queueing.h b/drivers/net/wireguard/queueing.h +--- a/drivers/net/wireguard/queueing.h ++++ b/drivers/net/wireguard/queueing.h +@@ -78,12 +78,14 @@ static inline void wg_reset_packet(struct sk_buff *skb, bool encapsulating) + u8 l4_hash = skb->l4_hash; + u8 sw_hash = skb->sw_hash; + u32 hash = skb->hash; ++ u8 tstamp_type = skb->tstamp_type; + skb_scrub_packet(skb, true); + memset(&skb->headers, 0, sizeof(skb->headers)); + if (encapsulating) { + skb->l4_hash = l4_hash; + skb->sw_hash = sw_hash; + skb->hash = hash; ++ skb->tstamp_type = tstamp_type; + } + skb->queue_mapping = 0; + skb->nohdr = 0; diff --git a/pkgbuilds/linux-omarchy-bore/0660-btusb-mediatek-mt7922-13d3-3625.patch b/pkgbuilds/linux-omarchy-bore/0660-btusb-mediatek-mt7922-13d3-3625.patch new file mode 100644 index 0000000..8789452 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0660-btusb-mediatek-mt7922-13d3-3625.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -801,6 +801,8 @@ static const struct usb_device_id quirks_table[] = { + BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3613), .driver_info = BTUSB_MEDIATEK | + BTUSB_WIDEBAND_SPEECH }, ++ { USB_DEVICE(0x13d3, 0x3625), .driver_info = BTUSB_MEDIATEK | ++ BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3627), .driver_info = BTUSB_MEDIATEK | + BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3628), .driver_info = BTUSB_MEDIATEK | diff --git a/pkgbuilds/linux-omarchy-bore/0661-rtw89-command-offload-source.patch b/pkgbuilds/linux-omarchy-bore/0661-rtw89-command-offload-source.patch new file mode 100644 index 0000000..539fe9a --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0661-rtw89-command-offload-source.patch @@ -0,0 +1,32 @@ +diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c +--- a/drivers/net/wireless/realtek/rtw89/fw.c ++++ b/drivers/net/wireless/realtek/rtw89/fw.c +@@ -11780,7 +11780,7 @@ static void rtw89_fw_cmd_ofld_write_rf(struct rtw89_dev *rtwdev, + static void rtw89_fw_cmd_ofld_udelay(struct rtw89_dev *rtwdev, u32 us) + { + struct rtw89_fw_cmd_ofld_arg cmd = { +- .src = RTW89_FW_CMD_OFLD_SRC_OTHER, ++ .src = RTW89_FW_CMD_OFLD_SRC_BB, + .type = RTW89_FW_CMD_OFLD_DELAY, + .value = us, + }; +@@ -11794,7 +11794,7 @@ static void rtw89_fw_cmd_ofld_udelay(struct rtw89_dev *rtwdev, u32 us) + static void rtw89_fw_cmd_ofld_mdelay(struct rtw89_dev *rtwdev, u32 ms) + { + struct rtw89_fw_cmd_ofld_arg cmd = { +- .src = RTW89_FW_CMD_OFLD_SRC_OTHER, ++ .src = RTW89_FW_CMD_OFLD_SRC_BB, + .type = RTW89_FW_CMD_OFLD_DELAY, + .value = ms * 1000, + }; +diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h +--- a/drivers/net/wireless/realtek/rtw89/fw.h ++++ b/drivers/net/wireless/realtek/rtw89/fw.h +@@ -3142,7 +3142,6 @@ enum rtw89_fw_cmd_ofld_arg_src { + RTW89_FW_CMD_OFLD_SRC_RF, + RTW89_FW_CMD_OFLD_SRC_MAC, + RTW89_FW_CMD_OFLD_SRC_RF_DDIE, +- RTW89_FW_CMD_OFLD_SRC_OTHER, + }; + + enum rtw89_fw_cmd_ofld_arg_type { diff --git a/pkgbuilds/linux-omarchy-bore/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch b/pkgbuilds/linux-omarchy-bore/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch new file mode 100644 index 0000000..d3b514c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch @@ -0,0 +1,27 @@ +diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c ++++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +@@ -516,6 +516,10 @@ iwl_mld_mac80211_tx(struct ieee80211_hw *hw, + u32 link_id = u32_get_bits(info->control.flags, + IEEE80211_TX_CTRL_MLO_LINK); + ++ if (unlikely(test_bit(STATUS_FW_ERROR, &mld->trans->status))) { ++ ieee80211_free_txskb(hw, skb); ++ return; ++ } + /* In AP mode, mgmt frames are sent on the bcast station, + * so the FW can't translate the MLD addr to the link addr. Do it here + */ +diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.c b/drivers/net/wireless/intel/iwlwifi/mld/tx.c +--- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c ++++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.c +@@ -987,6 +987,9 @@ void iwl_mld_tx_from_txq(struct iwl_mld *mld, struct ieee80211_txq *txq) + struct sk_buff *skb = NULL; + u8 zero_addr[ETH_ALEN] = {}; + ++ if (unlikely(test_bit(STATUS_FW_ERROR, &mld->trans->status))) ++ return; ++ + /* + * Don't transmit during firmware restart. The firmware is dead, + * so iwl_trans_tx() would return -EIO for each frame. Avoid the diff --git a/pkgbuilds/linux-omarchy-bore/0700-pci-target-speed-quirk.patch b/pkgbuilds/linux-omarchy-bore/0700-pci-target-speed-quirk.patch new file mode 100644 index 0000000..1102b3f --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0700-pci-target-speed-quirk.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -108,7 +108,11 @@ int pcie_failed_link_retrain(struct pci_dev *dev) + + pcie_capability_read_word(dev, PCI_EXP_LNKSTA, &lnksta); + pcie_capability_read_word(dev, PCI_EXP_LNKCTL2, &oldlnkctl2); +- if (!(lnksta & PCI_EXP_LNKSTA_DLLLA) && pcie_lbms_seen(dev, lnksta)) { ++ if (lnksta & PCI_EXP_LNKSTA_DLLLA) { ++ ; ++ } else if (PCIE_LNKCTL2_TLS2SPEED(oldlnkctl2) == PCIE_SPEED_2_5GT) { ++ return ret; ++ } else if (pcie_lbms_seen(dev, lnksta)) { + pci_info(dev, "broken device, retraining non-functional downstream link at 2.5GT/s\n"); + ret = pcie_set_target_speed(dev, PCIE_SPEED_2_5GT, false); + if (ret) diff --git a/pkgbuilds/linux-omarchy-bore/0750-applesmc-cache-race.patch b/pkgbuilds/linux-omarchy-bore/0750-applesmc-cache-race.patch new file mode 100644 index 0000000..bd8dd7c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0750-applesmc-cache-race.patch @@ -0,0 +1,31 @@ +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -33,6 +33,7 @@ + #include + #include + #include ++#include + + /* data port used by Apple SMC */ + #define APPLESMC_DATA_PORT 0x300 +@@ -372,7 +373,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) + __be32 be; + int ret = 0; + +- if (cache->valid) ++ /* Pairs with smp_store_release() to ensure cache contents are visible */ ++ if (smp_load_acquire(&cache->valid)) + return cache; + + mutex_lock(&smcreg.mutex); +@@ -391,7 +393,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) + cache->len = info[0]; + memcpy(cache->type, &info[1], 4); + cache->flags = info[5]; +- cache->valid = true; ++ /* Pairs with smp_load_acquire() to commit cache contents before setting valid */ ++ smp_store_release(&cache->valid, true); + + out: + mutex_unlock(&smcreg.mutex); diff --git a/pkgbuilds/linux-omarchy-bore/0751-applesmc-key-backlight-workqueue-leak.patch b/pkgbuilds/linux-omarchy-bore/0751-applesmc-key-backlight-workqueue-leak.patch new file mode 100644 index 0000000..6eda26b --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0751-applesmc-key-backlight-workqueue-leak.patch @@ -0,0 +1,22 @@ +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -1252,12 +1252,17 @@ static void applesmc_release_light_sensor(void) + + static int applesmc_create_key_backlight(void) + { ++ int ret; ++ + if (!smcreg.has_key_backlight) + return 0; + applesmc_led_wq = create_singlethread_workqueue("applesmc-led"); + if (!applesmc_led_wq) + return -ENOMEM; +- return led_classdev_register(&pdev->dev, &applesmc_backlight); ++ ret = led_classdev_register(&pdev->dev, &applesmc_backlight); ++ if (ret) ++ destroy_workqueue(applesmc_led_wq); ++ return ret; + } + + static void applesmc_release_key_backlight(void) diff --git a/pkgbuilds/linux-omarchy-bore/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch b/pkgbuilds/linux-omarchy-bore/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch new file mode 100644 index 0000000..da85b37 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch @@ -0,0 +1,23 @@ +diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c +--- a/drivers/acpi/power.c ++++ b/drivers/acpi/power.c +@@ -1121,6 +1121,19 @@ static const struct dmi_system_id dmi_leave_unused_power_resources_on[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "ZERO"), + } + ++ }, ++ { ++ /* ++ * Acer Swift 3 SF314-56G with NVIDIA MX250 suffers from the ++ * same ACPI power resource regression as the Thunderobot ZERO. ++ * The power resource controlling the dGPU is incorrectly turned off ++ * during initialization, causing the GPU to fall off the bus (D3cold). ++ */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Acer"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Swift SF314-56G"), ++ } ++ + }, + {} + }; diff --git a/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch b/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch new file mode 100644 index 0000000..bb6a9de --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch @@ -0,0 +1,8751 @@ +diff --git a/arch/x86/include/uapi/asm/amd_hsmp.h b/arch/x86/include/uapi/asm/amd_hsmp.h +--- a/arch/x86/include/uapi/asm/amd_hsmp.h ++++ b/arch/x86/include/uapi/asm/amd_hsmp.h +@@ -53,9 +53,21 @@ enum hsmp_message_ids { + HSMP_SET_XGMI_PSTATE_RANGE, /* 26h Set xGMI P-state range */ + HSMP_CPU_RAIL_ISO_FREQ_POLICY, /* 27h Get/Set Cpu Iso frequency policy */ + HSMP_DFC_ENABLE_CTRL, /* 28h Enable/Disable DF C-state */ ++ HSMP_PC6_ENABLE, /* 29h Get/Set PC6 enable/disable status */ ++ HSMP_CC6_ENABLE, /* 2Ah Get/Set CC6 enable/disable status */ + HSMP_GET_RAPL_UNITS = 0x30, /* 30h Get scaling factor for energy */ + HSMP_GET_RAPL_CORE_COUNTER, /* 31h Get core energy counter value */ + HSMP_GET_RAPL_PACKAGE_COUNTER, /* 32h Get package energy counter value */ ++ HSMP_DIMM_SB_RD, /* 33h Get DIMM sideband data */ ++ HSMP_READ_CCD_POWER, /* 34h Get average CCD power */ ++ HSMP_READ_TDELTA, /* 35h Get thermal behaviour */ ++ HSMP_GET_SVI3_VR_CTRL_TEMP, /* 36h Get SVI3 VR controller rail temp */ ++ HSMP_GET_ENABLED_HSMP_CMDS, /* 37h Get supported HSMP commands */ ++ HSMP_SET_GET_FLOOR_LIMIT, /* 38h Get/Set core floor frequency limit */ ++ HSMP_DIMM_SB_WR, /* 39h Set DIMM sideband data */ ++ HSMP_SDPS_LIMIT, /* 3Ah Get/Set SDPS limit */ ++ HSMP_PQOS_TRAFFIC_PRIORITY, /* 3Bh Get/Set traffic priority */ ++ HSMP_PQOS_FLOATING_BW, /* 3Ch Get/Set max floating bandwidth */ + HSMP_MSG_ID_MAX, + }; + +@@ -170,16 +182,27 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + {0, 1, HSMP_GET}, + + /* +- * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0 +- * input: args[0] = min link width[15:8] + max link width[7:0] ++ * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0/1 ++ * input: args[0] = set/get XGMI Link width[31] (0 = set, 1 = get) + ++ * min link width[15:8] + max link width[7:0] ++ * Link width encoding: 0 = x4, 1 = x8, 2 = x16. ++ * On SET, max must be >= min. On GET, [15:0] are reserved. ++ * output: args[0] = reserved[31:16] + min link width[15:8] + ++ * max link width[7:0] + */ +- {1, 0, HSMP_SET}, ++ {1, 1, HSMP_SET_GET}, + + /* +- * HSMP_SET_DF_PSTATE, num_args = 1, response_sz = 0 +- * input: args[0] = df pstate[7:0] ++ * HSMP_SET_DF_PSTATE (APBDisable), num_args = 1, response_sz = 0/1 ++ * input: args[0] = set APB_DISABLE / get APB state[31] ++ * (0 = set & lock DF P-state, 1 = get) + ++ * reserved[30:8] + ++ * DF P-state[7:0] (0..2; reserved on GET) ++ * output: args[0] = reserved[31:9] + ++ * APB state[8] (1 = disabled, 0 = enabled) + ++ * locked DF P-state[7:0] if [8] = 1, else reserved + */ +- {1, 0, HSMP_SET}, ++ {1, 1, HSMP_SET_GET}, + + /* HSMP_SET_AUTO_DF_PSTATE, num_args = 0, response_sz = 0 */ + {0, 0, HSMP_SET}, +@@ -305,16 +328,32 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + {1, 1, HSMP_SET}, + + /* +- * HSMP_SET_POWER_MODE, num_args = 1, response_sz = 0 +- * input: args[0] = power efficiency mode[2:0] ++ * HSMP_SET_POWER_MODE (PwrEfficiencyModeSelection), ++ * num_args = 1, response_sz = 1 ++ * input: args[0] = set/get policy[31] (0 = set, 1 = get) + ++ * high util point[30:24] + ++ * low util point[23:17] + ++ * PPT limit[16:5] + ++ * reserved[4:3] + mode selection[2:0] ++ * [30:5] are valid only when [2:0] is a balanced core mode ++ * (4 or 5). [2:0] is reserved when getting (bit[31] = 1). ++ * output: args[0] same layout, [31] reserved, [2:0] = arbitrated ++ * current efficiency mode. + */ + {1, 1, HSMP_SET_GET}, + + /* +- * HSMP_SET_PSTATE_MAX_MIN, num_args = 1, response_sz = 0 +- * input: args[0] = min df pstate[15:8] + max df pstate[7:0] ++ * HSMP_SET_PSTATE_MAX_MIN (DfPstateRange), num_args = 1, response_sz = 0/1 ++ * input: args[0] = set/get DF P-state range[31] (0 = set, 1 = get) + ++ * reserved[30:16] + ++ * min DF P-state[15:8] + max DF P-state[7:0] ++ * DF P-state encoding: 0 = DFP0 (high performance), ++ * 1 = DFP1, 2 = DFP2 (low performance). ++ * [15:0] are reserved when getting (args[0] bit[31] = 1). ++ * output: args[0] = reserved[31:16] + min DF P-state[15:8] + ++ * max DF P-state[7:0] + */ +- {1, 0, HSMP_SET}, ++ {1, 1, HSMP_SET_GET}, + + /* + * HSMP_GET_METRIC_TABLE_VER, num_args = 0, response_sz = 1 +@@ -328,11 +367,12 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + {0, 0, HSMP_GET}, + + /* +- * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 2 ++ * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 3 + * output: args[0] = lower 32 bits of the address + * output: args[1] = upper 32 bits of the address ++ * output: args[2] = DRAM region size in bytes + */ +- {0, 2, HSMP_GET}, ++ {0, 3, HSMP_GET}, + + /* + * HSMP_SET_XGMI_PSTATE_RANGE, num_args = 1, response_sz = 0 +@@ -355,9 +395,31 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + */ + {1, 1, HSMP_SET_GET}, + +- /* RESERVED(0x29-0x2f) */ +- {0, 0, HSMP_RSVD}, +- {0, 0, HSMP_RSVD}, ++ /* ++ * HSMP_PC6_ENABLE (Pc6Enable), num_args = 1, response_sz = 0/1 ++ * input: args[0] = set/get PC6 control[31] (0 = set, 1 = get) + ++ * reserved[30:1] + ++ * enable PC6[0] (0 = disable, 1 = enable; ++ * reserved on GET) ++ * output: args[0] = reserved[31:1] + current PC6 control[0] ++ * (last value configured via HSMP or APML) ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_CC6_ENABLE (CC6Enable), num_args = 1, response_sz = 0/1 ++ * Configures CC6 enable for all cores; changing the setting does ++ * not by itself transition cores in or out of CC6. ++ * input: args[0] = set/get CC6 control[31] (0 = set, 1 = get) + ++ * reserved[30:1] + ++ * enable CC6[0] (0 = disable, 1 = enable; ++ * reserved on GET) ++ * output: args[0] = reserved[31:1] + current CC6 control[0] ++ * (last value configured via HSMP or APML) ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* RESERVED(0x2B-0x2F) */ + {0, 0, HSMP_RSVD}, + {0, 0, HSMP_RSVD}, + {0, 0, HSMP_RSVD}, +@@ -385,6 +447,89 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + */ + {0, 2, HSMP_GET}, + ++ /* ++ * HSMP_DIMM_SB_RD, num_args = 1, response_sz = 1 ++ * input: args[0] = reg space[23] + reg offset[22:12] + ++ * device LID[11:8] + DIMM address[7:0] ++ * output: args[0] = read data byte[3:0] ++ */ ++ {1, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_READ_CCD_POWER, num_args = 1, response_sz = 1 ++ * input: args[0] = apic id of core[15:0] ++ * output: args[0] = CCD power(mWatts)[31:0] ++ */ ++ {1, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_READ_TDELTA, num_args = 0, response_sz = 1 ++ * output: args[0] = thermal behaviour[31:0] ++ */ ++ {0, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_GET_SVI3_VR_CTRL_TEMP, num_args = 1, response_sz = 1 ++ * input: args[0] = SVI3 rail index[3:1] + read temperature[0] ++ * output: args[0] = SVI3 rail index[30:28] + ++ * rail temperature in degree C[27:0] ++ */ ++ {1, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_GET_ENABLED_HSMP_CMDS, num_args = 1, response_sz = 3 ++ * input: args[0] = HSMP command mask[0] ++ * output: status of HSMP command = args[0], args[1], args[2] ++ */ ++ {1, 3, HSMP_GET}, ++ ++ /* ++ * HSMP_SET_GET_FLOOR_LIMIT, num_args = 1, response_sz = 1 ++ * input: args[0] = op[31:30] + reserved[29:28] + ++ * apic id[27:16] + floor frequency MHz[15:0] ++ * op encoding: 00 = set per-core floor, ++ * 01 = set all-cores floor (apic id reserved), ++ * 10 = get per-core floor, ++ * 11 = get per-core effective floor. ++ * Floor frequency field is reserved on GET (bit[31] = 1). ++ * output: args[0] = floor frequency MHz[15:0] ++ * (effective for op 11, configured for op 10; ++ * reserved on SET) ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_DIMM_SB_WR, num_args = 1, response_sz = 0 ++ * input: args[0] = write data[31:24] + reg space[23] + ++ * reg offset[22:12] + device LID[11:8] + ++ * DIMM address[7:0] ++ */ ++ {1, 0, HSMP_SET}, ++ ++ /* ++ * HSMP_SDPS_LIMIT, num_args = 1, response_sz = 1 ++ * input: args[0] = set/get SDPS limit[31] (0 = set, 1 = get) + ++ * SDPS limit[30:0] ++ * output: args[0] = SDPS limit[30:0] ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_PQOS_TRAFFIC_PRIORITY, num_args = 1, response_sz = 1 ++ * input: args[0] = op[31:30] + priority sel[27:26] + ++ * priority val[21:20] + input[19:0] ++ * output: args[0] = supported priorities or priority val[1:0] ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_PQOS_FLOATING_BW, num_args = 1, response_sz = 2 ++ * input: args[0] = op[31] + sub-op[30:29] + params[28:0] ++ * output: args[0] = discovery bits or floating/global memory BW (Gbps) ++ * output: args[1] = reserved or config (drop adj, sampling delay, ++ * hysteresis) ++ */ ++ {1, 2, HSMP_SET_GET}, + }; + + /* Metrics table (supported only with proto version 6) */ +@@ -467,6 +612,39 @@ struct hsmp_metric_table { + __u32 gfxclk_frequency[8]; + }; + ++/** ++ * struct hsmp_telemetry_data - Request descriptor for HSMP telemetry IOCTL ++ * @buf: Input. Userspace pointer (encoded as __u64 to keep the layout ++ * stable between 32-bit and 64-bit callers) to the destination ++ * buffer that receives the metric table. ++ * @size: Input. Size in bytes of the buffer pointed to by @buf, and the ++ * number of bytes copied out on success. Must be non-zero and no ++ * larger than the metric table size firmware reports for this ++ * socket; a larger value is rejected with -EINVAL rather than ++ * short-written. A smaller value returns the leading @size bytes ++ * of the snapshot. The kernel does not write this field back. ++ * @sock_ind: Input. Socket index from which the metric table is read. ++ * @reserved: Reserved for future use. Callers should set this to zero; ++ * future kernels may begin interpreting the field, so passing ++ * a non-zero value today is not forwards compatible. ++ * ++ * Placing @buf first lets all fields fall on their natural alignment under ++ * the surrounding #pragma pack(4), so the struct is a tight 16 bytes with ++ * the same wire layout on 32-bit and 64-bit userspace. ++ * ++ * The metric table layout depends on the HSMP protocol version reported by ++ * firmware, which userspace can read from the protocol_version sysfs ++ * attribute. Protocol version 6 uses struct hsmp_metric_table, so callers on ++ * that version pass sizeof(struct hsmp_metric_table). Later version metrics ++ * table layout is documented in the Public PPR. ++ */ ++struct hsmp_telemetry_data { ++ __u64 buf; ++ __u32 size; ++ __u16 sock_ind; ++ __u16 reserved; ++}; ++ + /* Reset to default packing */ + #pragma pack() + +@@ -474,4 +652,16 @@ struct hsmp_metric_table { + #define HSMP_BASE_IOCTL_NR 0xF8 + #define HSMP_IOCTL_CMD _IOWR(HSMP_BASE_IOCTL_NR, 0, struct hsmp_message) + ++/* ++ * Fetch the firmware metric (telemetry) table for a given socket via the ++ * HSMP character device. This avoids the PAGE_SIZE limitation of the ++ * sysfs binary attribute path for tables larger than one page (such as the ++ * ~13 KB table used by HSMP protocol version 7). ++ * ++ * The direction is _IOW because the kernel only reads the request struct; ++ * the table itself is written to the buffer that @buf points at. ++ */ ++#define HSMP_IOCTL_GET_TELEMETRY_DATA \ ++ _IOW(HSMP_BASE_IOCTL_NR, 1, struct hsmp_telemetry_data) ++ + #endif /*_ASM_X86_AMD_HSMP_H_*/ +diff --git a/arch/x86/platform/olpc/olpc-xo15-sci.c b/arch/x86/platform/olpc/olpc-xo15-sci.c +--- a/arch/x86/platform/olpc/olpc-xo15-sci.c ++++ b/arch/x86/platform/olpc/olpc-xo15-sci.c +@@ -18,8 +18,6 @@ + + #define DRV_NAME "olpc-xo15-sci" + #define PFX DRV_NAME ": " +-#define XO15_SCI_CLASS DRV_NAME +-#define XO15_SCI_DEVICE_NAME "OLPC XO-1.5 SCI" + + static unsigned long xo15_sci_gpe; + static bool lid_wake_on_close; +@@ -148,9 +146,6 @@ static int xo15_sci_probe(struct platform_device *pdev) + if (!device) + return -ENODEV; + +- strscpy(acpi_device_name(device), XO15_SCI_DEVICE_NAME); +- strscpy(acpi_device_class(device), XO15_SCI_CLASS); +- + /* Get GPE bit assignment (EC events). */ + status = acpi_evaluate_integer(device->handle, "_GPE", NULL, &tmp); + if (ACPI_FAILURE(status)) +diff --git a/drivers/char/sonypi.c b/drivers/char/sonypi.c +--- a/drivers/char/sonypi.c ++++ b/drivers/char/sonypi.c +@@ -1125,8 +1125,6 @@ static int sonypi_acpi_probe(struct platform_device *pdev) + return -ENODEV; + + sonypi_acpi_device = device; +- strscpy(acpi_device_name(device), "Sony laptop hotkeys"); +- strscpy(acpi_device_class(device), "sony/hotkey"); + return 0; + } + +diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c +--- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c ++++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c +@@ -7,6 +7,7 @@ + * + * Author: Basavaraj Natikar + */ ++#include + #include + #include + #include +@@ -102,20 +103,20 @@ static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement) + *platform_type = mode.op_mode.devicemode; + + if (mode.op_mode.ontablestate == 1) { +- *laptop_placement = ON_TABLE; ++ *laptop_placement = AMD_PMF_ON_TABLE; + } else if (mode.op_mode.ontablestate == 2) { +- *laptop_placement = ON_LAP_MOTION; ++ *laptop_placement = AMD_PMF_ON_LAP_MOTION; + } else if (mode.op_mode.inbagstate == 1) { +- *laptop_placement = IN_BAG; ++ *laptop_placement = AMD_PMF_IN_BAG; + } else if (mode.op_mode.outbagstate == 1) { +- *laptop_placement = OUT_OF_BAG; ++ *laptop_placement = AMD_PMF_OUT_OF_BAG; + } else if (mode.op_mode.ontablestate == 0 || mode.op_mode.inbagstate == 0 || + mode.op_mode.outbagstate == 0) { +- *laptop_placement = LP_UNKNOWN; ++ *laptop_placement = AMD_PMF_LP_UNKNOWN; + pr_warn_once("Unknown laptop placement\n"); + } else if (mode.op_mode.ontablestate == 3 || mode.op_mode.inbagstate == 3 || + mode.op_mode.outbagstate == 3) { +- *laptop_placement = LP_UNDEFINED; ++ *laptop_placement = AMD_PMF_LP_UNDEFINED; + pr_warn_once("Undefined laptop placement\n"); + } + +diff --git a/drivers/platform/arm64/qcom-hamoa-ec.c b/drivers/platform/arm64/qcom-hamoa-ec.c +--- a/drivers/platform/arm64/qcom-hamoa-ec.c ++++ b/drivers/platform/arm64/qcom-hamoa-ec.c +@@ -92,8 +92,10 @@ static int qcom_ec_read(struct qcom_ec *ec, u8 cmd, u8 resp_len, u8 *resp) + return ret; + else if (ret == 0 || ret == 0xff) + return -EOPNOTSUPP; ++ else if (ret != resp_len) ++ return -EIO; + +- if (resp[0] >= resp_len) ++ if (resp[0] != resp_len - 1) + return -EINVAL; + + return 0; +diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c +--- a/drivers/platform/mellanox/mlxbf-bootctl.c ++++ b/drivers/platform/mellanox/mlxbf-bootctl.c +@@ -10,6 +10,7 @@ + + #include + #include ++#include + #include + #include + #include +diff --git a/drivers/platform/mellanox/mlxbf-pmc.c b/drivers/platform/mellanox/mlxbf-pmc.c +--- a/drivers/platform/mellanox/mlxbf-pmc.c ++++ b/drivers/platform/mellanox/mlxbf-pmc.c +@@ -2262,13 +2262,19 @@ static int mlxbf_pmc_map_counters(struct device *dev) + + static int mlxbf_pmc_probe(struct platform_device *pdev) + { +- struct acpi_device *acpi_dev = ACPI_COMPANION(&pdev->dev); +- const char *hid = acpi_device_hid(acpi_dev); + struct device *dev = &pdev->dev; ++ struct acpi_device *acpi_dev; + struct arm_smccc_res res; ++ const char *hid; + guid_t guid; + int ret; + ++ acpi_dev = ACPI_COMPANION(&pdev->dev); ++ if (!acpi_dev) ++ return -ENODEV; ++ ++ hid = acpi_device_hid(acpi_dev); ++ + /* Ensure we have the UUID we expect for this service. */ + arm_smccc_smc(MLXBF_PMC_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res); + guid_parse(mlxbf_pmc_svc_uuid_str, &guid); +diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c +--- a/drivers/platform/surface/surface_acpi_notify.c ++++ b/drivers/platform/surface/surface_acpi_notify.c +@@ -777,12 +777,16 @@ static int san_consumer_links_setup(struct platform_device *pdev) + + static int san_probe(struct platform_device *pdev) + { +- struct acpi_device *san = ACPI_COMPANION(&pdev->dev); + struct ssam_controller *ctrl; ++ struct acpi_device *san; + struct san_data *data; + acpi_status astatus; + int status; + ++ san = ACPI_COMPANION(&pdev->dev); ++ if (!san) ++ return -ENODEV; ++ + ctrl = ssam_client_bind(&pdev->dev); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); +diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c +--- a/drivers/platform/surface/surface_aggregator_hub.c ++++ b/drivers/platform/surface/surface_aggregator_hub.c +@@ -348,8 +348,13 @@ static const struct ssam_hub_desc kip_hub = { + /* -- Driver registration. -------------------------------------------------- */ + + static const struct ssam_device_id ssam_hub_match[] = { +- { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, +- { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, ++ { ++ SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), ++ .driver_data = (unsigned long)&kip_hub, ++ }, { ++ SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), ++ .driver_data = (unsigned long)&base_hub, ++ }, + { } + }; + MODULE_DEVICE_TABLE(ssam, ssam_hub_match); +diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c +--- a/drivers/platform/surface/surface_aggregator_tabletsw.c ++++ b/drivers/platform/surface/surface_aggregator_tabletsw.c +@@ -622,8 +622,13 @@ static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { + /* -- Driver registration. -------------------------------------------------- */ + + static const struct ssam_device_id ssam_tablet_sw_match[] = { +- { SSAM_SDEV(KIP, SAM, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, +- { SSAM_SDEV(POS, SAM, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, ++ { ++ SSAM_SDEV(KIP, SAM, 0x00, 0x01), ++ .driver_data = (unsigned long)&ssam_kip_sw_desc, ++ }, { ++ SSAM_SDEV(POS, SAM, 0x00, 0x01), ++ .driver_data = (unsigned long)&ssam_pos_sw_desc, ++ }, + { }, + }; + MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -20,7 +20,6 @@ + #define SURFACE_PRO3_BUTTON_HID "MSHW0028" + #define SURFACE_PRO4_BUTTON_HID "MSHW0040" + #define SURFACE_BUTTON_OBJ_NAME "VGBI" +-#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons" + + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -212,11 +211,10 @@ static int surface_button_probe(struct platform_device *pdev) + goto err_free_button; + } + +- strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME); + snprintf(button->phys, sizeof(button->phys), "%s/buttons", + acpi_device_hid(device)); + +- input->name = acpi_device_name(device); ++ input->name = "Surface Pro 3/4 Buttons"; + input->phys = button->phys; + input->id.bustype = BUS_HOST; + input->dev.parent = &pdev->dev; +@@ -239,8 +237,8 @@ static int surface_button_probe(struct platform_device *pdev) + goto err_free_button; + } + +- dev_info(&pdev->dev, "%s [%s]\n", acpi_device_name(device), +- acpi_device_bid(device)); ++ dev_info(&pdev->dev, "%s [%s]\n", input->name, acpi_device_bid(device)); ++ + return 0; + + err_free_input: +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -802,7 +802,6 @@ config LG_LAPTOP + tristate "LG Laptop Extras" + depends on ACPI + depends on ACPI_BATTERY +- depends on ACPI_WMI + depends on INPUT + select INPUT_SPARSEKMAP + select NEW_LEDS +diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c +--- a/drivers/platform/x86/acer-wmi.c ++++ b/drivers/platform/x86/acer-wmi.c +@@ -1581,7 +1581,9 @@ static int WMI_gaming_execute_u32_u64(u32 method_id, u32 in, u64 *out) + return -EIO; + + obj = result.pointer; +- if (obj && out) { ++ if (!obj && out) { ++ ret = -ENOMSG; ++ } else if (obj && out) { + switch (obj->type) { + case ACPI_TYPE_INTEGER: + *out = obj->integer.value; +diff --git a/drivers/platform/x86/amd/Kconfig b/drivers/platform/x86/amd/Kconfig +--- a/drivers/platform/x86/amd/Kconfig ++++ b/drivers/platform/x86/amd/Kconfig +@@ -34,6 +34,17 @@ config AMD_WBRF + This mechanism will only be activated on platforms that advertise a + need for it. + ++config AMD_HALO_LED ++ tristate "AMD Halo Box RGB LED Driver" ++ depends on ACPI_WMI && LEDS_CLASS_MULTICOLOR ++ help ++ This driver provides RGB LED control for AMD Halo Box devices ++ through the LED multicolor subsystem. The Halo Box light bar can ++ be controlled via sysfs to display any RGB color combination. ++ ++ To compile this driver as a module, choose M here: the module ++ will be called amd_halo_led. ++ + config AMD_ISP_PLATFORM + tristate "AMD ISP4 platform driver" + depends on I2C && X86_64 && ACPI +diff --git a/drivers/platform/x86/amd/Makefile b/drivers/platform/x86/amd/Makefile +--- a/drivers/platform/x86/amd/Makefile ++++ b/drivers/platform/x86/amd/Makefile +@@ -10,5 +10,6 @@ obj-$(CONFIG_AMD_PMC) += pmc/ + obj-$(CONFIG_AMD_HSMP) += hsmp/ + obj-$(CONFIG_AMD_PMF) += pmf/ + obj-$(CONFIG_AMD_WBRF) += wbrf.o ++obj-$(CONFIG_AMD_HALO_LED) += amd_halo_led.o + obj-$(CONFIG_AMD_ISP_PLATFORM) += amd_isp4.o + obj-$(CONFIG_AMD_HFI) += hfi/ +diff --git a/drivers/platform/x86/amd/amd_halo_led.c b/drivers/platform/x86/amd/amd_halo_led.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/amd/amd_halo_led.c +@@ -0,0 +1,315 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * AMD Halo Box RGB LED Driver ++ * ++ * Copyright (C) 2026 Advanced Micro Devices, Inc. ++ * ++ * This driver provides RGB LED control for AMD Halo Box devices through ++ * the LED multicolor subsystem. The Halo Box light bar can be controlled ++ * via sysfs to display any RGB color combination. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#define AMD_HALO_GUID "081E747B-E028-4232-AF24-EAAEAB2B1E86" ++ ++/* WMI method IDs from MOF */ ++enum { ++ AMD_HALO_WMI_TURN_OFF = 0x04, ++ AMD_HALO_WMI_RGB = 0x07, ++}; ++ ++/* Arg0 of the AMD_HALO_WMI_RGB Method */ ++enum { ++ AMD_HALO_RGB_CMD_GET = 0x0, ++ AMD_HALO_RGB_CMD_SET = 0x1, ++}; ++ ++/* Status codes from spec */ ++#define AMD_HALO_STATUS_SUCCESS 0x0000 ++#define AMD_HALO_STATUS_INVALID_PARAM 0xFFFD ++ ++/* Brightness uses 0-100 range */ ++#define AMD_HALO_MAX_HW_BRIGHTNESS 100 ++ ++/** ++ * struct amd_halo_led_data - Driver private data ++ * @wdev: WMI device pointer ++ * @led_mc: LED multicolor class device ++ * @subled_info: RGB channel information ++ * @lock: Mutex to protect WMI calls ++ */ ++struct amd_halo_led_data { ++ struct wmi_device *wdev; ++ struct led_classdev_mc led_mc; ++ struct mc_subled subled_info[3]; ++ struct mutex lock; ++}; ++ ++struct amd_halo_wmi_args { ++ __le32 arg0; ++ __le32 arg1; ++}; ++ ++struct amd_halo_wmi_args_rgb { ++ __le32 cmd; ++ __le32 red; ++ __le32 green; ++ __le32 blue; ++}; ++ ++struct amd_halo_wmi_output_rgb { ++ __le16 status; ++ u8 red; ++ u8 green; ++ u8 blue; ++} __packed; ++ ++static inline int wmi_status_to_err(u16 status) ++{ ++ switch (status) { ++ case AMD_HALO_STATUS_SUCCESS: ++ return 0; ++ case AMD_HALO_STATUS_INVALID_PARAM: ++ return -EINVAL; ++ default: ++ return -EIO; ++ } ++} ++ ++static int __amd_halo_wmi_call(struct wmi_device *wdev, ++ u32 method_id, void *data, size_t length) ++{ ++ struct wmi_buffer input = { ++ .length = length, ++ .data = data, ++ }; ++ struct wmi_buffer output = { }; ++ int ret; ++ ++ /* Return buffer per spec: Bytes[0:1] = Status (little-endian) */ ++ ret = wmidev_invoke_method(wdev, 0, method_id, ++ &input, &output, sizeof(__le16)); ++ if (ret) ++ return ret; ++ ++ __le16 *result_status __free(kfree) = output.data; ++ ++ return wmi_status_to_err(le16_to_cpu(*result_status)); ++} ++ ++/** ++ * amd_halo_wmi_turn_off - Turn off all LED channels ++ * @wdev: WMI device pointer ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_wmi_turn_off(struct wmi_device *wdev) ++{ ++ struct amd_halo_wmi_args args = { }; ++ ++ return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_TURN_OFF, &args, sizeof(args)); ++} ++ ++/** ++ * amd_halo_wmi_set_rgb - Set all RGB channels atomically ++ * @wdev: WMI device pointer ++ * @r: brightness for red channel (0 - 100) ++ * @g: brightness for green channel (0 - 100) ++ * @b: brightness for blue channel (0 - 100) ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_wmi_set_rgb(struct wmi_device *wdev, u32 r, u32 g, u32 b) ++{ ++ struct amd_halo_wmi_args_rgb args = { ++ .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_SET), ++ .red = cpu_to_le32(r), ++ .green = cpu_to_le32(g), ++ .blue = cpu_to_le32(b), ++ }; ++ ++ if (r > AMD_HALO_MAX_HW_BRIGHTNESS || ++ g > AMD_HALO_MAX_HW_BRIGHTNESS || ++ b > AMD_HALO_MAX_HW_BRIGHTNESS) { ++ return -EINVAL; ++ } ++ ++ return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_RGB, &args, sizeof(args)); ++} ++ ++/** ++ * amd_halo_wmi_get_rgb - Get RGB values ++ * @wdev: WMI device pointer ++ * @r: output buffer for red value ++ * @g: output buffer for green value ++ * @b: output buffer for blue value ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_wmi_get_rgb(struct wmi_device *wdev, u8 *r, u8 *g, u8 *b) ++{ ++ struct amd_halo_wmi_args_rgb args = { ++ .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_GET), ++ }; ++ struct wmi_buffer input = { ++ .length = sizeof(args), ++ .data = &args, ++ }; ++ struct wmi_buffer output = { }; ++ int ret; ++ ++ ret = wmidev_invoke_method(wdev, 0, AMD_HALO_WMI_RGB, ++ &input, &output, sizeof(struct amd_halo_wmi_output_rgb)); ++ if (ret) ++ return ret; ++ ++ struct amd_halo_wmi_output_rgb *data __free(kfree) = output.data; ++ ++ ret = wmi_status_to_err(le16_to_cpu(data->status)); ++ if (ret) ++ return ret; ++ ++ if (data->red > AMD_HALO_MAX_HW_BRIGHTNESS || ++ data->green > AMD_HALO_MAX_HW_BRIGHTNESS || ++ data->blue > AMD_HALO_MAX_HW_BRIGHTNESS) { ++ return -EPROTO; ++ } ++ ++ *r = data->red; ++ *g = data->green; ++ *b = data->blue; ++ ++ return 0; ++} ++ ++/** ++ * amd_halo_brightness_set - Set LED brightness ++ * @cdev: LED class device ++ * @brightness: Brightness value ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_brightness_set(struct led_classdev *cdev, ++ enum led_brightness brightness) ++{ ++ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); ++ struct amd_halo_led_data *data = container_of(mc_cdev, ++ struct amd_halo_led_data, ++ led_mc); ++ u32 red_hw, green_hw, blue_hw; ++ int ret; ++ ++ guard(mutex)(&data->lock); ++ ++ led_mc_calc_color_components(mc_cdev, brightness); ++ ++ if (brightness == 0) ++ return amd_halo_wmi_turn_off(data->wdev); ++ ++ red_hw = mc_cdev->subled_info[0].brightness; ++ green_hw = mc_cdev->subled_info[1].brightness; ++ blue_hw = mc_cdev->subled_info[2].brightness; ++ ++ ret = amd_halo_wmi_set_rgb(data->wdev, red_hw, green_hw, blue_hw); ++ if (ret) ++ goto out; ++ ++ return 0; ++ ++out: ++ /* ++ * Consider the light bar non-functional if AMD_HALO_WMI_RGB failed. ++ * Attempt to turn the LED off completely as clean-up. ++ */ ++ if (amd_halo_wmi_turn_off(data->wdev)) ++ dev_warn_ratelimited(&data->wdev->dev, "Failed to turn LED off on cleanup\n"); ++ ++ return ret; ++} ++ ++static int amd_halo_probe(struct wmi_device *wdev, const void *context) ++{ ++ struct led_init_data led_init_data = { ++ .devicename = "amd_halo", ++ .default_label = "multicolor:" LED_FUNCTION_STATUS, ++ .devname_mandatory = true, ++ }; ++ struct amd_halo_led_data *data; ++ u8 r, g, b; ++ int ret; ++ ++ data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ ret = devm_mutex_init(&wdev->dev, &data->lock); ++ if (ret) ++ return ret; ++ ++ data->wdev = wdev; ++ dev_set_drvdata(&wdev->dev, data); ++ ++ data->subled_info[0].color_index = LED_COLOR_ID_RED; ++ data->subled_info[1].color_index = LED_COLOR_ID_GREEN; ++ data->subled_info[2].color_index = LED_COLOR_ID_BLUE; ++ ++ data->led_mc.led_cdev.brightness = AMD_HALO_MAX_HW_BRIGHTNESS; ++ data->led_mc.led_cdev.max_brightness = AMD_HALO_MAX_HW_BRIGHTNESS; ++ data->led_mc.led_cdev.brightness_set_blocking = amd_halo_brightness_set; ++ data->led_mc.led_cdev.flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN; ++ data->led_mc.num_colors = ARRAY_SIZE(data->subled_info); ++ data->led_mc.subled_info = data->subled_info; ++ ++ ret = amd_halo_wmi_get_rgb(wdev, &r, &g, &b); ++ if (ret) ++ return ret; ++ ++ data->subled_info[0].intensity = r; ++ data->subled_info[1].intensity = g; ++ data->subled_info[2].intensity = b; ++ ++ ret = devm_led_classdev_multicolor_register_ext(&wdev->dev, &data->led_mc, ++ &led_init_data); ++ if (ret) ++ return dev_err_probe(&wdev->dev, ret, ++ "Failed to register multicolor LED\n"); ++ return 0; ++} ++ ++static const struct wmi_device_id amd_halo_id_table[] = { ++ { .guid_string = AMD_HALO_GUID }, ++ { } ++}; ++MODULE_DEVICE_TABLE(wmi, amd_halo_id_table); ++ ++static struct wmi_driver amd_halo_driver = { ++ .driver = { ++ .name = "amd_halo_led", ++ }, ++ .id_table = amd_halo_id_table, ++ .probe = amd_halo_probe, ++ .no_singleton = true, ++}; ++ ++module_wmi_driver(amd_halo_driver); ++ ++MODULE_AUTHOR("Mario Limonciello (AMD) "); ++MODULE_AUTHOR("Yo-Jung Leo Lin (AMD) "); ++MODULE_DESCRIPTION("AMD Halo Box RGB LED Control Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c +--- a/drivers/platform/x86/amd/hsmp/acpi.c ++++ b/drivers/platform/x86/amd/hsmp/acpi.c +@@ -15,12 +15,18 @@ + #include + #include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include + #include ++#include ++#include ++#include + #include + #include + #include +@@ -38,6 +44,17 @@ + + static struct hsmp_plat_device *hsmp_pdev; + ++/* ++ * Tracks the ACPI socket platform devices that share the socket array and the ++ * /dev/hsmp misc device. The first probe initializes it, each further probe ++ * takes a reference and every remove (or probe failure) drops one; the last ++ * put frees the shared state via hsmp_acpi_sock_release(). All get/put run ++ * under hsmp_sock_rwsem held for write, so the counting is already serialized ++ * and the atomic in kref is not strictly needed; kref is used for the clearer ++ * get/put interface and its release callback. ++ */ ++static struct kref hsmp_acpi_sock_kref; ++ + struct hsmp_sys_attr { + struct device_attribute dattr; + u32 msg_id; +@@ -77,6 +94,8 @@ static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind) + * bytes to integer. + */ + uid = acpi_device_uid(ACPI_COMPANION(dev)); ++ if (!uid || strlen(uid) < 3) ++ return -EINVAL; + + return kstrtou16(uid + 2, 10, sock_ind); + } +@@ -104,7 +123,7 @@ static acpi_status hsmp_resource(struct acpi_resource *res, void *data) + return AE_OK; + } + +-static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) ++static int hsmp_read_acpi_dsd(struct device *dev, struct hsmp_socket *sock) + { + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *guid, *mailbox_package; +@@ -113,10 +132,10 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + int ret = 0; + int j; + +- status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL, ++ status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL, + &buf, ACPI_TYPE_PACKAGE); + if (ACPI_FAILURE(status)) { +- dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", ++ dev_err(dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", + acpi_format_exception(status)); + return -ENODEV; + } +@@ -139,7 +158,7 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + guid = &dsd->package.elements[0]; + mailbox_package = &dsd->package.elements[1]; + if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) { +- dev_err(sock->dev, "Invalid hsmp _DSD table data\n"); ++ dev_err(dev, "Invalid hsmp _DSD table data\n"); + ret = -EINVAL; + goto free_buf; + } +@@ -148,12 +167,18 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + union acpi_object *msgobj, *msgstr, *msgint; + + msgobj = &mailbox_package->package.elements[j]; +- msgstr = &msgobj->package.elements[0]; +- msgint = &msgobj->package.elements[1]; + + /* package should have 1 string and 1 integer object */ + if (msgobj->type != ACPI_TYPE_PACKAGE || +- msgstr->type != ACPI_TYPE_STRING || ++ msgobj->package.count < 2) { ++ ret = -EINVAL; ++ goto free_buf; ++ } ++ ++ msgstr = &msgobj->package.elements[0]; ++ msgint = &msgobj->package.elements[1]; ++ ++ if (msgstr->type != ACPI_TYPE_STRING || + msgint->type != ACPI_TYPE_INTEGER) { + ret = -EINVAL; + goto free_buf; +@@ -183,14 +208,14 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + return ret; + } + +-static int hsmp_read_acpi_crs(struct hsmp_socket *sock) ++static int hsmp_read_acpi_crs(struct device *dev, struct hsmp_socket *sock) + { + acpi_status status; + +- status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS, ++ status = acpi_walk_resources(ACPI_HANDLE(dev), METHOD_NAME__CRS, + hsmp_resource, sock); + if (ACPI_FAILURE(status)) { +- dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n", ++ dev_err(dev, "Failed to look up MP1 base address from CRS method, err: %s\n", + acpi_format_exception(status)); + return -EINVAL; + } +@@ -198,10 +223,10 @@ static int hsmp_read_acpi_crs(struct hsmp_socket *sock) + return -EINVAL; + + /* The mapped region should be un-cached */ +- sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr, ++ sock->virt_base_addr = devm_ioremap_uc(dev, sock->mbinfo.base_addr, + sock->mbinfo.size); + if (!sock->virt_base_addr) { +- dev_err(sock->dev, "Failed to ioremap MP1 base address\n"); ++ dev_err(dev, "Failed to ioremap MP1 base address\n"); + return -ENOMEM; + } + +@@ -215,7 +240,6 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) + int ret; + + sock->sock_ind = sock_ind; +- sock->dev = dev; + sock->amd_hsmp_rdwr = amd_hsmp_acpi_rdwr; + + sema_init(&sock->hsmp_sem, 1); +@@ -223,12 +247,27 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) + dev_set_drvdata(dev, sock); + + /* Read MP1 base address from CRS method */ +- ret = hsmp_read_acpi_crs(sock); ++ ret = hsmp_read_acpi_crs(dev, sock); + if (ret) + return ret; + + /* Read mailbox offsets from DSD table */ +- return hsmp_read_acpi_dsd(sock); ++ ret = hsmp_read_acpi_dsd(dev, sock); ++ if (ret) ++ return ret; ++ ++ /* ++ * Publish sock->dev last. hsmp_send_message() uses it (via ++ * smp_load_acquire()) as the readiness gate for the lock-free data ++ * plane, so it must become visible only after virt_base_addr, the ++ * mailbox offsets and the semaphore are fully initialized. On a ++ * multi-socket system socket 0 exposes /dev/hsmp before later sockets ++ * finish probing, so without this an ioctl aimed at a socket still in ++ * bring-up could pass the gate and dereference a NULL virt_base_addr. ++ */ ++ smp_store_release(&sock->dev, dev); ++ ++ return 0; + } + + static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj, +@@ -238,13 +277,31 @@ static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj + struct device *dev = container_of(kobj, struct device, kobj); + struct hsmp_socket *sock = dev_get_drvdata(dev); + ++ /* ++ * metrics_bin is a sysfs binary attribute and is capped at PAGE_SIZE. ++ * It can therefore only carry the protocol version 6 metric table ++ * (struct hsmp_metric_table). The larger tables defined from protocol ++ * version 7 onwards do not fit; userspace on those systems must read ++ * the snapshot through HSMP_IOCTL_GET_TELEMETRY_DATA on /dev/hsmp. ++ * Surface the unsupported case here as -EOPNOTSUPP rather than ++ * silently truncating the snapshot. ++ */ ++ if (hsmp_pdev->proto_ver != HSMP_PROTO_VER6) ++ return -EOPNOTSUPP; ++ + return hsmp_metric_tbl_read(sock, buf, count); + } + + static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj, + const struct bin_attribute *battr, int id) + { +- if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) ++ /* ++ * Keep metrics_bin visible on protocol version 7 and later as well, ++ * so that userspace which expects the file to exist gets a clear ++ * -EOPNOTSUPP from the read handler instead of -ENOENT, and is ++ * pointed at HSMP_IOCTL_GET_TELEMETRY_DATA as the supported path. ++ */ ++ if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) + return battr->attr.mode; + + return 0; +@@ -459,11 +516,20 @@ static ssize_t hsmp_freq_limit_source_show(struct device *dev, struct device_att + return len; + } + ++/* ++ * Bring up one ACPI HSMP socket: parse its ACPI table, run the mailbox ++ * handshake and register its sysfs/hwmon interfaces. ++ * ++ * Called with hsmp_sock_rwsem held for write by hsmp_acpi_probe(), so the ++ * per-socket bring-up cannot race a concurrent probe or remove. ++ */ + static int init_acpi(struct device *dev) + { + u16 sock_ind; + int ret; + ++ lockdep_assert_held_write(&hsmp_sock_rwsem); ++ + ret = hsmp_get_uid(dev, &sock_ind); + if (ret) + return ret; +@@ -491,7 +557,7 @@ static int init_acpi(struct device *dev) + return ret; + } + +- if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) { ++ if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) { + ret = hsmp_get_tbl_dram_base(sock_ind); + if (ret) + dev_info(dev, "Failed to init metric table\n"); +@@ -576,6 +642,60 @@ static const struct acpi_device_id amd_hsmp_acpi_ids[] = { + }; + MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids); + ++/* ++ * kref release: tear down the shared ACPI socket state once the last socket ++ * drops its reference. Deregister /dev/hsmp if it was registered, unmap any ++ * metric-table DRAM, destroy the per-socket mutexes and free the socket array. ++ * ++ * Runs from kref_put() with hsmp_sock_rwsem held for write, since the remove ++ * and probe-failure paths both drop their reference under that lock. The write ++ * lock has drained any in-flight hsmp_send_message(), so unmapping the mailbox ++ * and freeing the array cannot race the data plane. ++ */ ++static void hsmp_acpi_sock_release(struct kref *kref) ++{ ++ lockdep_assert_held_write(&hsmp_sock_rwsem); ++ ++ if (!IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) ++ hsmp_misc_deregister(); ++ hsmp_unmap_metric_tbls(hsmp_pdev); ++ hsmp_destroy_metric_read_locks(hsmp_pdev); ++ kfree(hsmp_pdev->sock); ++ hsmp_pdev->sock = NULL; ++ hsmp_pdev->num_sockets = 0; ++ hsmp_pdev->proto_ver = 0; ++} ++ ++/** ++ * hsmp_acpi_probe_failure_cleanup() - Undo a failed ACPI socket probe. ++ * @dev: ACPI companion device whose probe failed. ++ * ++ * This device already took a reference on entry to hsmp_acpi_probe(), so clear ++ * its sock->dev and drop that reference; the shared state is released if it was ++ * the last one. ++ * ++ * Clearing sock->dev matters on multi-socket systems: when a non-first socket ++ * fails, the array stays alive (owned by an already-probed socket) and ++ * remove() is never called for this device, yet devres unmaps its mailbox once ++ * probe() returns. Without clearing dev, a later message to this index would ++ * pass every gate in hsmp_send_message() and reach the unmapped mailbox. ++ * ++ * sock is NULL if probe failed before hsmp_parse_acpi_table() set the drvdata. ++ * ++ * Called from hsmp_acpi_probe(), which already holds hsmp_sock_rwsem for write. ++ */ ++static void hsmp_acpi_probe_failure_cleanup(struct device *dev) ++{ ++ struct hsmp_socket *sock = dev_get_drvdata(dev); ++ ++ lockdep_assert_held_write(&hsmp_sock_rwsem); ++ ++ if (sock) ++ sock->dev = NULL; ++ ++ kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); ++} ++ + static int hsmp_acpi_probe(struct platform_device *pdev) + { + int ret; +@@ -584,34 +704,61 @@ static int hsmp_acpi_probe(struct platform_device *pdev) + if (!hsmp_pdev) + return -ENOMEM; + +- if (!hsmp_pdev->is_probed) { ++ /* ++ * Multiple ACPI socket devices probe in parallel, but the one-time ++ * socket-array allocation and /dev/hsmp registration below must run ++ * exactly once. Hold the socket rwsem for write across the whole ++ * bring-up so it cannot race a concurrent probe or remove, and so the ++ * probe-failure teardown drains the data plane. ++ */ ++ guard(rwsem_write)(&hsmp_sock_rwsem); ++ ++ if (!hsmp_pdev->sock) { + hsmp_pdev->num_sockets = topology_max_packages(); + if (!hsmp_pdev->num_sockets) { + dev_err(&pdev->dev, "No CPU sockets detected\n"); + return -ENODEV; + } + +- hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets, +- sizeof(*hsmp_pdev->sock), +- GFP_KERNEL); ++ hsmp_pdev->sock = kcalloc(hsmp_pdev->num_sockets, ++ sizeof(*hsmp_pdev->sock), ++ GFP_KERNEL); + if (!hsmp_pdev->sock) + return -ENOMEM; ++ ++ hsmp_init_metric_read_locks(hsmp_pdev); ++ kref_init(&hsmp_acpi_sock_kref); ++ } else { ++ kref_get(&hsmp_acpi_sock_kref); + } + ++ /* ++ * This socket now holds a reference (kref_init on the first socket, ++ * kref_get afterwards). Every failure path below drops it via ++ * hsmp_acpi_probe_failure_cleanup(), and a successful probe hands it to ++ * hsmp_acpi_remove(). ++ */ + ret = init_acpi(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize HSMP interface.\n"); ++ hsmp_acpi_probe_failure_cleanup(&pdev->dev); + return ret; + } + +- if (!hsmp_pdev->is_probed) { +- ret = hsmp_misc_register(&pdev->dev); ++ if (IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) { ++ /* ++ * Register /dev/hsmp unparented. It is a singleton shared by all ++ * ACPI sockets and outlives all but the last of them, so ++ * parenting it to this socket's device would leave a dangling ++ * parent once that socket is unbound. ++ */ ++ ret = hsmp_misc_register(NULL); + if (ret) { + dev_err(&pdev->dev, "Failed to register misc device\n"); ++ hsmp_acpi_probe_failure_cleanup(&pdev->dev); + return ret; + } +- hsmp_pdev->is_probed = true; +- dev_dbg(&pdev->dev, "AMD HSMP ACPI is probed successfully\n"); ++ dev_dbg(&pdev->dev, "AMD HSMP ACPI misc device registered\n"); + } + + return 0; +@@ -619,14 +766,26 @@ static int hsmp_acpi_probe(struct platform_device *pdev) + + static void hsmp_acpi_remove(struct platform_device *pdev) + { ++ struct hsmp_socket *sock = dev_get_drvdata(&pdev->dev); ++ + /* +- * We register only one misc_device even on multi-socket system. +- * So, deregister should happen only once. ++ * Serialize the kref_put() and any release it triggers against a ++ * concurrent probe, and drain the data plane for the whole ++ * teardown: this covers the per-socket unbind, whose mailbox devres ++ * unmaps once we return, and the last unbind that frees the socket ++ * array in hsmp_acpi_sock_release(). + */ +- if (hsmp_pdev->is_probed) { +- hsmp_misc_deregister(); +- hsmp_pdev->is_probed = false; +- } ++ guard(rwsem_write)(&hsmp_sock_rwsem); ++ ++ /* ++ * Clear this socket's dev so hsmp_send_message() rejects it before ++ * devres unmaps the mailbox. On a non-final unbind the socket array ++ * stays alive, so without this a later message to this index would ++ * reach an unmapped iomem region. ++ */ ++ sock->dev = NULL; ++ ++ kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); + } + + static struct platform_driver amd_hsmp_driver = { +diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c +--- a/drivers/platform/x86/amd/hsmp/hsmp.c ++++ b/drivers/platform/x86/amd/hsmp/hsmp.c +@@ -10,10 +10,17 @@ + #include + + #include ++#include + #include + #include ++#include ++#include ++#include ++#include + #include ++#include + #include ++#include + + #include "hsmp.h" + +@@ -40,6 +47,17 @@ + + static struct hsmp_plat_device hsmp_pdev; + ++/* ++ * Gates the AMD HSMP data plane against socket bring-up and teardown. ++ * ++ * hsmp_send_message() takes it for read, so open /dev/hsmp fds and hwmon reads ++ * run concurrently. Probe and remove take it for write: probe brings sockets ++ * up (running the mailbox handshake via hsmp_send_message_locked()) and remove ++ * tears them down, both excluding and draining the data plane. ++ */ ++DECLARE_RWSEM(hsmp_sock_rwsem); ++EXPORT_SYMBOL_NS_GPL(hsmp_sock_rwsem, "AMD_HSMP"); ++ + /* + * Send a message to the HSMP port via PCI-e config space registers + * or by writing to MMIO space. +@@ -182,29 +200,33 @@ static int validate_message(struct hsmp_message *msg) + return -EINVAL; + + /* +- * Some older HSMP SET messages are updated to add GET in the same message. +- * In these messages, GET returns the current value and SET also returns +- * the successfully set value. To support this GET and SET in same message +- * while maintaining backward compatibility for the HSMP users, +- * hsmp_msg_desc_table[] indicates only maximum allowed response_sz. ++ * As the HSMP protocol evolves, newer platforms may define more ++ * response arguments for existing messages. Use an upper-bound ++ * check so that older userspace callers requesting fewer response ++ * words than what the current hsmp_msg_desc_table[] defines are ++ * still accepted, while rejecting requests that exceed the ++ * hardware capability. + */ +- if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET) { +- if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) +- return -EINVAL; +- } else { +- /* only HSMP_SET or HSMP_GET messages go through this strict check */ +- if (msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz) +- return -EINVAL; +- } ++ if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) ++ return -EINVAL; ++ + return 0; + } + +-int hsmp_send_message(struct hsmp_message *msg) ++/* ++ * Core message send. The caller must hold hsmp_sock_rwsem: the data plane ++ * takes it for read so many messages run concurrently, while the probe-time ++ * senders run under the write lock taken by probe. Holding it here serializes ++ * every message against socket teardown, which also holds it for write. ++ */ ++static int hsmp_send_message_locked(struct hsmp_message *msg) + { + struct hsmp_socket *sock; + unsigned int sock_ind; + int ret; + ++ lockdep_assert_held(&hsmp_sock_rwsem); ++ + if (!msg) + return -EINVAL; + ret = validate_message(msg); +@@ -223,6 +245,20 @@ int hsmp_send_message(struct hsmp_message *msg) + sock_ind = array_index_nospec(msg->sock_ind, hsmp_pdev.num_sockets); + sock = &hsmp_pdev.sock[sock_ind]; + ++ /* ++ * A slot exists for every possible socket, but it is only usable once ++ * that socket has actually been probed. Reject messages aimed at a ++ * socket that was never brought up or is still in bring-up, so we never ++ * operate on a zero-initialized semaphore or an unmapped mailbox. A ++ * non-NULL dev also guarantees virt_base_addr, the mailbox offsets and ++ * the semaphore are visible. ++ * ++ * Held under hsmp_sock_rwsem; pairs with smp_store_release(&sock->dev) ++ * in hsmp_parse_acpi_table(). ++ */ ++ if (!smp_load_acquire(&sock->dev)) ++ return -ENODEV; ++ + ret = down_interruptible(&sock->hsmp_sem); + if (ret < 0) + return ret; +@@ -233,6 +269,19 @@ int hsmp_send_message(struct hsmp_message *msg) + + return ret; + } ++ ++int hsmp_send_message(struct hsmp_message *msg) ++{ ++ /* ++ * Data-plane entry point: open /dev/hsmp fds and hwmon sysfs reads issue ++ * messages from here. Take hsmp_sock_rwsem for read so messages run ++ * concurrently with each other but are drained and kept out while ++ * probe/remove hold it for write to tear a socket down. ++ */ ++ guard(rwsem_read)(&hsmp_sock_rwsem); ++ ++ return hsmp_send_message_locked(msg); ++} + EXPORT_SYMBOL_NS_GPL(hsmp_send_message, "AMD_HSMP"); + + int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args) +@@ -273,7 +322,7 @@ int hsmp_test(u16 sock_ind, u32 value) + msg.args[0] = value; + msg.sock_ind = sock_ind; + +- ret = hsmp_send_message(&msg); ++ ret = hsmp_send_message_locked(&msg); + if (ret) + return ret; + +@@ -301,7 +350,7 @@ static bool is_get_msg(struct hsmp_message *msg) + return false; + } + +-long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++static long hsmp_ioctl_msg(struct file *fp, unsigned long arg) + { + int __user *arguser = (int __user *)arg; + struct hsmp_message msg = { 0 }; +@@ -370,11 +419,139 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) + return 0; + } + +-ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) ++static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, ++ size_t size); ++ ++/* ++ * Fetch the firmware metric (telemetry) table for the requested socket and ++ * copy it to the userspace buffer described by the request. ++ * ++ * The metric table size is variable across HSMP protocol versions and on ++ * Family 1Ah Model 50h-5Fh exceeds PAGE_SIZE. The request carries the buffer ++ * size, which may be anything up to the size firmware reported for this ++ * socket's table. ++ */ ++static long hsmp_ioctl_get_telemetry(struct file *fp, unsigned long arg) ++{ ++ void *kbuf __free(kvfree) = NULL; ++ void __user *arguser = (void __user *)arg; ++ struct hsmp_telemetry_data req; ++ struct hsmp_socket *sock; ++ void __user *user_buf; ++ size_t tbl_size; ++ unsigned int sock_ind; ++ int ret; ++ ++ /* Telemetry data is read-only; require read access on the fd. */ ++ if (!(fp->f_mode & FMODE_READ)) ++ return -EPERM; ++ ++ if (copy_from_user(&req, arguser, sizeof(req))) ++ return -EFAULT; ++ ++ /* ++ * Reserved fields must be zero so future kernels can safely ++ * repurpose them without breaking already-deployed userspace. ++ */ ++ if (req.reserved) ++ return -EINVAL; ++ ++ user_buf = u64_to_user_ptr(req.buf); ++ ++ /* ++ * /dev/hsmp is a singleton character device that outlives an individual ++ * socket unbind, so an ioctl on an already-open fd can run concurrently ++ * with socket teardown. Hold hsmp_sock_rwsem for read across the socket ++ * lookup, the checks on its metric-table state and the read itself: ++ * probe and remove take the same lock for write, so they cannot free the ++ * socket array, unmap the table or destroy the per-socket mutex while ++ * this runs. ++ * ++ * The lock is dropped before the copy_to_user() below. Faulting in the ++ * destination can block indefinitely on a userfaultfd-backed buffer, ++ * which would leave a socket unbind waiting for the write lock. ++ */ ++ scoped_guard(rwsem_read, &hsmp_sock_rwsem) { ++ if (!hsmp_pdev.sock || req.sock_ind >= hsmp_pdev.num_sockets) ++ return -ENODEV; ++ ++ /* ++ * Sanitize the user-controlled socket index against speculative ++ * execution. The bounds check above retires the out-of-range ++ * case with -ENODEV, but a mispredicted branch can still let the ++ * CPU speculatively use sock_ind as an index into ++ * hsmp_pdev.sock[] and pull arbitrary kernel memory into the ++ * cache (Spectre v1, CVE-2017-5753). array_index_nospec() turns ++ * the bounds check into a data-flow clamp so the speculative ++ * load is in-range too. ++ */ ++ sock_ind = array_index_nospec(req.sock_ind, hsmp_pdev.num_sockets); ++ sock = &hsmp_pdev.sock[sock_ind]; ++ if (!sock->metric_tbl_addr) ++ return -ENODEV; ++ ++ tbl_size = sock->metric_tbl_size; ++ if (!tbl_size) ++ return -ENODEV; ++ ++ /* ++ * A request shorter than the firmware table is served with the ++ * leading @size bytes of the snapshot, so userspace built ++ * against an older table layout keeps working on firmware that ++ * grew the table. Asking for more than firmware provides is ++ * rejected rather than short-written, so a caller can never ++ * mistake a partial copy for a full one. ++ */ ++ if (!req.size || req.size > tbl_size) ++ return -EINVAL; ++ ++ /* ++ * The bounce buffer is overwritten in full by memcpy_fromio() ++ * inside hsmp_metric_tbl_read_locked(); use kvmalloc() to avoid ++ * the zeroing cost of kvzalloc() on the ~13 KB allocation done ++ * on every ioctl call. ++ */ ++ kbuf = kvmalloc(tbl_size, GFP_KERNEL); ++ if (!kbuf) ++ return -ENOMEM; ++ ++ ret = hsmp_metric_tbl_read_locked(sock, kbuf, tbl_size); ++ } ++ ++ if (ret < 0) ++ return ret; ++ ++ if (copy_to_user(user_buf, kbuf, req.size)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ switch (cmd) { ++ case HSMP_IOCTL_CMD: ++ return hsmp_ioctl_msg(fp, arg); ++ case HSMP_IOCTL_GET_TELEMETRY_DATA: ++ return hsmp_ioctl_get_telemetry(fp, arg); ++ default: ++ return -ENOTTY; ++ } ++} ++ ++/* ++ * Caller must hold hsmp_sock_rwsem. It keeps @sock, its metric-table mapping ++ * and its metric_read_lock alive: probe and remove take the same lock for ++ * write while they bring sockets up and tear them down. ++ */ ++static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, ++ size_t size) + { + struct hsmp_message msg = { 0 }; + int ret; + ++ lockdep_assert_held(&hsmp_sock_rwsem); ++ + if (!sock || !buf) + return -EINVAL; + +@@ -383,8 +560,7 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) + return -ENOMEM; + } + +- /* Do not support lseek(), also don't allow more than the size of metric table */ +- if (size != sizeof(struct hsmp_metric_table)) { ++ if (size != sock->metric_tbl_size) { + dev_err(sock->dev, "Wrong buffer size\n"); + return -EINVAL; + } +@@ -392,27 +568,77 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) + msg.msg_id = HSMP_GET_METRIC_TABLE; + msg.sock_ind = sock->sock_ind; + +- ret = hsmp_send_message(&msg); ++ /* ++ * HSMP_GET_METRIC_TABLE makes firmware refill this socket's shared ++ * metric DRAM region, which is then copied out below. Hold the ++ * per-socket lock across the fill-and-copy so concurrent readers of the ++ * same socket cannot return a torn snapshot. ++ */ ++ guard(mutex)(&sock->metric_read_lock); ++ ++ ret = hsmp_send_message_locked(&msg); + if (ret) + return ret; + memcpy_fromio(buf, sock->metric_tbl_addr, size); + + return size; + } ++ ++ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) ++{ ++ guard(rwsem_read)(&hsmp_sock_rwsem); ++ ++ return hsmp_metric_tbl_read_locked(sock, buf, size); ++} + EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP"); + ++void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev) ++{ ++ u16 i; ++ ++ for (i = 0; i < pdev->num_sockets; i++) ++ mutex_init(&pdev->sock[i].metric_read_lock); ++} ++EXPORT_SYMBOL_NS_GPL(hsmp_init_metric_read_locks, "AMD_HSMP"); ++ ++void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev) ++{ ++ u16 i; ++ ++ for (i = 0; i < pdev->num_sockets; i++) ++ mutex_destroy(&pdev->sock[i].metric_read_lock); ++} ++EXPORT_SYMBOL_NS_GPL(hsmp_destroy_metric_read_locks, "AMD_HSMP"); ++ ++void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev) ++{ ++ struct hsmp_socket *sock; ++ u16 i; ++ ++ for (i = 0; i < pdev->num_sockets; i++) { ++ sock = &pdev->sock[i]; ++ if (sock->metric_tbl_addr) { ++ iounmap(sock->metric_tbl_addr); ++ sock->metric_tbl_addr = NULL; ++ } ++ sock->metric_tbl_size = 0; ++ } ++} ++EXPORT_SYMBOL_NS_GPL(hsmp_unmap_metric_tbls, "AMD_HSMP"); ++ + int hsmp_get_tbl_dram_base(u16 sock_ind) + { + struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind]; + struct hsmp_message msg = { 0 }; + phys_addr_t dram_addr; ++ size_t tbl_size; + int ret; + + msg.sock_ind = sock_ind; + msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz; + msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR; + +- ret = hsmp_send_message(&msg); ++ ret = hsmp_send_message_locked(&msg); + if (ret) + return ret; + +@@ -425,12 +651,33 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) + dev_err(sock->dev, "Invalid DRAM address for metric table\n"); + return -ENOMEM; + } +- sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr, +- sizeof(struct hsmp_metric_table)); ++ /* ++ * The ACPI socket array is shared across sockets and outlives a ++ * per-socket unbind, so metric_tbl_addr may hold a mapping from an ++ * earlier bind of this socket. Unmap it before remapping so an ++ * unbind/rebind cycle does not leak a metric-table mapping. This runs ++ * during probe before the metric sysfs attribute is exposed, so no ++ * reader can be using it. ++ */ ++ if (sock->metric_tbl_addr) { ++ iounmap(sock->metric_tbl_addr); ++ sock->metric_tbl_addr = NULL; ++ } ++ sock->metric_tbl_size = 0; ++ ++ /* SMU returns table size from Family 1Ah Model 50h and forward */ ++ if (msg.args[2]) ++ tbl_size = msg.args[2]; ++ else ++ tbl_size = sizeof(struct hsmp_metric_table); ++ ++ sock->metric_tbl_addr = ioremap(dram_addr, tbl_size); + if (!sock->metric_tbl_addr) { + dev_err(sock->dev, "Failed to ioremap metric table addr\n"); + return -ENOMEM; + } ++ sock->metric_tbl_size = tbl_size; ++ + return 0; + } + EXPORT_SYMBOL_NS_GPL(hsmp_get_tbl_dram_base, "AMD_HSMP"); +@@ -444,7 +691,7 @@ int hsmp_cache_proto_ver(u16 sock_ind) + msg.sock_ind = sock_ind; + msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz; + +- ret = hsmp_send_message(&msg); ++ ret = hsmp_send_message_locked(&msg); + if (!ret) + hsmp_pdev.proto_ver = msg.args[0]; + +@@ -463,6 +710,14 @@ int hsmp_misc_register(struct device *dev) + hsmp_pdev.mdev.name = HSMP_CDEV_NAME; + hsmp_pdev.mdev.minor = MISC_DYNAMIC_MINOR; + hsmp_pdev.mdev.fops = &hsmp_fops; ++ /* ++ * The caller chooses the parent. The platform driver has a single ++ * device whose lifetime matches /dev/hsmp and parents it there. The ++ * ACPI driver passes NULL: its /dev/hsmp is a singleton shared by ++ * per-socket devices that can be unbound individually and out of order, ++ * so parenting it to one would leave it attached to an already-removed ++ * device. ++ */ + hsmp_pdev.mdev.parent = dev; + hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME; + hsmp_pdev.mdev.mode = 0644; +@@ -474,6 +729,7 @@ EXPORT_SYMBOL_NS_GPL(hsmp_misc_register, "AMD_HSMP"); + void hsmp_misc_deregister(void) + { + misc_deregister(&hsmp_pdev.mdev); ++ hsmp_pdev.mdev.this_device = NULL; + } + EXPORT_SYMBOL_NS_GPL(hsmp_misc_deregister, "AMD_HSMP"); + +diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h +--- a/drivers/platform/x86/amd/hsmp/hsmp.h ++++ b/drivers/platform/x86/amd/hsmp/hsmp.h +@@ -15,9 +15,12 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include ++#include + + #define HSMP_METRICS_TABLE_NAME "metrics_bin" + +@@ -27,7 +30,7 @@ + #define HSMP_DEVNODE_NAME "hsmp" + #define ACPI_HSMP_DEVICE_HID "AMDI0097" + +-#define DRIVER_VERSION "2.5" ++#define DRIVER_VERSION "2.6" + + struct hsmp_mbaddr_info { + u32 base_addr; +@@ -41,8 +44,12 @@ struct hsmp_socket { + struct bin_attribute hsmp_attr; + struct hsmp_mbaddr_info mbinfo; + void __iomem *metric_tbl_addr; ++ /* Size of the region mapped at @metric_tbl_addr, as reported by SMU */ ++ size_t metric_tbl_size; + void __iomem *virt_base_addr; + struct semaphore hsmp_sem; ++ /* Serializes HSMP_GET_METRIC_TABLE fill-and-copy for this socket */ ++ struct mutex metric_read_lock; + char name[HSMP_ATTR_GRP_NAME_SIZE]; + struct device *dev; + u16 sock_ind; +@@ -54,7 +61,6 @@ struct hsmp_plat_device { + struct hsmp_socket *sock; + u32 proto_ver; + u16 num_sockets; +- bool is_probed; + }; + + int hsmp_cache_proto_ver(u16 sock_ind); +@@ -63,6 +69,9 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg); + void hsmp_misc_deregister(void); + int hsmp_misc_register(struct device *dev); + int hsmp_get_tbl_dram_base(u16 sock_ind); ++void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev); ++void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev); ++void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev); + ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size); + struct hsmp_plat_device *get_hsmp_pdev(void); + #if IS_ENABLED(CONFIG_HWMON) +@@ -71,4 +80,10 @@ int hsmp_create_sensor(struct device *dev, u16 sock_ind); + static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; } + #endif + int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args); ++ ++/* ++ * Gates the HSMP data plane: hsmp_send_message() takes it for read; probe and ++ * remove take it for write to bring sockets up and tear them down. ++ */ ++extern struct rw_semaphore hsmp_sock_rwsem; + #endif /* HSMP_H */ +diff --git a/drivers/platform/x86/amd/hsmp/hwmon.c b/drivers/platform/x86/amd/hsmp/hwmon.c +--- a/drivers/platform/x86/amd/hsmp/hwmon.c ++++ b/drivers/platform/x86/amd/hsmp/hwmon.c +@@ -31,6 +31,9 @@ static int hsmp_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + if (attr != hwmon_power_cap) + return -EOPNOTSUPP; + ++ if (val < 0) ++ return -EINVAL; ++ + msg.num_args = 1; + msg.args[0] = val / MICROWATT_PER_MILLIWATT; + msg.msg_id = HSMP_SET_SOCKET_POWER_LIMIT; +diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c +--- a/drivers/platform/x86/amd/hsmp/plat.c ++++ b/drivers/platform/x86/amd/hsmp/plat.c +@@ -13,12 +13,14 @@ + + #include + #include ++#include + #include + #include + #include + #include + #include + #include ++#include + #include + + #include +@@ -201,6 +203,25 @@ static int init_platform_device(struct device *dev) + return 0; + } + ++/* ++ * The socket array is devm-managed and freed by the driver core, but the ++ * metric-table DRAM regions are mapped with plain ioremap() during probe and ++ * the per-socket mutexes need an explicit mutex_destroy(), neither of which ++ * devres covers. ++ * ++ * Take the data-plane rwsem for write to drain any in-flight ++ * hsmp_send_message(), unmap the metric tables, destroy the mutexes and drop ++ * the global socket pointer, all before devres frees the array. Registered as ++ * a devres action so it runs on both remove and probe failure. ++ */ ++static void hsmp_pltdrv_release(void *data) ++{ ++ guard(rwsem_write)(&hsmp_sock_rwsem); ++ hsmp_unmap_metric_tbls(hsmp_pdev); ++ hsmp_destroy_metric_read_locks(hsmp_pdev); ++ hsmp_pdev->sock = NULL; ++} ++ + static int hsmp_pltdrv_probe(struct platform_device *pdev) + { + int ret; +@@ -211,7 +232,22 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev) + if (!hsmp_pdev->sock) + return -ENOMEM; + +- ret = init_platform_device(&pdev->dev); ++ hsmp_init_metric_read_locks(hsmp_pdev); ++ ++ ret = devm_add_action_or_reset(&pdev->dev, hsmp_pltdrv_release, NULL); ++ if (ret) ++ return ret; ++ ++ /* ++ * init_platform_device() runs the mailbox handshake via the probe-only ++ * senders, which issue messages through hsmp_send_message_locked() and ++ * so require hsmp_sock_rwsem held. Hold it for write, matching probe's ++ * role as a socket bring-up path. The lock is not held across ++ * devm_add_action_or_reset() above so the release action, which also ++ * takes it for write, does not deadlock if that registration fails. ++ */ ++ scoped_guard(rwsem_write, &hsmp_sock_rwsem) ++ ret = init_platform_device(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Failed to init HSMP mailbox\n"); + return ret; +diff --git a/drivers/platform/x86/amd/pmc/mp1_stb.c b/drivers/platform/x86/amd/pmc/mp1_stb.c +--- a/drivers/platform/x86/amd/pmc/mp1_stb.c ++++ b/drivers/platform/x86/amd/pmc/mp1_stb.c +@@ -299,10 +299,7 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) + if (!enable_stb) + return 0; + +- if (amd_is_stb_supported(dev)) { +- debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, +- &amd_stb_debugfs_fops_v2); +- } else { ++ if (!amd_is_stb_supported(dev)) { + debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, + &amd_stb_debugfs_fops); + return 0; +@@ -342,8 +339,18 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) + } + + dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size); +- if (!dev->stb_virt_addr) ++ if (!dev->stb_virt_addr) { + ret = -ENOMEM; ++ goto out; ++ } ++ ++ /* ++ * Only expose stb_read once the buffer is mapped; otherwise a read ++ * faults on a NULL dev->stb_virt_addr, now that a failed STB init no ++ * longer aborts probe. ++ */ ++ debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, ++ &amd_stb_debugfs_fops_v2); + + out: + /* Restore the default message port for subsequent SMU operations */ +diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c +--- a/drivers/platform/x86/amd/pmc/pmc-quirks.c ++++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c +@@ -71,6 +71,14 @@ static const struct dmi_system_id fwbug_list[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "20XK"), + } + }, ++ { ++ .ident = "T14 Gen2 AMD", ++ .driver_data = &quirk_s2idle_spurious_8042, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "20XL"), ++ } ++ }, + { + .ident = "T14 Gen1 AMD", + .driver_data = &quirk_s2idle_spurious_8042, +diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c +--- a/drivers/platform/x86/amd/pmc/pmc.c ++++ b/drivers/platform/x86/amd/pmc/pmc.c +@@ -939,19 +939,22 @@ static int amd_pmc_probe(struct platform_device *pdev) + } + + amd_pmc_dbgfs_register(dev); ++ ++ /* ++ * STB is an optional debug facility (enable_stb); a failure to set it ++ * up must not stop the rest of the driver - most importantly the s0i3 ++ * LPS0 handler - from working, so treat it as non-fatal. ++ */ + err = amd_stb_s2d_init(dev); + if (err) +- goto err_dbgfs_unregister; ++ dev_warn(dev->dev, "STB initialization failed (%d), continuing without STB support\n", ++ err); + + if (IS_ENABLED(CONFIG_AMD_MP2_STB)) + amd_mp2_stb_init(dev); + pm_report_max_hw_sleep(U64_MAX); + return 0; + +-err_dbgfs_unregister: +- amd_pmc_dbgfs_unregister(dev); +- if (IS_ENABLED(CONFIG_SUSPEND)) +- acpi_unregister_lps0_dev(&amd_pmc_s2idle_dev_ops); + err_pci_dev_put: + pci_dev_put(rdev); + return err; +diff --git a/drivers/platform/x86/amd/pmf/Kconfig b/drivers/platform/x86/amd/pmf/Kconfig +--- a/drivers/platform/x86/amd/pmf/Kconfig ++++ b/drivers/platform/x86/amd/pmf/Kconfig +@@ -30,3 +30,13 @@ config AMD_PMF_DEBUG + in the PMF config store. + + Say Y here to enable more debug logs and Say N here if you are not sure. ++ ++config AMD_PMF_UTIL_SUPPORT ++ bool "AMD PMF Util layer support" ++ depends on AMD_PMF ++ help ++ Enabling this option provides a character device for userspace to capture ++ PMF features (Smart PC Builder, Auto Mode, Static Power Slider, Dynamic ++ Power Slider AC/DC) along with PMF metrics from the AMD PMF driver. ++ ++ Say Y here to enable it and Say N here if you are not sure. +diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile +--- a/drivers/platform/x86/amd/pmf/Makefile ++++ b/drivers/platform/x86/amd/pmf/Makefile +@@ -7,4 +7,6 @@ + obj-$(CONFIG_AMD_PMF) += amd-pmf.o + amd-pmf-y := core.o acpi.o sps.o \ + auto-mode.o cnqf.o \ +- tee-if.o spc.o ++ tee-if.o spc.o metrics.o ++# Build util.c only when AMD_PMF_UTIL_SUPPORT is enabled ++amd-pmf-$(CONFIG_AMD_PMF_UTIL_SUPPORT) += util.o +diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c +--- a/drivers/platform/x86/amd/pmf/core.c ++++ b/drivers/platform/x86/amd/pmf/core.c +@@ -11,13 +11,13 @@ + #include + #include + #include ++#include + #include + #include + #include + #include + #include + #include +-#include + #include + #include "pmf.h" + +@@ -26,6 +26,13 @@ + #define AMD_PMF_REGISTER_RESPONSE 0xA78 + #define AMD_PMF_REGISTER_ARGUMENT 0xA58 + ++/* PMF-SMU communication registers for 1AH_M80H */ ++#define AMD_PMF_REGISTER_MESSAGE_V2 0xA04 ++#define AMD_PMF_REGISTER_RESPONSE_V2 0xA08 ++#define AMD_PMF_REGISTER_ARGUMENT0_V2 0xA0C ++#define AMD_PMF_REGISTER_ARGUMENT1_V2 0xAAC ++#define AMD_PMF_REGISTER_ARGUMENT2_V2 0xAB0 ++ + /* Base address of SMU for mapping physical address to virtual address */ + #define AMD_PMF_MAPPING_SIZE 0x01000 + #define AMD_PMF_BASE_ADDR_OFFSET 0x10000 +@@ -48,7 +55,7 @@ + #define DELAY_MAX_US 3000 + + /* override Metrics Table sample size time (in ms) */ +-static int metrics_table_loop_ms = 1000; ++int metrics_table_loop_ms = 1000; + module_param(metrics_table_loop_ms, int, 0644); + MODULE_PARM_DESC(metrics_table_loop_ms, "Metrics Table sample size time (default = 1000ms)"); + +@@ -61,7 +68,15 @@ static bool smart_pc_support = true; + module_param(smart_pc_support, bool, 0444); + MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)"); + +-static struct device *pmf_device; ++static bool amd_pmf_supports_accumulator_metrics(struct amd_pmf_dev *pdev) ++{ ++ switch (pdev->cpu_id) { ++ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: ++ return true; ++ default: ++ return false; ++ } ++} + + static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data) + { +@@ -131,37 +146,6 @@ int amd_pmf_get_power_source(void) + return POWER_SOURCE_DC; + } + +-static void amd_pmf_get_metrics(struct work_struct *work) +-{ +- struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work); +- ktime_t time_elapsed_ms; +- int socket_power; +- +- guard(mutex)(&dev->update_mutex); +- +- /* Transfer table contents */ +- memset(dev->buf, 0, sizeof(dev->m_table)); +- amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); +- memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table)); +- +- time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time; +- /* Calculate the avg SoC power consumption */ +- socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power; +- +- if (dev->amt_enabled) { +- /* Apply the Auto Mode transition */ +- amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms); +- } +- +- if (dev->cnqf_enabled) { +- /* Apply the CnQF transition */ +- amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms); +- } +- +- dev->start_time = ktime_to_ms(ktime_get()); +- schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms)); +-} +- + static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset) + { + return ioread32(dev->regbase + reg_offset); +@@ -176,13 +160,19 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev) + { + u32 value; + +- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_RESPONSE); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->resp_reg); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_RESPONSE:%x\n", value); + +- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT:%d\n", value); ++ if (amd_pmf_supports_accumulator_metrics(dev)) { ++ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[1]); ++ dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT1:%d\n", value); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[2]); ++ dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT2:%d\n", value); ++ } + +- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_MESSAGE); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->msg_reg); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value); + } + +@@ -208,7 +198,7 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + guard(mutex)(&dev->lock); + + /* Wait until we get a valid response */ +- rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE, ++ rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg, + val, val != 0, PMF_MSG_DELAY_MIN_US, + PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); + if (rc) { +@@ -217,16 +207,16 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + } + + /* Write zero to response register */ +- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_RESPONSE, 0); ++ amd_pmf_reg_write(dev, dev->smu_regs->resp_reg, 0); + + /* Write argument into argument register */ +- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_ARGUMENT, arg); ++ amd_pmf_reg_write(dev, dev->smu_regs->arg_reg[0], arg); + + /* Write message ID to message ID register */ +- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_MESSAGE, message); ++ amd_pmf_reg_write(dev, dev->smu_regs->msg_reg, message); + + /* Wait until we get a valid response */ +- rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE, ++ rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg, + val, val != 0, PMF_MSG_DELAY_MIN_US, + PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); + if (rc) { +@@ -239,7 +229,14 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + if (get) { + /* PMFW may take longer time to return back the data */ + usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US); +- *data = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT); ++ *data = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]); ++ if (amd_pmf_supports_accumulator_metrics(dev) && ++ message == GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR) { ++ dev->dram_addr.hi = amd_pmf_reg_read(dev, ++ dev->smu_regs->arg_reg[1]); ++ dev->dram_addr.size = amd_pmf_reg_read(dev, ++ dev->smu_regs->arg_reg[2]); ++ } + } + break; + case AMD_PMF_RESULT_CMD_REJECT_BUSY: +@@ -262,132 +259,32 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + return rc; + } + +-static const struct pci_device_id pmf_pci_ids[] = { +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RMB) }, +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PS) }, +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M20H_ROOT) }, +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M60H_ROOT) }, +- { } ++/* RMB, PS, 1AH_M20H and 1AH_M60H share the same v1 SMU mailbox registers */ ++static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v1 = { ++ .msg_reg = AMD_PMF_REGISTER_MESSAGE, ++ .resp_reg = AMD_PMF_REGISTER_RESPONSE, ++ .arg_reg = { AMD_PMF_REGISTER_ARGUMENT, 0, 0 }, + }; + +-int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer) +-{ +- u64 phys_addr; +- u32 hi, low; ++/* 1AH_M80H uses an extended mailbox with three argument registers */ ++static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v2 = { ++ .msg_reg = AMD_PMF_REGISTER_MESSAGE_V2, ++ .resp_reg = AMD_PMF_REGISTER_RESPONSE_V2, ++ .arg_reg = { ++ AMD_PMF_REGISTER_ARGUMENT0_V2, ++ AMD_PMF_REGISTER_ARGUMENT1_V2, ++ AMD_PMF_REGISTER_ARGUMENT2_V2, ++ }, ++}; + +- /* Get Metrics Table Address */ +- if (alloc_buffer) { +- switch (dev->cpu_id) { +- case AMD_CPU_ID_PS: +- case AMD_CPU_ID_RMB: +- dev->mtable_size = sizeof(dev->m_table); +- break; +- case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: +- case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: +- dev->mtable_size = sizeof(dev->m_table_v2); +- break; +- default: +- dev_err(dev->dev, "Invalid CPU id: 0x%x", dev->cpu_id); +- } +- +- dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL); +- if (!dev->buf) +- return -ENOMEM; +- } +- +- phys_addr = virt_to_phys(dev->buf); +- hi = phys_addr >> 32; +- low = phys_addr & GENMASK(31, 0); +- +- amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL); +- amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL); +- +- return 0; +-} +- +-int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev) +-{ +- int ret; +- +- INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics); +- +- ret = amd_pmf_set_dram_addr(dev, true); +- if (ret) +- return ret; +- +- /* +- * Start collecting the metrics data after a small delay +- * or else, we might end up getting stale values from PMFW. +- */ +- schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3)); +- +- return 0; +-} +- +-static int is_npu_metrics_supported(struct amd_pmf_dev *pdev) +-{ +- switch (pdev->cpu_id) { +- case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: +- case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: +- return 0; +- default: +- return -EOPNOTSUPP; +- } +-} +- +-static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data) +-{ +- int ret, i; +- +- guard(mutex)(&dev->metrics_mutex); +- +- ret = is_npu_metrics_supported(dev); +- if (ret) +- return ret; +- +- ret = amd_pmf_set_dram_addr(dev, true); +- if (ret) +- return ret; +- +- memset(dev->buf, 0, dev->mtable_size); +- +- /* Send SMU command to get NPU metrics */ +- ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); +- if (ret) { +- dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret); +- return ret; +- } +- +- memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size); +- +- data->npuclk_freq = dev->m_table_v2.npuclk_freq; +- for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++) +- data->npu_busy[i] = dev->m_table_v2.npu_busy[i]; +- data->npu_power = dev->m_table_v2.npu_power; +- data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq; +- data->npu_reads = dev->m_table_v2.npu_reads; +- data->npu_writes = dev->m_table_v2.npu_writes; +- +- return 0; +-} +- +-int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info) +-{ +- struct amd_pmf_dev *pdev; +- +- if (!info) +- return -EINVAL; +- +- if (!pmf_device) +- return -ENODEV; +- +- pdev = dev_get_drvdata(pmf_device); +- if (!pdev) +- return -ENODEV; +- +- return amd_pmf_get_smu_metrics(pdev, info); +-} +-EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF"); ++static const struct pci_device_id pmf_pci_ids[] = { ++ { PCI_DEVICE_DATA(AMD, CPU_ID_RMB, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, CPU_ID_PS, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, 1AH_M20H_ROOT, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, 1AH_M60H_ROOT, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, 1AH_M80H_ROOT, &amd_pmf_smu_regs_v2) }, ++ { } ++}; + + static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev) + { +@@ -536,6 +433,19 @@ static void amd_pmf_deinit_features(struct amd_pmf_dev *dev) + } + } + ++static int amd_pmf_get_smu_mb_offset(struct amd_pmf_dev *pdev, struct pci_dev *rdev) ++{ ++ const struct pci_device_id *id; ++ ++ id = pci_match_id(pmf_pci_ids, rdev); ++ if (!id) ++ return -ENODEV; ++ ++ pdev->smu_regs = (const struct amd_pmf_smu_regs *)id->driver_data; ++ ++ return 0; ++} ++ + static const struct acpi_device_id amd_pmf_acpi_ids[] = { + {"AMDI0100", 0x100}, + {"AMDI0102", 0}, +@@ -543,6 +453,7 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = { + {"AMDI0105", 0}, + {"AMDI0107", 0}, + {"AMDI0108", 0}, ++ {"AMDI0109", 0}, + { } + }; + MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids); +@@ -624,6 +535,17 @@ static int amd_pmf_probe(struct platform_device *pdev) + if (err) + return err; + ++ /* Populate smu_regs with SoC-specific SMU mailbox register offsets */ ++ err = amd_pmf_get_smu_mb_offset(dev, rdev); ++ if (err) ++ return err; ++ ++ if (amd_pmf_supports_accumulator_metrics(dev)) { ++ err = amd_pmf_get_tbl_dram_addr(dev); ++ if (err) ++ return err; ++ } ++ + apmf_acpi_init(dev); + platform_set_drvdata(pdev, dev); + amd_pmf_dbgfs_register(dev); +@@ -632,7 +554,11 @@ static int amd_pmf_probe(struct platform_device *pdev) + if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) + amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD); + +- pmf_device = dev->dev; ++ amd_pmf_set_device(dev->dev); ++ ++ err = amd_pmf_cdev_register(dev); ++ if (err) ++ dev_warn(dev->dev, "failed to register util interface: %d\n", err); + + dev_info(dev->dev, "registered PMF device successfully\n"); + +@@ -643,6 +569,7 @@ static void amd_pmf_remove(struct platform_device *pdev) + { + struct amd_pmf_dev *dev = platform_get_drvdata(pdev); + ++ amd_pmf_cdev_unregister(); + amd_pmf_deinit_features(dev); + if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) + amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_UNLOAD); +diff --git a/drivers/platform/x86/amd/pmf/metrics.c b/drivers/platform/x86/amd/pmf/metrics.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/amd/pmf/metrics.c +@@ -0,0 +1,330 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * AMD Platform Management Framework Driver - Metrics Support ++ * ++ * Copyright (c) 2026, Advanced Micro Devices, Inc. ++ * All Rights Reserved. ++ * ++ * Authors: Shyam Sundar S K ++ * Patil Rajesh Reddy ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmf.h" ++ ++static struct device *pmf_device; ++ ++static u16 amd_pmf_q10_acc_to_mW(u64 avg_acc) ++{ ++ u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024) * MILLIWATT_PER_WATT; ++ ++ return min_t(u16, val, U16_MAX); ++} ++ ++static u16 amd_pmf_q10_acc_to_int(u64 avg_acc) ++{ ++ u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024); ++ ++ return min_t(u16, val, U16_MAX); ++} ++ ++static u64 amd_pmf_avg_acc_metric(u32 curr_counter, u32 prev_counter, ++ u64 curr_value, u64 prev_value) ++{ ++ u32 counter_diff; ++ u64 val_diff; ++ ++ /* Check for counter reset */ ++ if (curr_counter <= prev_counter) ++ return 0; ++ ++ counter_diff = curr_counter - prev_counter; ++ ++ if (curr_value < prev_value) ++ return 0; ++ ++ val_diff = curr_value - prev_value; ++ ++ return DIV_U64_ROUND_CLOSEST(val_diff, counter_diff); ++} ++ ++int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR, GET_CMD, ++ ARG_NONE, &dev->dram_addr.lo); ++ if (ret) { ++ dev_err(dev->dev, "Failed to get DRAM address: %d\n", ret); ++ return ret; ++ } ++ ++ dev->metrics_table_phys = ((u64)dev->dram_addr.hi << 32) | dev->dram_addr.lo; ++ dev->mtable_size = dev->dram_addr.size; ++ ++ if (dev->mtable_size != sizeof(dev->mtable_v3)) { ++ dev_err(dev->dev, "Metrics table size mismatch: got %u, expected %zu\n", ++ dev->dram_addr.size, sizeof(dev->mtable_v3)); ++ return -EINVAL; ++ } ++ ++ dev->metrics_table_virt = devm_ioremap(dev->dev, dev->metrics_table_phys, dev->mtable_size); ++ if (!dev->metrics_table_virt) { ++ dev_err(dev->dev, "Failed to map DRAM address for PMF metrics table\n"); ++ dev->metrics_table_phys = 0; ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static int amd_pmf_get_metrics_table_log_sample(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ if (!dev->metrics_table_virt) { ++ dev_err(dev->dev, "Metrics DRAM not mapped\n"); ++ return -EINVAL; ++ } ++ ++ /* Send command to PMFW to update metrics table log sample */ ++ ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE, SET_CMD, ARG_NONE, NULL); ++ if (ret) { ++ dev_err(dev->dev, "Failed to request metrics log sample: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void amd_pmf_get_metrics(struct work_struct *work) ++{ ++ struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work); ++ ktime_t time_elapsed_ms; ++ int socket_power; ++ ++ guard(mutex)(&dev->update_mutex); ++ ++ /* Transfer table contents */ ++ memset(dev->buf, 0, sizeof(dev->m_table)); ++ amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); ++ memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table)); ++ ++ time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time; ++ /* Calculate the avg SoC power consumption */ ++ socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power; ++ ++ if (dev->amt_enabled) { ++ /* Apply the Auto Mode transition */ ++ amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms); ++ } ++ ++ if (dev->cnqf_enabled) { ++ /* Apply the CnQF transition */ ++ amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms); ++ } ++ ++ dev->start_time = ktime_to_ms(ktime_get()); ++ schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms)); ++} ++ ++int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer) ++{ ++ u64 phys_addr; ++ u32 hi, low; ++ ++ /* Get Metrics Table Address */ ++ if (alloc_buffer) { ++ switch (dev->cpu_id) { ++ case AMD_CPU_ID_PS: ++ case AMD_CPU_ID_RMB: ++ dev->mtable_size = sizeof(dev->m_table); ++ break; ++ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: ++ dev->mtable_size = sizeof(dev->m_table_v2); ++ break; ++ default: ++ dev_err(dev->dev, "Invalid CPU id: 0x%x\n", dev->cpu_id); ++ } ++ ++ dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL); ++ if (!dev->buf) ++ return -ENOMEM; ++ } ++ ++ phys_addr = virt_to_phys(dev->buf); ++ hi = upper_32_bits(phys_addr); ++ low = lower_32_bits(phys_addr); ++ ++ amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL); ++ amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL); ++ ++ return 0; ++} ++ ++int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics); ++ ++ ret = amd_pmf_set_dram_addr(dev, true); ++ if (ret) ++ return ret; ++ ++ /* ++ * Start collecting the metrics data after a small delay ++ * or else, we might end up getting stale values from PMFW. ++ */ ++ schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3)); ++ ++ return 0; ++} ++ ++void amd_pmf_set_device(struct device *p_device) ++{ ++ pmf_device = p_device; ++} ++ ++static int is_npu_metrics_supported(struct amd_pmf_dev *pdev) ++{ ++ switch (pdev->cpu_id) { ++ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: ++ return 0; ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static void amd_pmf_calculate_acc_npu_metrics(struct amd_pmf_dev *dev, ++ struct amd_pmf_npu_metrics *data) ++{ ++ struct amd_pmf_metrics_iod *curr, *prev; ++ u64 val; ++ int i; ++ ++ curr = &dev->mtable_v3.iod; ++ prev = &dev->prev_metrics.iod; ++ ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npu_temp_acc, prev->npu_temp_acc); ++ data->npu_temp = amd_pmf_q10_acc_to_int(val); ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npu_power_acc, prev->npu_power_acc); ++ data->npu_power = amd_pmf_q10_acc_to_mW(val); ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npuhclk_freq_eff_acc, ++ prev->npuhclk_freq_eff_acc); ++ data->mpnpuclk_freq = amd_pmf_q10_acc_to_int(val); ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->aieclk_freq_eff_acc, ++ prev->aieclk_freq_eff_acc); ++ data->npuclk_freq = amd_pmf_q10_acc_to_int(val); ++ ++ for (i = 0; i < ARRAY_SIZE(curr->npu_busy_acc); i++) { ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npu_busy_acc[i], ++ prev->npu_busy_acc[i]); ++ data->npu_busy[i] = amd_pmf_q10_acc_to_int(val); ++ } ++} ++ ++static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data) ++{ ++ int ret, i; ++ ++ guard(mutex)(&dev->metrics_mutex); ++ ++ ret = is_npu_metrics_supported(dev); ++ if (ret) ++ return ret; ++ ++ memset(data, 0, sizeof(*data)); ++ ++ switch (dev->cpu_id) { ++ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: ++ ret = amd_pmf_set_dram_addr(dev, true); ++ if (ret) ++ return ret; ++ ++ memset(dev->buf, 0, dev->mtable_size); ++ ++ /* Send SMU command to get NPU metrics */ ++ ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); ++ if (ret) { ++ dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret); ++ return ret; ++ } ++ ++ memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size); ++ ++ data->npuclk_freq = dev->m_table_v2.npuclk_freq; ++ for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++) ++ data->npu_busy[i] = dev->m_table_v2.npu_busy[i]; ++ data->npu_power = dev->m_table_v2.npu_power; ++ data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq; ++ data->npu_reads = dev->m_table_v2.npu_reads; ++ data->npu_writes = dev->m_table_v2.npu_writes; ++ break; ++ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: ++ ret = amd_pmf_get_metrics_table_log_sample(dev); ++ if (ret) ++ return ret; ++ ++ memcpy_fromio(&dev->mtable_v3, dev->metrics_table_virt, sizeof(dev->mtable_v3)); ++ ++ /* ++ * Ignore the first sample, as previous metrics is uninitialized (zero) ++ * Metrics are calculated as: ++ * metrics = current_metrics - previous_metrics ++ * Skipping the initial sample ensures accurate delta calculations. ++ */ ++ if (!dev->npu_metrics_have_prev) { ++ memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3)); ++ dev->npu_metrics_have_prev = true; ++ return 0; ++ } ++ ++ amd_pmf_calculate_acc_npu_metrics(dev, data); ++ memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3)); ++ break; ++ } ++ ++ return 0; ++} ++ ++int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info) ++{ ++ struct amd_pmf_dev *pdev; ++ ++ if (!info) ++ return -EINVAL; ++ ++ if (!pmf_device) ++ return -ENODEV; ++ ++ pdev = dev_get_drvdata(pmf_device); ++ if (!pdev) ++ return -ENODEV; ++ ++ return amd_pmf_get_smu_metrics(pdev, info); ++} ++EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF"); +diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h +--- a/drivers/platform/x86/amd/pmf/pmf.h ++++ b/drivers/platform/x86/amd/pmf/pmf.h +@@ -14,10 +14,13 @@ + #include + #include + #include ++#include ++#include + #include + #include + #include + #include ++#include + + #define POLICY_BUF_MAX_SZ 0x4b000 + #define POLICY_SIGN_COOKIE 0x31535024 +@@ -28,6 +31,11 @@ + #define AMD_CPU_ID_PS 0x14e8 + #define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507 + #define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122 ++#define PCI_DEVICE_ID_AMD_1AH_M80H_ROOT 0x115b ++ ++/* Aliases required by PCI_DEVICE_DATA() macro naming convention */ ++#define PCI_DEVICE_ID_AMD_CPU_ID_RMB AMD_CPU_ID_RMB ++#define PCI_DEVICE_ID_AMD_CPU_ID_PS AMD_CPU_ID_PS + + struct cookie_header { + u32 sign; +@@ -70,6 +78,10 @@ struct cookie_header { + #define SET_PMF_PPT 0x25 + #define SET_PMF_PPT_APU_ONLY 0x26 + ++/* Message IDs for 1AH_M80H platform */ ++#define GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE 0x0E ++#define GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR 0x0F ++ + /* OS slider update notification */ + #define DC_BEST_PERF 0 + #define DC_BETTER_PERF 1 +@@ -130,6 +142,14 @@ struct cookie_header { + #define GET_CMD true + + #define METRICS_TABLE_ID 7 ++#define BIOS_OUTPUT_MAX 10 ++ ++extern int metrics_table_loop_ms; ++ ++#define AMD_PMF_METRIC_CORE_TYPE_MAX 4 ++#define AMD_PMF_METRIC_CCX_MAX 4 ++#define AMD_PMF_NUM_CLK_DPM_LEVELS 8 ++#define AMD_PMF_NUM_MAX_CORES 12 + + typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data); + +@@ -242,6 +262,199 @@ struct apmf_fan_idx { + u32 fan_ctl_idx; + } __packed; + ++struct amd_pmf_metrics_iod { ++ u32 counter_acc; ++ /* Set Voltage */ ++ u64 vddcr_set_voltage; ++ u64 vddcr_soc_set_voltage; ++ u64 vddcr_npu_set_voltage; ++ u64 vddcr_lp_set_voltage; ++ u64 vddcr_gfx_set_voltage; ++ u64 vdd_misc_set_voltage; ++ /* Telemetry Voltages */ ++ u64 vddcr_telemetry_voltage; ++ u64 vddcr_soc_telemetry_voltage; ++ u64 vddcr_npu_telemetry_voltage; ++ u64 vddcr_lp_telemetry_voltage; ++ u64 vddcr_gfx_telemetry_voltage; ++ u64 vdd_misc_telemetry_voltage; ++ /* Telemetry Powers */ ++ u64 vddcr_telemetry_power; ++ u64 vddcr_soc_telemetry_power; ++ u64 vddcr_npu_telemetry_power; ++ u64 vddcr_lp_telemetry_power; ++ u64 vddcr_gfx_telemetry_power; ++ u64 vdd_misc_telemetry_power; ++ /* Throttlers - Fast PPT */ ++ u32 fppt_fused_limit; ++ u32 fppt_max_irm_limit; ++ u32 fppt_max_pbo_limit; ++ u32 fppt_limit; ++ u64 fppt_value_acc; ++ u32 fppt_residency_acc; ++ /* Throttlers - Slow PPT */ ++ u32 sppt_fused_limit; ++ u32 sppt_max_irm_limit; ++ u32 sppt_max_pbo_limit; ++ u32 sppt_limit; ++ u64 sppt_value_acc; ++ u32 sppt_residency_acc; ++ /* Throttlers - STAPM */ ++ u32 spl_fused_limit; ++ u32 spl_max_irm_limit; ++ u32 spl_max_pbo_limit; ++ u32 spl_limit; ++ u64 spl_value_acc; ++ u32 spl_residency_acc; ++ /* Throttlers - TDC VDDCR */ ++ u32 tdc_vddcr_fused_limit; ++ u32 tdc_vddcr_max_irm_limit; ++ u32 tdc_vddcr_max_pbo_limit; ++ u32 tdc_vddcr_limit; ++ u64 tdc_vddcr_value_acc; ++ u32 tdc_vddcr_residency_acc; ++ /* Throttlers - TDC VDDCR_SOC */ ++ u32 tdc_vddcr_soc_fused_limit; ++ u32 tdc_vddcr_soc_max_irm_limit; ++ u32 tdc_vddcr_soc_max_pbo_limit; ++ u32 tdc_vddcr_soc_limit; ++ u64 tdc_vddcr_soc_value_acc; ++ u32 tdc_vddcr_soc_residency_acc; ++ /* Throttlers - TDC VDDCR_NPU */ ++ u32 tdc_vddcr_npu_fused_limit; ++ u32 tdc_vddcr_npu_max_irm_limit; ++ u32 tdc_vddcr_npu_max_pbo_limit; ++ u32 tdc_vddcr_npu_limit; ++ u64 tdc_vddcr_npu_value_acc; ++ u32 tdc_vddcr_npu_residency_acc; ++ /* Throttlers - TDC VDDCR_LP */ ++ u32 tdc_vddcr_lp_fused_limit; ++ u32 tdc_vddcr_lp_max_irm_limit; ++ u32 tdc_vddcr_lp_max_pbo_limit; ++ u32 tdc_vddcr_lp_limit; ++ u64 tdc_vddcr_lp_value_acc; ++ u32 tdc_vddcr_lp_residency_acc; ++ /* Throttlers - TDC VDDCR_GFX */ ++ u32 tdc_vddcr_gfx_fused_limit; ++ u32 tdc_vddcr_gfx_max_irm_limit; ++ u32 tdc_vddcr_gfx_max_pbo_limit; ++ u32 tdc_vddcr_gfx_limit; ++ u64 tdc_vddcr_gfx_value_acc; ++ u32 tdc_vddcr_gfx_residency_acc; ++ /* Throttlers - EDC VDDCR */ ++ u32 edc_vddcr_fused_limit; ++ u32 edc_vddcr_max_irm_limit; ++ u32 edc_vddcr_max_pbo_limit; ++ u32 edc_vddcr_limit; ++ /* Throttlers - Thermal */ ++ u32 thm_fused_limit; ++ u32 thm_limit; ++ u64 thm_value_acc; ++ u32 thm_residency_acc; ++ u32 prochot_residency_acc; ++ u64 gfx_temp_acc; ++ u64 soc_temp_acc; ++ u32 p3t_fused_limit; ++ u64 p3t_value_acc; ++ /* Power */ ++ u64 system_power_acc; ++ u64 apu_power_acc; ++ u64 dgpu_power_acc; ++ u64 npu_power_acc; ++ /* Frequencies */ ++ u64 fclk_freq_eff_acc; ++ u64 memclk_freq_eff_acc; ++ u64 lclk_freq_eff_acc; ++ u64 gfxclk_freq_eff_acc; ++ u64 socclk_freq_eff_acc; ++ u64 vclk_freq_eff_acc; ++ u64 vpeclk_freq_eff_acc; ++ u64 aieclk_freq_eff_acc; ++ u64 npuhclk_freq_eff_acc; ++ /* Bandwidth */ ++ u64 dram_read_bandwidth; ++ u64 dram_write_bandwidth; ++ /* Activity Monitors */ ++ u64 gfx_busy_acc; ++ u64 vcn_busy_acc; ++ u64 npu_busy_acc[3]; ++ /* STT Limits */ ++ u32 stt_min_limit; ++ u64 stt_apu_hotspot_temp_acc; ++ u64 stt_hs2_hotspot_temp_acc; ++ u32 stt_apu_temp_limit; ++ u64 stt_apu_skin_temp_acc; ++ /* Residencies */ ++ u64 cpuoff_residency_ccx0; ++ u64 cpuoff_residency_ccx1; ++ u64 cpuoff_residency_ccx2; ++ u64 cpuoff_residency_ccx3; ++ /* DF-pstates */ ++ u32 fclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u32 uclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u32 ddr_rate_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u8 dfpstate_source[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ /* System */ ++ u8 gfx_disabled; ++ u8 spare2[3]; ++ u32 gfxclk_fmax; ++ u8 cclk_core_fuse_enable[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; ++ u8 cclk_core_enabled[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; ++ u32 cclk_fmax[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; ++ /* Overclock Capable */ ++ u8 cpu_precise_and_direct_oc_capable; ++ u8 gfx_precise_and_direct_oc_capable; ++ u8 pbo_basic_oc_capable; ++ u8 pbo_advanced_oc_capable; ++ u8 pbo_nitro_oc_capable; ++ u8 memory_and_fabric_oc_capable; ++ u8 misc_oc_capable; ++ u8 extreme_cold_oc_capable; ++ u8 down_config_control_capable; ++ u8 spare0[3]; ++ /* Overclock Status */ ++ u32 fit_limit_scalar; ++ u8 ln2_enabled; ++ u8 cpu_precise_and_direct_oc_enabled; ++ u8 gfx_precise_and_direct_oc_enabled; ++ u8 spare1[2]; ++ /* Voltage Guardband in PSM count */ ++ s8 psm_guardband[5][5][3]; ++ s32 core_power_limit_offset; ++ u32 max_freq_offset[5]; ++ u64 npu_temp_acc; ++ u64 dfpstate_residency_acc[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u32 cclk_fboost; ++ /* PMF */ ++ u32 pmf_fast_apu_ppt_limit; ++ u64 pmf_fast_apu_ppt_value_acc; ++ u32 pmf_fast_apu_ppt_residency_acc; ++ u32 pmf_slow_apu_ppt_limit; ++ u64 pmf_slow_apu_ppt_value_acc; ++ u32 pmf_slow_apu_ppt_residency_acc; ++ u32 pmf_fast_spm_limit; ++ u64 pmf_fast_spm_value_acc; ++ u32 pmf_fast_spm_residency_acc; ++ u32 pmf_slow_spm_limit; ++ u64 pmf_slow_spm_value_acc; ++ u32 pmf_slow_spm_residency_acc; ++ u32 spare3[5]; ++} __packed __aligned(4); ++ ++struct amd_pmf_metrics_ccx { ++ u64 core_c0[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_cc6[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_freq[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_freqeff[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_temp[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_power[AMD_PMF_NUM_MAX_CORES]; ++} __packed __aligned(4); ++ ++struct amd_pmf_metrics_v3 { ++ struct amd_pmf_metrics_iod iod; ++ struct amd_pmf_metrics_ccx ccx[AMD_PMF_METRIC_CCX_MAX]; ++} __packed __aligned(4); ++ + struct smu_pmf_metrics_v2 { + u16 core_frequency[16]; /* MHz */ + u16 core_power[16]; /* mW */ +@@ -391,6 +604,19 @@ struct pmf_cbi_ring_buffer { + int tail; + }; + ++/* SoC-specific SMU mailbox register offsets */ ++struct amd_pmf_smu_regs { ++ u32 msg_reg; ++ u32 resp_reg; ++ u32 arg_reg[3]; ++}; ++ ++struct amd_pmf_arg_data { ++ u32 lo; ++ u32 hi; ++ u32 size; ++}; ++ + struct amd_pmf_dev { + void __iomem *regbase; + void __iomem *smu_virt_addr; +@@ -442,6 +668,14 @@ struct amd_pmf_dev { + struct pmf_cbi_ring_buffer cbi_buf; + struct mutex cbi_mutex; /* Protects ring buffer access */ + struct mutex metrics_mutex; ++ u32 bios_output[BIOS_OUTPUT_MAX]; ++ const struct amd_pmf_smu_regs *smu_regs; ++ void __iomem *metrics_table_virt; /* Mapped DRAM virtual address for metrics table */ ++ phys_addr_t metrics_table_phys; /* DRAM physical address for metrics table */ ++ struct amd_pmf_metrics_v3 mtable_v3; /* IOD and CCX */ ++ struct amd_pmf_arg_data dram_addr; ++ struct amd_pmf_metrics_v3 prev_metrics; /* Previous metrics for delta calculation */ ++ bool npu_metrics_have_prev; + }; + + struct apmf_sps_prop_granular_v2 { +@@ -680,14 +914,6 @@ enum system_state { + SYSTEM_STATE_MAX, + }; + +-enum ta_slider { +- TA_BEST_BATTERY, +- TA_BETTER_BATTERY, +- TA_BETTER_PERFORMANCE, +- TA_BEST_PERFORMANCE, +- TA_MAX, +-}; +- + struct amd_pmf_pb_bitmap { + const char *name; + u32 bit_mask; +@@ -719,20 +945,6 @@ static const struct amd_pmf_pb_bitmap custom_bios_inputs_v1[] __used = { + {"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(16)}, + }; + +-enum platform_type { +- PTYPE_UNKNOWN = 0, +- LID_CLOSE, +- CLAMSHELL, +- FLAT, +- TENT, +- STAND, +- TABLET, +- BOOK, +- PRESENTATION, +- PULL_FWD, +- PTYPE_INVALID = 0xf, +-}; +- + /* Command ids for TA communication */ + enum ta_pmf_command { + TA_PMF_COMMAND_POLICY_BUILDER_INITIALIZE, +@@ -871,6 +1083,10 @@ 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); ++void amd_pmf_set_device(struct device *p_device); ++ ++/* Metrics layer */ ++int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev); + + /* SPS Layer */ + int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf); +@@ -923,9 +1139,19 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq + 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); ++u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index); + + int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid); + void amd_pmf_tee_deinit(struct amd_pmf_dev *dev); + int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev); + ++/* Util Layer */ ++#if IS_ENABLED(CONFIG_AMD_PMF_UTIL_SUPPORT) ++int amd_pmf_cdev_register(struct amd_pmf_dev *dev); ++void amd_pmf_cdev_unregister(void); ++#else ++static inline int amd_pmf_cdev_register(struct amd_pmf_dev *dev) { return 0; } ++static inline void amd_pmf_cdev_unregister(void) {} ++#endif ++ + #endif /* PMF_H */ +diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c +--- a/drivers/platform/x86/amd/pmf/spc.c ++++ b/drivers/platform/x86/amd/pmf/spc.c +@@ -10,6 +10,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -17,61 +18,7 @@ + #include "pmf.h" + + #ifdef CONFIG_AMD_PMF_DEBUG +-static const char *platform_type_as_str(u16 platform_type) +-{ +- switch (platform_type) { +- case CLAMSHELL: +- return "CLAMSHELL"; +- case FLAT: +- return "FLAT"; +- case TENT: +- return "TENT"; +- case STAND: +- return "STAND"; +- case TABLET: +- return "TABLET"; +- case BOOK: +- return "BOOK"; +- case PRESENTATION: +- return "PRESENTATION"; +- case PULL_FWD: +- return "PULL_FWD"; +- default: +- return "UNKNOWN"; +- } +-} +- +-static const char *laptop_placement_as_str(u16 device_state) +-{ +- switch (device_state) { +- case ON_TABLE: +- return "ON_TABLE"; +- case ON_LAP_MOTION: +- return "ON_LAP_MOTION"; +- case IN_BAG: +- return "IN_BAG"; +- case OUT_OF_BAG: +- return "OUT_OF_BAG"; +- default: +- return "UNKNOWN"; +- } +-} +- +-static const char *ta_slider_as_str(unsigned int state) +-{ +- switch (state) { +- case TA_BEST_PERFORMANCE: +- return "PERFORMANCE"; +- case TA_BETTER_PERFORMANCE: +- return "BALANCED"; +- case TA_BEST_BATTERY: +- return "POWER_SAVER"; +- default: +- return "Unknown TA Slider State"; +- } +-} +- +-static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) ++u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) + { + switch (index) { + case 0 ... 1: +@@ -82,13 +29,15 @@ static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int + return 0; + } + } ++EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs); + + 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, "Slider State: %s\n", ++ amd_pmf_get_slider_position(in->ev_info.power_slider)); + dev_dbg(dev->dev, "Power Source: %s\n", amd_pmf_source_as_str(in->ev_info.power_source)); + dev_dbg(dev->dev, "Battery Percentage: %u\n", in->ev_info.bat_percentage); + dev_dbg(dev->dev, "Designed Battery Capacity: %u\n", in->ev_info.bat_design); +@@ -102,9 +51,10 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table * + dev_dbg(dev->dev, "LID State: %s\n", in->ev_info.lid_state ? "close" : "open"); + dev_dbg(dev->dev, "User Presence: %s\n", in->ev_info.user_present ? "Present" : "Away"); + dev_dbg(dev->dev, "Ambient Light: %d\n", in->ev_info.ambient_light); +- dev_dbg(dev->dev, "Platform type: %s\n", platform_type_as_str(in->ev_info.platform_type)); ++ dev_dbg(dev->dev, "Platform type: %s\n", ++ amd_pmf_get_platform_type(in->ev_info.platform_type)); + dev_dbg(dev->dev, "Laptop placement: %s\n", +- laptop_placement_as_str(in->ev_info.device_state)); ++ amd_pmf_get_laptop_placement(in->ev_info.device_state)); + 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)); +@@ -287,14 +237,14 @@ static int amd_pmf_get_slider_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_ + switch (dev->current_profile) { + case PLATFORM_PROFILE_PERFORMANCE: + case PLATFORM_PROFILE_BALANCED_PERFORMANCE: +- val = TA_BEST_PERFORMANCE; ++ val = AMD_PMF_TA_BEST_PERFORMANCE; + break; + case PLATFORM_PROFILE_BALANCED: +- val = TA_BETTER_PERFORMANCE; ++ val = AMD_PMF_TA_BETTER_PERFORMANCE; + break; + case PLATFORM_PROFILE_LOW_POWER: + case PLATFORM_PROFILE_QUIET: +- val = TA_BEST_BATTERY; ++ val = AMD_PMF_TA_BEST_BATTERY; + break; + default: + dev_err(dev->dev, "Unknown Platform Profile.\n"); +diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c +--- a/drivers/platform/x86/amd/pmf/tee-if.c ++++ b/drivers/platform/x86/amd/pmf/tee-if.c +@@ -8,7 +8,9 @@ + * Author: Shyam Sundar S K + */ + ++#include + #include ++#include + #include + #include + #include "pmf.h" +@@ -97,11 +99,18 @@ static int amd_pmf_get_bios_output_idx(u32 action_idx) + + static void amd_pmf_update_bios_output(struct amd_pmf_dev *pdev, struct ta_pmf_action *action) + { +- u32 bios_idx; ++ int bios_idx; ++ int ret; + + bios_idx = amd_pmf_get_bios_output_idx(action->action_index); ++ if (bios_idx < 0 || bios_idx >= ARRAY_SIZE(pdev->bios_output)) { ++ dev_warn(pdev->dev, "BIOS output index %d out of bounds\n", bios_idx); ++ return; ++ } + +- amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx); ++ ret = amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx); ++ if (!ret) ++ pdev->bios_output[bios_idx] = action->value; + } + + static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_result *out) +diff --git a/drivers/platform/x86/amd/pmf/util.c b/drivers/platform/x86/amd/pmf/util.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/amd/pmf/util.c +@@ -0,0 +1,153 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * AMD Platform Management Framework Util Layer ++ * ++ * Copyright (c) 2026, Advanced Micro Devices, Inc. ++ * All Rights Reserved. ++ * ++ * Authors: Shyam Sundar S K ++ * Sanket Goswami ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmf.h" ++ ++static struct amd_pmf_dev *pmf_dev_handle; ++static DEFINE_MUTEX(pmf_util_lock); ++ ++static int amd_pmf_populate_data(struct amd_pmf_dev *pdev, struct amd_pmf_info *info) ++{ ++ struct ta_pmf_shared_memory *ta_sm = NULL; ++ struct ta_pmf_enact_table *in = NULL; ++ int idx; ++ ++ if (!pdev || !info) ++ return -EINVAL; ++ ++ if (!pdev->shbuf) ++ return -EINVAL; ++ ++ ta_sm = pdev->shbuf; ++ in = &ta_sm->pmf_input.enact_table; ++ ++ /* Set size */ ++ info->size = sizeof(*info); ++ ++ /* PMF Feature support flags */ ++ if (is_apmf_func_supported(pdev, APMF_FUNC_AUTO_MODE)) ++ info->features_supported |= AMD_PMF_FEAT_AUTO_MODE; ++ if (is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) ++ info->features_supported |= AMD_PMF_FEAT_STATIC_POWER_SLIDER; ++ if (pdev->smart_pc_enabled) ++ info->features_supported |= AMD_PMF_FEAT_POLICY_BUILDER; ++ if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_AC)) ++ info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC; ++ if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_DC)) ++ info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC; ++ ++ /* Device States */ ++ info->platform_type = in->ev_info.platform_type; ++ info->laptop_placement = in->ev_info.device_state; ++ info->lid_state = in->ev_info.lid_state; ++ info->user_presence = in->ev_info.user_present; ++ info->slider_position = in->ev_info.power_slider; ++ ++ /* Thermal and Power Metrics */ ++ info->power_source = in->ev_info.power_source; ++ info->skin_temp = in->ev_info.skin_temperature; ++ info->gfx_busy = in->ev_info.gfx_busy; ++ info->ambient_light = in->ev_info.ambient_light; ++ info->avg_c0_residency = in->ev_info.avg_c0residency; ++ info->max_c0_residency = in->ev_info.max_c0residency; ++ info->socket_power = in->ev_info.socket_power; ++ ++ /* Custom BIOS input parameters */ ++ for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++) ++ info->bios_input[idx] = amd_pmf_get_ta_custom_bios_inputs(in, idx); ++ ++ /* BIOS output parameters */ ++ for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++) ++ info->bios_output[idx] = pdev->bios_output[idx]; ++ ++ return 0; ++} ++ ++static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) ++{ ++ struct amd_pmf_dev *pdev = filp->private_data; ++ void __user *argp = (void __user *)arg; ++ struct amd_pmf_info info = {}; ++ size_t copy_size; ++ __u64 user_size; ++ int ret; ++ ++ if (cmd != IOCTL_AMD_PMF_POPULATE_DATA) ++ return -ENOTTY; ++ ++ /* First read just the size field from userspace */ ++ if (copy_from_user(&user_size, argp, sizeof(user_size))) ++ return -EFAULT; ++ ++ guard(mutex)(&pmf_util_lock); ++ ret = amd_pmf_populate_data(pdev, &info); ++ if (ret) ++ return ret; ++ ++ copy_size = min_t(size_t, user_size, sizeof(info)); ++ ++ /* Set actual size being copied */ ++ info.size = copy_size; ++ ++ if (copy_to_user(argp, &info, copy_size)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static int amd_pmf_open(struct inode *inode, struct file *filp) ++{ ++ guard(mutex)(&pmf_util_lock); ++ if (!pmf_dev_handle) ++ return -ENODEV; ++ ++ filp->private_data = pmf_dev_handle; ++ return 0; ++} ++ ++static const struct file_operations pmf_if_ops = { ++ .owner = THIS_MODULE, ++ .open = amd_pmf_open, ++ .unlocked_ioctl = amd_pmf_set_ioctl, ++}; ++ ++static struct miscdevice amd_pmf_util_if = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = "amdpmf_interface", ++ .fops = &pmf_if_ops, ++}; ++ ++int amd_pmf_cdev_register(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ guard(mutex)(&pmf_util_lock); ++ pmf_dev_handle = dev; ++ ret = misc_register(&amd_pmf_util_if); ++ if (ret) ++ pmf_dev_handle = NULL; ++ ++ return ret; ++} ++ ++void amd_pmf_cdev_unregister(void) ++{ ++ guard(mutex)(&pmf_util_lock); ++ if (pmf_dev_handle) ++ misc_deregister(&amd_pmf_util_if); ++ pmf_dev_handle = NULL; ++} +diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c +--- a/drivers/platform/x86/asus-armoury.c ++++ b/drivers/platform/x86/asus-armoury.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -93,6 +94,8 @@ struct asus_armoury_priv { + + u32 mini_led_dev_id; + u32 gpu_mux_dev_id; ++ ++ bool requires_fan_curve; + }; + + static struct asus_armoury_priv asus_armoury = { +@@ -216,6 +219,22 @@ static int armoury_set_devstate(struct kobj_attribute *attr, + u32 result; + int err; + ++ /* On some models, PPT changes require an active fan curve */ ++ if (asus_armoury.requires_fan_curve) { ++ switch (dev_id) { ++ case ASUS_WMI_DEVID_PPT_PL1_SPL: ++ case ASUS_WMI_DEVID_PPT_PL2_SPPT: ++ case ASUS_WMI_DEVID_PPT_PL3_FPPT: ++ case ASUS_WMI_DEVID_PPT_APU_SPPT: ++ case ASUS_WMI_DEVID_PPT_PLAT_SPPT: ++ if (!asus_wmi_custom_fan_curve_is_enabled()) { ++ pr_warn_once("PPT change requires an active fan curve on this model. Enable a custom fan curve first.\n"); ++ return -EBUSY; ++ } ++ break; ++ } ++ } ++ + /* + * Prevent developers from bricking devices or issuing dangerous + * commands that can be difficult or impossible to recover from. +@@ -989,10 +1008,11 @@ static int asus_fw_attr_add(void) + /* Init / exit ****************************************************************/ + + /* Set up the min/max and defaults for ROG tunables */ +-static void init_rog_tunables(void) ++static int init_rog_tunables(void) + { + const struct power_limits *ac_limits, *dc_limits; +- struct rog_tunables *ac_rog_tunables = NULL, *dc_rog_tunables = NULL; ++ struct rog_tunables *ac_rog_tunables __free(kfree) = NULL; ++ struct rog_tunables *dc_rog_tunables __free(kfree) = NULL; + const struct power_data *power_data; + const struct dmi_system_id *dmi_id; + +@@ -1000,24 +1020,25 @@ static void init_rog_tunables(void) + dmi_id = dmi_first_match(power_limits); + if (!dmi_id) { + pr_warn("No matching power limits found for this system\n"); +- return; ++ return 0; + } + + /* Get the power data for this system */ + power_data = dmi_id->driver_data; + if (!power_data) { + pr_info("No power data available for this system\n"); +- return; ++ return 0; + } + ++ asus_armoury.requires_fan_curve = power_data->requires_fan_curve; ++ + /* Initialize AC power tunables */ + ac_limits = power_data->ac_data; + if (ac_limits) { +- ac_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]); ++ ac_rog_tunables = kzalloc_obj(*ac_rog_tunables); + if (!ac_rog_tunables) +- goto err_nomem; ++ return -ENOMEM; + +- asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = ac_rog_tunables; + ac_rog_tunables->power_limits = ac_limits; + + /* Set initial AC values */ +@@ -1060,13 +1081,10 @@ static void init_rog_tunables(void) + /* Initialize DC power tunables */ + dc_limits = power_data->dc_data; + if (dc_limits) { +- dc_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]); +- if (!dc_rog_tunables) { +- kfree(ac_rog_tunables); +- goto err_nomem; +- } ++ dc_rog_tunables = kzalloc_obj(*dc_rog_tunables); ++ if (!dc_rog_tunables) ++ return -ENOMEM; + +- asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = dc_rog_tunables; + dc_rog_tunables->power_limits = dc_limits; + + /* Set initial DC values */ +@@ -1106,15 +1124,16 @@ static void init_rog_tunables(void) + pr_debug("No DC PPT limits defined\n"); + } + +- return; ++ asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = no_free_ptr(ac_rog_tunables); ++ asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = no_free_ptr(dc_rog_tunables); + +-err_nomem: +- pr_err("Failed to allocate memory for tunables\n"); ++ return 0; + } + + static int __init asus_fw_init(void) + { + char *wmi_uid; ++ int err; + + wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID); + if (!wmi_uid) +@@ -1127,10 +1146,21 @@ static int __init asus_fw_init(void) + if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI)) + return -ENODEV; + +- init_rog_tunables(); ++ err = init_rog_tunables(); ++ if (err) ++ return err; + + /* Must always be last step to ensure data is available */ +- return asus_fw_attr_add(); ++ err = asus_fw_attr_add(); ++ if (err) ++ goto err_free_tunables; ++ ++ return 0; ++ ++err_free_tunables: ++ kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]); ++ kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]); ++ return err; + } + + static void __exit asus_fw_exit(void) +diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h +--- a/drivers/platform/x86/asus-armoury.h ++++ b/drivers/platform/x86/asus-armoury.h +@@ -369,6 +369,38 @@ static const struct dmi_system_id power_limits[] = { + }, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA401KM"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .nv_dynamic_boost_max = 15, ++ .nv_dynamic_boost_min = 5, ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .nv_tgp_max = 95, ++ .nv_tgp_min = 55, ++ .ppt_pl1_spl_max = 80, ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl2_sppt_max = 80, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl3_fppt_max = 80, ++ .ppt_pl3_fppt_min = 35, ++ }, ++ .dc_data = &(struct power_limits) { ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .ppt_pl1_spl_max = 35, ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl2_sppt_max = 44, ++ .ppt_pl2_sppt_min = 31, ++ .ppt_pl3_fppt_max = 65, ++ .ppt_pl3_fppt_min = 45, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA401UM"), +@@ -754,6 +786,40 @@ static const struct dmi_system_id power_limits[] = { + }, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA608WV"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .nv_dynamic_boost_max = 25, ++ .nv_dynamic_boost_min = 5, ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .nv_tgp_max = 115, ++ .nv_tgp_min = 55, ++ .ppt_pl1_spl_max = 90, ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl2_sppt_max = 90, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl3_fppt_max = 90, ++ .ppt_pl3_fppt_min = 35, ++ }, ++ .dc_data = &(struct power_limits) { ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .ppt_pl1_spl_def = 45, ++ .ppt_pl1_spl_max = 65, ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl2_sppt_def = 54, ++ .ppt_pl2_sppt_max = 65, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl3_fppt_max = 65, ++ .ppt_pl3_fppt_min = 35, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA617NS"), +@@ -909,6 +975,32 @@ static const struct dmi_system_id power_limits[] = { + .requires_fan_curve = true, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FX517ZR"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_max = 85, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 135, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 45, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_max = 60, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FX607VU"), +@@ -2079,6 +2171,35 @@ static const struct dmi_system_id power_limits[] = { + .requires_fan_curve = true, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "G635LX"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_def = 140, ++ .ppt_pl1_spl_max = 175, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 175, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ .nv_tgp_min = 80, ++ .nv_tgp_max = 150, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 55, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 70, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G713PV"), +@@ -2322,6 +2443,35 @@ static const struct dmi_system_id power_limits[] = { + }, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "HN7306EA"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 35, ++ .ppt_pl1_spl_def = 60, ++ .ppt_pl1_spl_max = 85, ++ .ppt_pl2_sppt_min = 40, ++ .ppt_pl2_sppt_def = 70, ++ .ppt_pl2_sppt_max = 95, ++ .ppt_pl3_fppt_min = 50, ++ .ppt_pl3_fppt_def = 85, ++ .ppt_pl3_fppt_max = 115, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 35, ++ .ppt_pl1_spl_def = 45, ++ .ppt_pl1_spl_max = 60, ++ .ppt_pl2_sppt_min = 40, ++ .ppt_pl2_sppt_def = 55, ++ .ppt_pl2_sppt_max = 70, ++ .ppt_pl3_fppt_min = 50, ++ .ppt_pl3_fppt_def = 65, ++ .ppt_pl3_fppt_max = 85, ++ }, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "RC71"), +diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c +--- a/drivers/platform/x86/asus-laptop.c ++++ b/drivers/platform/x86/asus-laptop.c +@@ -42,8 +42,6 @@ + #define ASUS_LAPTOP_VERSION "0.42" + + #define ASUS_LAPTOP_NAME "Asus Laptop Support" +-#define ASUS_LAPTOP_CLASS "hotkey" +-#define ASUS_LAPTOP_DEVICE_NAME "Hotkey" + #define ASUS_LAPTOP_FILE KBUILD_MODNAME + #define ASUS_LAPTOP_PREFIX "\\_SB.ATKD." + +@@ -1524,9 +1522,8 @@ static void asus_acpi_notify(acpi_handle handle, u32 event, void *data) + + /* TODO Find a better way to handle events count. */ + count = asus->event_count[event % 128]++; +- acpi_bus_generate_netlink_event(asus->device->pnp.device_class, +- dev_name(&asus->device->dev), event, +- count); ++ acpi_bus_generate_netlink_event("hotkey", dev_name(&asus->device->dev), ++ event, count); + + if (event >= ATKD_BRNUP_MIN && event <= ATKD_BRNUP_MAX) + event = ATKD_BRNUP; +@@ -1840,8 +1837,6 @@ static int asus_acpi_probe(struct platform_device *pdev) + if (!asus) + return -ENOMEM; + asus->handle = device->handle; +- strscpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME); +- strscpy(acpi_device_class(device), ASUS_LAPTOP_CLASS); + asus->device = device; + + asus_dmi_check(); +diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c +--- a/drivers/platform/x86/asus-nb-wmi.c ++++ b/drivers/platform/x86/asus-nb-wmi.c +@@ -632,6 +632,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = { + { KE_KEY, 0x86, { KEY_PROG1 } }, /* MyASUS Key */ + { KE_KEY, 0x88, { KEY_RFKILL } }, /* Radio Toggle Key */ + { KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */ ++ { KE_KEY, 0x8B, { KEY_PROG3 } }, /* ProArt key (PX13/HN7306) */ + { KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */ + { KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */ + { KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */ +diff --git a/drivers/platform/x86/asus-wireless.c b/drivers/platform/x86/asus-wireless.c +--- a/drivers/platform/x86/asus-wireless.c ++++ b/drivers/platform/x86/asus-wireless.c +@@ -132,6 +132,10 @@ static int asus_wireless_probe(struct platform_device *pdev) + const struct acpi_device_id *id; + int err; + ++ id = acpi_match_acpi_device(device_ids, adev); ++ if (!id) ++ return -ENODEV; ++ + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; +@@ -139,6 +143,7 @@ static int asus_wireless_probe(struct platform_device *pdev) + platform_set_drvdata(pdev, data); + + data->adev = adev; ++ data->hswc_params = (const struct hswc_params *)id->driver_data; + + data->idev = devm_input_allocate_device(&pdev->dev); + if (!data->idev) +@@ -153,12 +158,6 @@ static int asus_wireless_probe(struct platform_device *pdev) + if (err) + return err; + +- id = acpi_match_acpi_device(device_ids, adev); +- if (!id) +- return 0; +- +- data->hswc_params = (const struct hswc_params *)id->driver_data; +- + data->wq = create_singlethread_workqueue("asus_wireless_workqueue"); + if (!data->wq) + return -ENOMEM; +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -4073,6 +4073,30 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus) + return 0; + } + ++/* ++ * Returns true if at least one custom fan curve is active ++ * ++ * Used by asus-armoury to check if PPT writes will be accepted by the BIOS ++ * on models that require an active fan curve for TDP changes. ++ */ ++bool asus_wmi_custom_fan_curve_is_enabled(void) ++{ ++ struct fan_curve_data *curves; ++ struct asus_wmi *asus; ++ ++ guard(spinlock_irqsave)(&asus_ref.lock); ++ asus = asus_ref.asus; ++ if (!asus) ++ return false; ++ ++ curves = asus->custom_fan_curves; ++ ++ return (asus->cpu_fan_curve_available && curves[FAN_CURVE_DEV_CPU].enabled) || ++ (asus->gpu_fan_curve_available && curves[FAN_CURVE_DEV_GPU].enabled) || ++ (asus->mid_fan_curve_available && curves[FAN_CURVE_DEV_MID].enabled); ++} ++EXPORT_SYMBOL_NS_GPL(asus_wmi_custom_fan_curve_is_enabled, "ASUS_WMI"); ++ + /* Throttle thermal policy ****************************************************/ + static int throttle_thermal_policy_write(struct asus_wmi *asus) + { +@@ -5244,20 +5268,20 @@ static int asus_wmi_add(struct platform_device *pdev) + return 0; + + fail_wmi_handler: ++ asus_screenpad_exit(asus); ++fail_screenpad: + asus_wmi_backlight_exit(asus); + fail_backlight: + asus_wmi_rfkill_exit(asus); +-fail_screenpad: +- asus_screenpad_exit(asus); + fail_rfkill: + asus_wmi_led_exit(asus); + fail_leds: ++fail_custom_fan_curve: + fail_hwmon: + asus_wmi_input_exit(asus); + fail_input: + asus_wmi_sysfs_exit(asus->platform_device); + fail_sysfs: +-fail_custom_fan_curve: + fail_platform_profile_setup: + fail_fan_boost_mode: + fail_platform: +diff --git a/drivers/platform/x86/dell/dell-smbios-base.c b/drivers/platform/x86/dell/dell-smbios-base.c +--- a/drivers/platform/x86/dell/dell-smbios-base.c ++++ b/drivers/platform/x86/dell/dell-smbios-base.c +@@ -40,7 +40,7 @@ struct smbios_device { + struct list_head list; + struct device *device; + int priority; +- int (*call_fn)(struct calling_interface_buffer *arg); ++ smbios_callback_fn_t call_fn; + }; + + struct smbios_call { +@@ -146,7 +146,7 @@ int dell_smbios_error(int value) + } + EXPORT_SYMBOL_GPL(dell_smbios_error); + +-int dell_smbios_register_device(struct device *d, int priority, void *call_fn) ++int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn) + { + struct smbios_device *priv; + +@@ -312,7 +312,7 @@ int dell_smbios_call(struct calling_interface_buffer *buffer) + goto out_smbios_call; + } + +- ret = selected->call_fn(buffer); ++ ret = selected->call_fn(selected->device, buffer); + + out_smbios_call: + mutex_unlock(&smbios_mutex); +diff --git a/drivers/platform/x86/dell/dell-smbios-smm.c b/drivers/platform/x86/dell/dell-smbios-smm.c +--- a/drivers/platform/x86/dell/dell-smbios-smm.c ++++ b/drivers/platform/x86/dell/dell-smbios-smm.c +@@ -49,7 +49,7 @@ static void find_cmd_address(const struct dmi_header *dm, void *dummy) + } + } + +-static int dell_smbios_smm_call(struct calling_interface_buffer *input) ++static int dell_smbios_smm_call(struct device *dev, struct calling_interface_buffer *input) + { + struct smi_cmd command; + size_t size; +@@ -70,7 +70,7 @@ static int dell_smbios_smm_call(struct calling_interface_buffer *input) + } + + /* When enabled this indicates that SMM won't work */ +-static bool test_wsmt_enabled(void) ++static bool test_wsmt_enabled(struct device *dev) + { + struct calling_interface_token *wsmt; + +@@ -88,7 +88,7 @@ static bool test_wsmt_enabled(void) + memset(buffer, 0, sizeof(struct calling_interface_buffer)); + buffer->input[0] = wsmt->location; + buffer->output[0] = 99; +- dell_smbios_smm_call(buffer); ++ dell_smbios_smm_call(dev, buffer); + if (buffer->output[0] == 99) + return true; + +@@ -109,7 +109,7 @@ int init_dell_smbios_smm(void) + + dmi_walk(find_cmd_address, NULL); + +- if (test_wsmt_enabled()) { ++ if (test_wsmt_enabled(&platform_device->dev)) { + pr_debug("Disabling due to WSMT enabled\n"); + ret = -ENODEV; + goto fail_wsmt; +diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c +--- a/drivers/platform/x86/dell/dell-smbios-wmi.c ++++ b/drivers/platform/x86/dell/dell-smbios-wmi.c +@@ -6,21 +6,20 @@ + */ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + ++#include + #include + #include + #include +-#include + #include + #include + #include ++#include + #include + #include + #include + #include "dell-smbios.h" + #include "dell-wmi-descriptor.h" + +-static DEFINE_MUTEX(call_mutex); +-static DEFINE_MUTEX(list_mutex); + static int wmi_supported; + + struct misc_bios_flags_structure { +@@ -32,21 +31,16 @@ struct misc_bios_flags_structure { + #define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" + + struct wmi_smbios_priv { ++ struct mutex call_lock; /* Protects the content of the SMBIOS buffer */ + struct dell_wmi_smbios_buffer *buf; +- struct list_head list; + struct wmi_device *wdev; + struct device *child; + u64 req_buf_size; + struct miscdevice char_dev; + }; +-static LIST_HEAD(wmi_list); + +-static inline struct wmi_smbios_priv *get_first_smbios_priv(void) +-{ +- return list_first_entry_or_null(&wmi_list, +- struct wmi_smbios_priv, +- list); +-} ++static DECLARE_RWSEM(chardev_lock); /* Protects chardev_priv */ ++static struct wmi_smbios_priv *chardev_priv; + + static int run_smbios_call(struct wmi_device *wdev) + { +@@ -80,50 +74,36 @@ static int run_smbios_call(struct wmi_device *wdev) + return 0; + } + +-static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) ++static int dell_smbios_wmi_call(struct device *dev, struct calling_interface_buffer *buffer) + { +- struct wmi_smbios_priv *priv; ++ struct wmi_smbios_priv *priv = dev_get_drvdata(dev); + size_t difference; + size_t size; + int ret; + +- mutex_lock(&call_mutex); +- priv = get_first_smbios_priv(); +- if (!priv) { +- ret = -ENODEV; +- goto out_wmi_call; +- } +- + size = sizeof(struct calling_interface_buffer); + difference = priv->req_buf_size - sizeof(u64) - size; + ++ guard(mutex)(&priv->call_lock); ++ + memset(&priv->buf->ext, 0, difference); + memcpy(&priv->buf->std, buffer, size); + ret = run_smbios_call(priv->wdev); + memcpy(buffer, &priv->buf->std, size); +-out_wmi_call: +- mutex_unlock(&call_mutex); + + return ret; + } + +-static int dell_smbios_wmi_open(struct inode *inode, struct file *filp) +-{ +- struct wmi_smbios_priv *priv; +- +- priv = container_of(filp->private_data, struct wmi_smbios_priv, char_dev); +- filp->private_data = priv; +- +- return nonseekable_open(inode, filp); +-} +- + static ssize_t dell_smbios_wmi_read(struct file *filp, char __user *buffer, size_t length, + loff_t *offset) + { +- struct wmi_smbios_priv *priv = filp->private_data; ++ guard(rwsem_read)(&chardev_lock); + +- return simple_read_from_buffer(buffer, length, offset, &priv->req_buf_size, +- sizeof(priv->req_buf_size)); ++ if (!chardev_priv) ++ return -ENODEV; ++ ++ return simple_read_from_buffer(buffer, length, offset, &chardev_priv->req_buf_size, ++ sizeof(chardev_priv->req_buf_size)); + } + + static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv, +@@ -167,22 +147,22 @@ static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv, + static long dell_smbios_wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) + { + struct dell_wmi_smbios_buffer __user *input = (struct dell_wmi_smbios_buffer __user *)arg; +- struct wmi_smbios_priv *priv = filp->private_data; +- long ret; + + if (cmd != DELL_WMI_SMBIOS_CMD) + return -ENOIOCTLCMD; + +- mutex_lock(&call_mutex); +- ret = dell_smbios_wmi_do_ioctl(priv, input); +- mutex_unlock(&call_mutex); ++ guard(rwsem_read)(&chardev_lock); + +- return ret; ++ if (!chardev_priv) ++ return -ENODEV; ++ ++ guard(mutex)(&chardev_priv->call_lock); ++ ++ return dell_smbios_wmi_do_ioctl(chardev_priv, input); + } + + static const struct file_operations dell_smbios_wmi_fops = { + .owner = THIS_MODULE, +- .open = dell_smbios_wmi_open, + .read = dell_smbios_wmi_read, + .unlocked_ioctl = dell_smbios_wmi_ioctl, + .compat_ioctl = compat_ptr_ioctl, +@@ -195,10 +175,29 @@ static void dell_smbios_wmi_unregister_chardev(void *data) + misc_deregister(char_dev); + } + ++static void dell_smbios_wmi_clear_chardev(void *data) ++{ ++ guard(rwsem_write)(&chardev_lock); ++ ++ chardev_priv = NULL; ++} ++ + static int dell_smbios_wmi_register_chardev(struct wmi_smbios_priv *priv) + { + int ret; + ++ scoped_guard(rwsem_write, &chardev_lock) { ++ /* We can only have a single chardev at a time */ ++ if (chardev_priv) ++ return -EBUSY; ++ ++ chardev_priv = priv; ++ } ++ ++ ret = devm_add_action_or_reset(&priv->wdev->dev, dell_smbios_wmi_clear_chardev, NULL); ++ if (ret < 0) ++ return ret; ++ + priv->char_dev.minor = MISC_DYNAMIC_MINOR; + priv->char_dev.name = "wmi/dell-smbios"; + priv->char_dev.fops = &dell_smbios_wmi_fops; +@@ -254,31 +253,20 @@ static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context) + if (!priv->buf) + return -ENOMEM; + ++ ret = devm_mutex_init(&wdev->dev, &priv->call_lock); ++ if (ret) ++ return ret; ++ + ret = dell_smbios_wmi_register_chardev(priv); + if (ret) + return ret; + +- ret = dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call); +- if (ret) +- return ret; +- +- mutex_lock(&list_mutex); +- list_add_tail(&priv->list, &wmi_list); +- mutex_unlock(&list_mutex); +- +- return 0; ++ return dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call); + } + + static void dell_smbios_wmi_remove(struct wmi_device *wdev) + { +- struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev); +- +- mutex_lock(&call_mutex); +- mutex_lock(&list_mutex); +- list_del(&priv->list); +- mutex_unlock(&list_mutex); + dell_smbios_unregister_device(&wdev->dev); +- mutex_unlock(&call_mutex); + } + + static const struct wmi_device_id dell_smbios_wmi_id_table[] = { +@@ -316,6 +304,7 @@ static struct wmi_driver dell_smbios_wmi_driver = { + .probe = dell_smbios_wmi_probe, + .remove = dell_smbios_wmi_remove, + .id_table = dell_smbios_wmi_id_table, ++ .no_singleton = true, + }; + + int init_dell_smbios_wmi(void) +diff --git a/drivers/platform/x86/dell/dell-smbios.h b/drivers/platform/x86/dell/dell-smbios.h +--- a/drivers/platform/x86/dell/dell-smbios.h ++++ b/drivers/platform/x86/dell/dell-smbios.h +@@ -47,6 +47,8 @@ + + struct notifier_block; + ++typedef int (*smbios_callback_fn_t)(struct device *dev, struct calling_interface_buffer *buffer); ++ + struct calling_interface_token { + u16 tokenID; + u16 location; +@@ -64,7 +66,7 @@ struct calling_interface_structure { + struct calling_interface_token tokens[]; + } __packed; + +-int dell_smbios_register_device(struct device *d, int priority, void *call_fn); ++int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn); + void dell_smbios_unregister_device(struct device *d); + + int dell_smbios_error(int value); +diff --git a/drivers/platform/x86/dell/dell-wmi-base.c b/drivers/platform/x86/dell/dell-wmi-base.c +--- a/drivers/platform/x86/dell/dell-wmi-base.c ++++ b/drivers/platform/x86/dell/dell-wmi-base.c +@@ -456,7 +456,7 @@ static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, __l + key++; + used = 1; + } else if (type == 0x0012 && code == 0x000d && remaining > 0) { +- value = (le16_to_cpu(buffer[2]) == 2); ++ value = (le16_to_cpu(buffer[0]) == 2); + used = 1; + } + +@@ -843,9 +843,22 @@ static int __init dell_wmi_init(void) + + err = dell_privacy_register_driver(); + if (err) +- return err; ++ goto out_smbios; + +- return wmi_driver_register(&dell_wmi_driver); ++ err = wmi_driver_register(&dell_wmi_driver); ++ if (err) ++ goto out_privacy; ++ ++ return 0; ++ ++out_privacy: ++ dell_privacy_unregister_driver(); ++ ++out_smbios: ++ if (wmi_requires_smbios_request) ++ dell_wmi_events_set_enabled(false); ++ ++ return err; + } + late_initcall(dell_wmi_init); + +diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c +--- a/drivers/platform/x86/dell/dell-wmi-ddv.c ++++ b/drivers/platform/x86/dell/dell-wmi-ddv.c +@@ -196,40 +196,30 @@ static int dell_wmi_ddv_query_integer(struct wmi_device *wdev, enum dell_ddv_met + static int dell_wmi_ddv_query_buffer(struct wmi_device *wdev, enum dell_ddv_method method, + u32 arg, struct dell_wmi_buffer **result) + { +- struct dell_wmi_buffer *buffer; + struct wmi_buffer output; + size_t buffer_size; + int ret; + +- ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(*buffer)); ++ ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(struct dell_wmi_buffer)); + if (ret < 0) + return ret; + +- buffer = output.data; +- if (!le32_to_cpu(buffer->raw_size)) { +- ret = -ENODATA; ++ struct dell_wmi_buffer *buffer __free(kfree) = output.data; + +- goto err_free; +- } ++ if (!le32_to_cpu(buffer->raw_size)) ++ return -ENODATA; + + buffer_size = struct_size(buffer, raw_data, le32_to_cpu(buffer->raw_size)); + if (buffer_size > output.length) { + dev_warn(&wdev->dev, + FW_WARN "Dell WMI buffer size (%zu) exceeds WMI buffer size (%zu)\n", + buffer_size, output.length); +- ret = -EMSGSIZE; +- +- goto err_free; ++ return -EMSGSIZE; + } + +- *result = buffer; ++ *result = no_free_ptr(buffer); + + return 0; +- +-err_free: +- kfree(output.data); +- +- return ret; + } + + static ssize_t dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_method method, +diff --git a/drivers/platform/x86/dell/dell-wmi-privacy.c b/drivers/platform/x86/dell/dell-wmi-privacy.c +--- a/drivers/platform/x86/dell/dell-wmi-privacy.c ++++ b/drivers/platform/x86/dell/dell-wmi-privacy.c +@@ -92,11 +92,11 @@ bool dell_privacy_has_mic_mute(void) + { + struct privacy_wmi_data *priv; + +- mutex_lock(&list_mutex); ++ guard(mutex)(&list_mutex); ++ + priv = list_first_entry_or_null(&wmi_list, + struct privacy_wmi_data, + list); +- mutex_unlock(&list_mutex); + + return priv && (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)); + } +diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h +--- a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h ++++ b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h +@@ -107,7 +107,7 @@ enum { + static int get_##type##_instance_id(struct kobject *kobj) \ + { \ + int i; \ +- for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \ ++ for (i = 0; i < wmi_priv.type##_instances_count; i++) { \ + if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\ + return i; \ + } \ +diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c +--- a/drivers/platform/x86/eeepc-laptop.c ++++ b/drivers/platform/x86/eeepc-laptop.c +@@ -34,8 +34,6 @@ + #define EEEPC_LAPTOP_NAME "Eee PC Hotkey Driver" + #define EEEPC_LAPTOP_FILE "eeepc" + +-#define EEEPC_ACPI_CLASS "hotkey" +-#define EEEPC_ACPI_DEVICE_NAME "Hotkey" + #define EEEPC_ACPI_HID "ASUS010" + + MODULE_AUTHOR("Corentin Chary, Eric Cooper"); +@@ -1214,8 +1212,7 @@ static void eeepc_acpi_notify(acpi_handle handle, u32 event, void *data) + if (event > ACPI_MAX_SYS_NOTIFY) + return; + count = eeepc->event_count[event % 128]++; +- acpi_bus_generate_netlink_event(device->pnp.device_class, +- dev_name(&device->dev), event, ++ acpi_bus_generate_netlink_event("hotkey", dev_name(&device->dev), event, + count); + + /* Brightness events are special */ +@@ -1376,8 +1373,6 @@ static int eeepc_acpi_probe(struct platform_device *pdev) + if (!eeepc) + return -ENOMEM; + eeepc->handle = device->handle; +- strscpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME); +- strscpy(acpi_device_class(device), EEEPC_ACPI_CLASS); + eeepc->device = device; + + platform_set_drvdata(pdev, eeepc); +diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c +--- a/drivers/platform/x86/fujitsu-laptop.c ++++ b/drivers/platform/x86/fujitsu-laptop.c +@@ -56,7 +56,6 @@ + + #define FUJITSU_LCD_N_LEVELS 8 + +-#define ACPI_FUJITSU_CLASS "fujitsu" + #define ACPI_FUJITSU_BL_HID "FUJ02B1" + #define ACPI_FUJITSU_BL_DRIVER_NAME "Fujitsu laptop FUJ02B1 ACPI brightness driver" + #define ACPI_FUJITSU_BL_DEVICE_NAME "Fujitsu FUJ02B1" +@@ -466,7 +465,7 @@ static int acpi_fujitsu_bl_input_setup(struct device *dev) + snprintf(priv->phys, sizeof(priv->phys), "%s/video/input0", + acpi_device_hid(device)); + +- priv->input->name = acpi_device_name(device); ++ priv->input->name = ACPI_FUJITSU_BL_DEVICE_NAME; + priv->input->phys = priv->phys; + priv->input->id.bustype = BUS_HOST; + priv->input->id.product = 0x06; +@@ -546,13 +545,11 @@ static int acpi_fujitsu_bl_probe(struct platform_device *pdev) + return -ENOMEM; + + fujitsu_bl = priv; +- strscpy(acpi_device_name(device), ACPI_FUJITSU_BL_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); + + platform_set_drvdata(pdev, priv); + +- pr_info("ACPI: %s [%s]\n", +- acpi_device_name(device), acpi_device_bid(device)); ++ pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_BL_DEVICE_NAME, ++ acpi_device_bid(device)); + + if (get_max_brightness(&pdev->dev) <= 0) + priv->max_brightness = FUJITSU_LCD_N_LEVELS; +@@ -681,7 +678,7 @@ static int acpi_fujitsu_laptop_input_setup(struct device *dev) + snprintf(priv->phys, sizeof(priv->phys), "%s/input0", + acpi_device_hid(device)); + +- priv->input->name = acpi_device_name(device); ++ priv->input->name = ACPI_FUJITSU_LAPTOP_DEVICE_NAME; + priv->input->phys = priv->phys; + priv->input->id.bustype = BUS_HOST; + +@@ -1012,9 +1009,6 @@ static int acpi_fujitsu_laptop_probe(struct platform_device *pdev) + WARN_ONCE(fext, "More than one FUJ02E3 ACPI device was found. Driver may not work as intended."); + fext = &pdev->dev; + +- strscpy(acpi_device_name(device), ACPI_FUJITSU_LAPTOP_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); +- + platform_set_drvdata(pdev, priv); + + /* kfifo */ +@@ -1024,8 +1018,8 @@ static int acpi_fujitsu_laptop_probe(struct platform_device *pdev) + if (ret) + return ret; + +- pr_info("ACPI: %s [%s]\n", +- acpi_device_name(device), acpi_device_bid(device)); ++ pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_LAPTOP_DEVICE_NAME, ++ acpi_device_bid(device)); + + while (call_fext_func(fext, FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0 && + i++ < MAX_HOTKEY_RINGBUFFER_SIZE) +diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujitsu-tablet.c +--- a/drivers/platform/x86/fujitsu-tablet.c ++++ b/drivers/platform/x86/fujitsu-tablet.c +@@ -22,8 +22,6 @@ + + #define MODULENAME "fujitsu-tablet" + +-#define ACPI_FUJITSU_CLASS "fujitsu" +- + #define INVERT_TABLET_MODE_BIT 0x01 + #define INVERT_DOCK_STATE_BIT 0x02 + #define FORCE_TABLET_MODE_IF_UNDOCK 0x04 +@@ -160,6 +158,7 @@ static struct { + struct fujitsu_config config; + unsigned long prev_keymask; + ++ char name[17]; + char phys[21]; + + int irq; +@@ -458,14 +457,10 @@ static int acpi_fujitsu_probe(struct platform_device *pdev) + if (ACPI_FAILURE(status) || !fujitsu.irq || !fujitsu.io_base) + return -ENODEV; + +- sprintf(acpi_device_name(adev), "Fujitsu %s", acpi_device_hid(adev)); +- sprintf(acpi_device_class(adev), "%s", ACPI_FUJITSU_CLASS); ++ scnprintf(fujitsu.name, sizeof(fujitsu.name), "Fujitsu %s", acpi_device_hid(adev)); ++ scnprintf(fujitsu.phys, sizeof(fujitsu.phys), "%s/input0", acpi_device_hid(adev)); + +- snprintf(fujitsu.phys, sizeof(fujitsu.phys), +- "%s/input0", acpi_device_hid(adev)); +- +- error = input_fujitsu_setup(&pdev->dev, +- acpi_device_name(adev), fujitsu.phys); ++ error = input_fujitsu_setup(&pdev->dev, fujitsu.name, fujitsu.phys); + if (error) + return error; + +diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +--- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c ++++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +@@ -353,6 +353,11 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor + case PSWD_ENCODINGS: + size = min_t(u32, password_data->encodings_size, MAX_ENCODINGS_SIZE); + for (pos_values = 0; pos_values < size; pos_values++) { ++ if (elem + pos_values >= password_obj_count) { ++ pr_err("Error elem-objects package is too small\n"); ++ return -EINVAL; ++ } ++ + ret = hp_convert_hexstr_to_str(password_obj[elem + pos_values].string.pointer, + password_obj[elem + pos_values].string.length, + &str_value, &value_len); +diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c +--- a/drivers/platform/x86/hp/hp-wmi.c ++++ b/drivers/platform/x86/hp/hp-wmi.c +@@ -14,6 +14,8 @@ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + + #include ++#include ++#include + #include + #include + #include +@@ -23,6 +25,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -58,6 +61,12 @@ enum hp_ec_offsets { + #define HP_FAN_SPEED_AUTOMATIC 0x00 + #define HP_POWER_LIMIT_DEFAULT 0x00 + #define HP_POWER_LIMIT_NO_CHANGE 0xFF ++#define HPWMI_MUX_MODE_UMA BIT(0) ++#define HPWMI_MUX_MODE_HYBRID BIT(1) ++#define HPWMI_MUX_MODE_DISCRETE BIT(2) ++#define HPWMI_MUX_MODE_OPTIMUS BIT(3) ++#define HPWMI_MUX_MODE_MASK GENMASK(6, 0) ++#define HPWMI_MUX_LEGACY_MASK (HPWMI_MUX_MODE_HYBRID | HPWMI_MUX_MODE_DISCRETE) + + #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required + +@@ -133,11 +142,52 @@ static const struct thermal_profile_params omen_v1_no_ec_thermal_params = { + .ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET, + }; + +-/* +- * A generic pointer for the currently-active board's thermal profile +- * parameters. +- */ +-static struct thermal_profile_params *active_thermal_profile_params; ++struct hp_wmi_fan_profile_params { ++ int (*get_fan_speed)(int fan); ++ bool fan_table; ++}; ++ ++struct hp_wmi_board_params { ++ const struct thermal_profile_params *thermal_profile; ++ const struct hp_wmi_fan_profile_params *fan_profile; ++}; ++ ++static int hp_wmi_get_fan_speed_victus_s(int fan); ++ ++static const struct hp_wmi_fan_profile_params victus_s_fan_profile_params = { ++ .get_fan_speed = hp_wmi_get_fan_speed_victus_s, ++ .fan_table = true, ++}; ++ ++static const struct hp_wmi_board_params victus_s_board_params = { ++ .thermal_profile = &victus_s_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, ++}; ++ ++static const struct hp_wmi_board_params omen_v1_board_params = { ++ .thermal_profile = &omen_v1_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, ++}; ++ ++static const struct hp_wmi_board_params omen_v1_legacy_board_params = { ++ .thermal_profile = &omen_v1_legacy_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, ++}; ++ ++static const struct hp_wmi_board_params omen_v1_no_ec_board_params = { ++ .thermal_profile = &omen_v1_no_ec_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, ++}; ++ ++static const struct hp_wmi_board_params *active_board_params; ++ ++static const struct thermal_profile_params *hp_wmi_thermal_profile(void) ++{ ++ if (!active_board_params) ++ return NULL; ++ ++ return active_board_params->thermal_profile; ++} + + /* DMI board names of devices that should use the omen specific path for + * thermal profiles. +@@ -157,7 +207,7 @@ static const char * const omen_thermal_profile_boards[] = { + "886B", "886C", "88C8", "88CB", "88D1", "88D2", "88F4", "88F5", "88F6", + "88F7", "88FD", "88FE", "88FF", + "8900", "8901", "8902", "8912", "8917", "8918", "8949", "894A", "89EB", +- "8A15", "8A42", ++ "8A15", "8A42", "8A43", + "8BAD", + "8C58", + "8E41", +@@ -187,83 +237,103 @@ static const char * const victus_thermal_profile_boards[] = { + "8A25", + }; + +-/* DMI Board names of Victus 16-r and Victus 16-s laptops */ +-static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = { ++/* DMI board-specific feature data for Omen and Victus laptops. */ ++static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, ++ }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A3D") }, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A44") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, ++ }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAA") }, ++ .driver_data = (void *)&omen_v1_board_params, ++ }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BA9") }, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAB") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, ++ }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BB3") }, ++ .driver_data = (void *)&omen_v1_no_ec_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BC2") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCA") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCD") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C76") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C77") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D26") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") }, +- .driver_data = (void *)&omen_v1_no_ec_thermal_params, ++ .driver_data = (void *)&omen_v1_no_ec_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") }, +- .driver_data = (void *)&omen_v1_no_ec_thermal_params, ++ .driver_data = (void *)&omen_v1_no_ec_board_params, ++ }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D88") }, ++ .driver_data = (void *)&omen_v1_no_ec_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD6") }, +@@ -271,7 +341,7 @@ static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, + }, + {}, + }; +@@ -336,6 +406,7 @@ enum hp_wmi_commandtype { + HPWMI_POSTCODEERROR_QUERY = 0x2a, + HPWMI_SYSTEM_DEVICE_MODE = 0x40, + HPWMI_THERMAL_PROFILE_QUERY = 0x4c, ++ HPWMI_GRAPHICS_MUX_QUERY = 0x52, + }; + + struct victus_power_limits { +@@ -504,9 +575,9 @@ struct hp_wmi_hwmon_priv { + struct mutex lock; /* protects mode, pwm */ + u8 min_rpm; + u8 max_rpm; +- int gpu_delta; + u8 mode; +- u8 pwm; ++ u8 cpu_pwm; ++ u8 gpu_pwm; + struct delayed_work keep_alive_dwork; + }; + +@@ -833,24 +904,20 @@ static int hp_wmi_fan_speed_max_set(int enabled) + return enabled; + } + +-static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) ++static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv) + { + u8 fan_speed[2]; +- int gpu_speed, ret; ++ int ret; + +- fan_speed[CPU_FAN] = speed; +- fan_speed[GPU_FAN] = speed; ++ if (priv->cpu_pwm == HP_FAN_SPEED_AUTOMATIC) ++ fan_speed[CPU_FAN] = HP_FAN_SPEED_AUTOMATIC; ++ else ++ fan_speed[CPU_FAN] = pwm_to_rpm(priv->cpu_pwm, priv); + +- /* +- * GPU fan speed is always a little higher than CPU fan speed, we fetch +- * this delta value from the fan table during hwmon init. +- * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to +- * automatic mode. +- */ +- if (speed != HP_FAN_SPEED_AUTOMATIC) { +- gpu_speed = speed + priv->gpu_delta; +- fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX); +- } ++ if (priv->gpu_pwm == HP_FAN_SPEED_AUTOMATIC) ++ fan_speed[GPU_FAN] = HP_FAN_SPEED_AUTOMATIC; ++ else ++ fan_speed[GPU_FAN] = pwm_to_rpm(priv->gpu_pwm, priv); + + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) +@@ -867,7 +934,9 @@ static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) + + static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv) + { +- return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC); ++ priv->cpu_pwm = HP_FAN_SPEED_AUTOMATIC; ++ priv->gpu_pwm = HP_FAN_SPEED_AUTOMATIC; ++ return hp_wmi_fan_speed_set(priv); + } + + static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv) +@@ -1128,12 +1197,134 @@ static int camera_shutter_input_setup(void) + return err; + } + ++static const u8 mux_bitmask_map[] = { ++ [0] = HPWMI_MUX_MODE_HYBRID, ++ [1] = HPWMI_MUX_MODE_DISCRETE, ++ [2] = HPWMI_MUX_MODE_OPTIMUS, ++ [3] = HPWMI_MUX_MODE_UMA, ++}; ++ ++static int hp_wmi_get_mux_supported_modes(u8 *supported) ++{ ++ u8 legacy_buffer[4] = {}; ++ u8 buffer[128] = {}; ++ u32 req_packet = 0; ++ int ret; ++ ++ if (!supported) ++ return -EINVAL; ++ ++ /* Try modern BIOS design data query (128-byte buffer) */ ++ ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM, ++ buffer, zero_if_sup(req_packet), sizeof(buffer)); ++ if (ret == 0) { ++ *supported = buffer[7]; ++ return 0; ++ } ++ ++ /* ++ * (Fallback): Legacy BIOS behavior based on Omen Gaming Hub. ++ * If the modern query is not supported, check if the MUX query endpoint ++ * responds to a read request. If it succeeds, the hardware has MUX ++ * capability but lacks the mode map, defaulting to Hybrid + Discrete. ++ */ ++ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ, ++ legacy_buffer, sizeof(legacy_buffer), 0); ++ if (ret < 0) ++ return ret; ++ if (ret > 0) ++ return -EINVAL; ++ ++ *supported = HPWMI_MUX_LEGACY_MASK; ++ return 0; ++} ++ ++static int hp_wmi_get_mux_mode(u8 *mode) ++{ ++ u8 buffer[4] = {}; ++ int ret; ++ ++ if (!mode) ++ return -EINVAL; ++ ++ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ, ++ buffer, sizeof(buffer), sizeof(buffer)); ++ if (ret < 0) ++ return ret; ++ if (ret > 0) ++ return -EINVAL; ++ ++ /* Mask the highest bit, which might be used as a BIOS status flag */ ++ *mode = buffer[0] & HPWMI_MUX_MODE_MASK; ++ return 0; ++} ++ ++static int hp_wmi_set_mux_mode(u8 mode) ++{ ++ u8 buffer[4] = { mode, 0x00, 0x00, 0x00 }; ++ int ret; ++ ++ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_WRITE, ++ buffer, sizeof(buffer), sizeof(buffer)); ++ if (ret < 0) ++ return ret; ++ if (ret > 0) ++ return -EINVAL; ++ ++ return 0; ++} ++ ++static ssize_t gpu_mux_mode_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ u8 mode; ++ int ret; ++ ++ ret = hp_wmi_get_mux_mode(&mode); ++ if (ret) ++ return ret; ++ ++ return sysfs_emit(buf, "%u\n", mode); ++} ++ ++static ssize_t gpu_mux_mode_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, ++ size_t count) ++{ ++ u32 requested; ++ u8 supported; ++ int ret; ++ ++ ret = kstrtou32(buf, 0, &requested); ++ if (ret) ++ return ret; ++ if (requested >= ARRAY_SIZE(mux_bitmask_map)) ++ return -EINVAL; ++ ++ ret = hp_wmi_get_mux_supported_modes(&supported); ++ if (ret) ++ return ret; ++ ++ /* Verify if the requested mode is allowed by the hardware mask */ ++ if (!(supported & mux_bitmask_map[requested])) ++ return -EOPNOTSUPP; ++ ++ ret = hp_wmi_set_mux_mode(requested); ++ if (ret) ++ return ret; ++ ++ return count; ++} ++ + static DEVICE_ATTR_RO(display); + static DEVICE_ATTR_RO(hddtemp); + static DEVICE_ATTR_RW(als); + static DEVICE_ATTR_RO(dock); + static DEVICE_ATTR_RO(tablet); + static DEVICE_ATTR_RW(postcode); ++static DEVICE_ATTR_RW(gpu_mux_mode); + + static struct attribute *hp_wmi_attrs[] = { + &dev_attr_display.attr, +@@ -1142,6 +1333,7 @@ static struct attribute *hp_wmi_attrs[] = { + &dev_attr_dock.attr, + &dev_attr_tablet.attr, + &dev_attr_postcode.attr, ++ &dev_attr_gpu_mux_mode.attr, + NULL, + }; + ATTRIBUTE_GROUPS(hp_wmi); +@@ -1793,6 +1985,38 @@ static bool is_victus_s_thermal_profile(void) + return is_victus_s_board; + } + ++static const struct hp_wmi_fan_profile_params *hp_wmi_fan_profile(void) ++{ ++ if (!active_board_params) ++ return NULL; ++ ++ return active_board_params->fan_profile; ++} ++ ++static bool hp_wmi_fan_control_supported(void) ++{ ++ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); ++ ++ return params && params->get_fan_speed; ++} ++ ++static bool hp_wmi_fan_table_supported(void) ++{ ++ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); ++ ++ return params && params->fan_table; ++} ++ ++static int hp_wmi_get_active_fan_speed(int fan) ++{ ++ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); ++ ++ if (!params || !params->get_fan_speed) ++ return -EOPNOTSUPP; ++ ++ return params->get_fan_speed(fan); ++} ++ + static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable, + bool *ppab_enable, + u8 *dstate, +@@ -1878,7 +2102,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil + u8 current_dstate, current_gpu_slowdown_temp, tp; + const struct thermal_profile_params *params; + +- params = active_thermal_profile_params; ++ params = hp_wmi_thermal_profile(); ++ if (!params) ++ return -ENODEV; ++ + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || + params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { + *profile = active_platform_profile; +@@ -1890,10 +2117,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil + return ret; + + /* +- * We cannot use active_thermal_profile_params here, because boards +- * like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically +- * it should have tp == 0x30 || tp == 0x31, as corrected by the Omen +- * Gaming Hub on windows. Hence accept both of these values. ++ * Boards like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, ++ * but logically it should have tp == 0x30 || tp == 0x31, as ++ * corrected by the Omen Gaming Hub on windows. Hence accept both ++ * of these values. + */ + if (tp == victus_s_thermal_params.performance || + tp == omen_v1_thermal_params.performance) { +@@ -1928,12 +2155,12 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil + + static int platform_profile_victus_s_set_ec(enum platform_profile_option profile) + { +- struct thermal_profile_params *params; ++ const struct thermal_profile_params *params; + bool gpu_ctgp_enable, gpu_ppab_enable; + u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */ + int err, tp; + +- params = active_thermal_profile_params; ++ params = hp_wmi_thermal_profile(); + if (!params) + return -ENODEV; + +@@ -2199,6 +2426,7 @@ static const struct platform_profile_ops hp_wmi_platform_profile_ops = { + static int thermal_profile_setup(struct platform_device *device) + { + const struct platform_profile_ops *ops; ++ const struct thermal_profile_params *params; + int err, tp; + + if (is_omen_thermal_profile()) { +@@ -2230,13 +2458,17 @@ static int thermal_profile_setup(struct platform_device *device) + + ops = &platform_profile_victus_ops; + } else if (is_victus_s_thermal_profile()) { ++ params = hp_wmi_thermal_profile(); ++ if (!params) ++ return -ENODEV; ++ + /* + * For an unknown EC layout board, platform_profile_victus_s_get_ec(), + * behaves like a wrapper around active_platform_profile, to avoid using + * uninitialized data, we default to PLATFORM_PROFILE_BALANCED. + */ +- if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || +- active_thermal_profile_params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { ++ if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || ++ params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { + active_platform_profile = PLATFORM_PROFILE_BALANCED; + } else { + err = platform_profile_victus_s_get_ec(&active_platform_profile); +@@ -2404,7 +2636,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) + + switch (priv->mode) { + case PWM_MODE_MAX: +- if (is_victus_s_thermal_profile()) { ++ if (hp_wmi_fan_control_supported()) { + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) + return ret; +@@ -2416,16 +2648,16 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) + secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); + return 0; + case PWM_MODE_MANUAL: +- if (!is_victus_s_thermal_profile()) ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; +- ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv)); ++ ret = hp_wmi_fan_speed_set(priv); + if (ret < 0) + return ret; + mod_delayed_work(system_dfl_wq, &priv->keep_alive_dwork, + secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); + return 0; + case PWM_MODE_AUTO: +- if (is_victus_s_thermal_profile()) { ++ if (hp_wmi_fan_control_supported()) { + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) + return ret; +@@ -2449,12 +2681,12 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, + { + switch (type) { + case hwmon_pwm: +- if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile()) ++ if (attr == hwmon_pwm_input && !hp_wmi_fan_control_supported()) + return 0; + return 0644; + case hwmon_fan: +- if (is_victus_s_thermal_profile()) { +- if (hp_wmi_get_fan_speed_victus_s(channel) >= 0) ++ if (hp_wmi_fan_control_supported()) { ++ if (hp_wmi_get_active_fan_speed(channel) >= 0) + return 0444; + } else { + if (hp_wmi_get_fan_speed(channel) >= 0) +@@ -2478,8 +2710,8 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + priv = dev_get_drvdata(dev); + switch (type) { + case hwmon_fan: +- if (is_victus_s_thermal_profile()) +- ret = hp_wmi_get_fan_speed_victus_s(channel); ++ if (hp_wmi_fan_control_supported()) ++ ret = hp_wmi_get_active_fan_speed(channel); + else + ret = hp_wmi_get_fan_speed(channel); + if (ret < 0) +@@ -2488,10 +2720,10 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + return 0; + case hwmon_pwm: + if (attr == hwmon_pwm_input) { +- if (!is_victus_s_thermal_profile()) ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; + +- rpm = hp_wmi_get_fan_speed_victus_s(channel); ++ rpm = hp_wmi_get_active_fan_speed(channel); + if (rpm < 0) + return rpm; + *val = rpm_to_pwm(rpm / 100, priv); +@@ -2518,14 +2750,15 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) + { + struct hp_wmi_hwmon_priv *priv; +- int rpm; ++ int cpu_rpm, gpu_rpm; + + priv = dev_get_drvdata(dev); + guard(mutex)(&priv->lock); + switch (type) { + case hwmon_pwm: + if (attr == hwmon_pwm_input) { +- if (!is_victus_s_thermal_profile()) ++ int rpm; ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; + /* PWM input is invalid when not in manual mode */ + if (priv->mode != PWM_MODE_MANUAL) +@@ -2534,7 +2767,10 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + /* ensure PWM input is within valid fan speeds */ + rpm = pwm_to_rpm(val, priv); + rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm); +- priv->pwm = rpm_to_pwm(rpm, priv); ++ if (channel == CPU_FAN) ++ priv->cpu_pwm = rpm_to_pwm(rpm, priv); ++ else if (channel == GPU_FAN) ++ priv->gpu_pwm = rpm_to_pwm(rpm, priv); + return hp_wmi_apply_fan_settings(priv); + } + switch (val) { +@@ -2542,16 +2778,20 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + priv->mode = PWM_MODE_MAX; + return hp_wmi_apply_fan_settings(priv); + case PWM_MODE_MANUAL: +- if (!is_victus_s_thermal_profile()) ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; + /* + * When switching to manual mode, set fan speed to + * current RPM values to ensure a smooth transition. + */ +- rpm = hp_wmi_get_fan_speed_victus_s(channel); +- if (rpm < 0) +- return rpm; +- priv->pwm = rpm_to_pwm(rpm / 100, priv); ++ cpu_rpm = hp_wmi_get_active_fan_speed(CPU_FAN); ++ if (cpu_rpm < 0) ++ return cpu_rpm; ++ gpu_rpm = hp_wmi_get_active_fan_speed(GPU_FAN); ++ if (gpu_rpm < 0) ++ return gpu_rpm; ++ priv->cpu_pwm = rpm_to_pwm(cpu_rpm / 100, priv); ++ priv->gpu_pwm = rpm_to_pwm(gpu_rpm / 100, priv); + priv->mode = PWM_MODE_MANUAL; + return hp_wmi_apply_fan_settings(priv); + case PWM_MODE_AUTO: +@@ -2567,7 +2807,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + + static const struct hwmon_channel_info * const info[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), +- HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT), ++ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT, HWMON_PWM_INPUT), + NULL + }; + +@@ -2608,14 +2848,14 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) + struct victus_s_fan_table *fan_table; + u8 min_rpm, max_rpm; + u8 cpu_rpm, gpu_rpm, noise_db; +- int gpu_delta, i, num_entries, ret; ++ int i, num_entries, ret; + size_t header_size, entry_size; + + /* Default behaviour on hwmon init is automatic mode */ + priv->mode = PWM_MODE_AUTO; + +- /* Bypass all non-Victus S devices */ +- if (!is_victus_s_thermal_profile()) ++ /* Bypass devices without fan control support. */ ++ if (!hp_wmi_fan_table_supported()) + return 0; + + ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY, +@@ -2654,10 +2894,8 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) + if (min_rpm == U8_MAX || max_rpm == 0) + return -EINVAL; + +- gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm; + priv->min_rpm = min_rpm; + priv->max_rpm = max_rpm; +- priv->gpu_delta = gpu_delta; + + return 0; + } +@@ -2697,24 +2935,25 @@ static int hp_wmi_hwmon_init(void) + return 0; + } + +-static void __init setup_active_thermal_profile_params(void) ++static void __init setup_active_board_params(void) + { + const struct dmi_system_id *id; ++ const struct thermal_profile_params *params; + +- /* +- * Currently only victus_s devices use the +- * active_thermal_profile_params +- */ +- id = dmi_first_match(victus_s_thermal_profile_boards); ++ id = dmi_first_match(hp_wmi_feature_boards); + if (id) { ++ active_board_params = id->driver_data; ++ params = hp_wmi_thermal_profile(); ++ if (!params) ++ return; ++ + /* + * Marking this boolean is required to ensure that + * is_victus_s_thermal_profile() behaves like a valid + * wrapper. + */ + is_victus_s_board = true; +- active_thermal_profile_params = id->driver_data; +- if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { ++ if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { + pr_warn("Unknown EC layout for board %s. Thermal profile readback will be disabled. Please report this to platform-driver-x86@vger.kernel.org\n", + dmi_get_system_info(DMI_BOARD_NAME)); + } +@@ -2749,10 +2988,10 @@ static int __init hp_wmi_init(void) + } + + /* +- * Setup active board's thermal profile parameters before +- * starting platform driver probe. ++ * Setup active board feature data before starting platform ++ * driver probe. + */ +- setup_active_thermal_profile_params(); ++ setup_active_board_params(); + err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup); + if (err) + goto err_unregister_device; +diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c +--- a/drivers/platform/x86/huawei-wmi.c ++++ b/drivers/platform/x86/huawei-wmi.c +@@ -6,6 +6,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -527,11 +528,104 @@ static void huawei_wmi_battery_exit(struct device *dev) + + /* Fn lock */ + ++/* GFRS byte[1] / SFRS byte[2] (FRSR) fn-lock state values */ ++#define FN_LOCK_ACPI_OFF 1 ++#define FN_LOCK_ACPI_ON 2 ++#define FN_LOCK_ACPI_STAT_OK 0 ++ ++/* ++ * Newer Huawei models (e.g. HUAWEI FLMH-XX / MateBook 14 2024) use direct ++ * ACPI methods \GFRS / \SFRS (Get/Set Fn key Reversal Status) to control ++ * Fn-lock via EC registers 0x6B (read) and 0x6C (write). ++ * ++ * GFRS response buffer layout: ++ * byte[0] = STAT (FN_LOCK_ACPI_STAT_OK = success) ++ * byte[1] = FN_LOCK_ACPI_OFF (fn-lock off) or FN_LOCK_ACPI_ON (fn-lock on) ++ * ++ * SFRS argument layout (CreateByteField(Arg0, 0x02, FRSR)): ++ * Value is read from byte[2] of the integer argument, so it must be ++ * passed as (value << 16): ++ * (FN_LOCK_ACPI_OFF << 16) = fn-lock off (writes 0x55 to EC 0x6C) ++ * (FN_LOCK_ACPI_ON << 16) = fn-lock on (writes 0x5A to EC 0x6C) ++ */ ++ ++static int huawei_acpi_fn_lock_get(int *on) ++{ ++ union acpi_object acpi_arg; ++ struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg }; ++ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; ++ acpi_status status; ++ ++ acpi_arg.type = ACPI_TYPE_INTEGER; ++ acpi_arg.integer.value = 0; ++ ++ status = acpi_evaluate_object(NULL, "\\GFRS", &arg_list, &output); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ union acpi_object *obj __free(kfree) = output.pointer; ++ if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 2) ++ return -ENODATA; ++ ++ /* byte[0] = STAT, byte[1] = fn-lock state */ ++ if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK) ++ return -EIO; ++ ++ switch (obj->buffer.pointer[1]) { ++ case FN_LOCK_ACPI_OFF: ++ if (on) ++ *on = 0; ++ break; ++ case FN_LOCK_ACPI_ON: ++ if (on) ++ *on = 1; ++ break; ++ default: ++ return -ENODATA; ++ } ++ ++ return 0; ++} ++ ++static int huawei_acpi_fn_lock_set(int on) ++{ ++ union acpi_object acpi_arg; ++ struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg }; ++ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; ++ acpi_status status; ++ ++ /* ++ * SFRS reads byte[2] of its argument via CreateByteField(Arg0, 0x02). ++ * on=0 → FRSR=FN_LOCK_ACPI_OFF → EC gets 0x55 (fn-lock off) ++ * on=1 → FRSR=FN_LOCK_ACPI_ON → EC gets 0x5A (fn-lock on) ++ */ ++ acpi_arg.type = ACPI_TYPE_INTEGER; ++ acpi_arg.integer.value = (on ? FN_LOCK_ACPI_ON : FN_LOCK_ACPI_OFF) << 16; ++ ++ status = acpi_evaluate_object(NULL, "\\SFRS", &arg_list, &output); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ union acpi_object *obj __free(kfree) = output.pointer; ++ if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 1) ++ return -ENODATA; ++ ++ if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK) ++ return -EIO; ++ ++ return 0; ++} ++ + static int huawei_wmi_fn_lock_get(int *on) + { + u8 ret[0x100] = { 0 }; + int err, i; + ++ /* Newer models: use direct ACPI \GFRS method */ ++ if (acpi_has_method(NULL, "\\GFRS")) ++ return huawei_acpi_fn_lock_get(on); ++ ++ /* WMI fallback */ + err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100); + if (err) + return err; +@@ -550,6 +644,11 @@ static int huawei_wmi_fn_lock_set(int on) + { + union hwmi_arg arg; + ++ /* Newer models: use direct ACPI \SFRS method */ ++ if (acpi_has_method(NULL, "\\SFRS")) ++ return huawei_acpi_fn_lock_set(on); ++ ++ /* WMI fallback */ + arg.cmd = FN_LOCK_SET; + arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on. + +diff --git a/drivers/platform/x86/intel/int1092/intel_sar.c b/drivers/platform/x86/intel/int1092/intel_sar.c +--- a/drivers/platform/x86/intel/int1092/intel_sar.c ++++ b/drivers/platform/x86/intel/int1092/intel_sar.c +@@ -91,10 +91,10 @@ static acpi_status parse_package(struct wwan_sar_context *context, union acpi_ob + item->package.count <= data->total_dev_mode) + return AE_ERROR; + +- data->device_mode_info = devm_kmalloc_array(&context->sar_device->dev, +- data->total_dev_mode, +- sizeof(*data->device_mode_info), +- GFP_KERNEL); ++ data->device_mode_info = devm_kcalloc(&context->sar_device->dev, ++ data->total_dev_mode, ++ sizeof(*data->device_mode_info), ++ GFP_KERNEL); + if (!data->device_mode_info) + return AE_ERROR; + +diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c +--- a/drivers/platform/x86/intel/pmc/core.c ++++ b/drivers/platform/x86/intel/pmc/core.c +@@ -1583,7 +1583,7 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel + { + const u8 *lpm_indices; + int num_maps, mode_offset = 0; +- int ret, lpm_size; ++ int ret = 0, lpm_size; + u8 mode; + + lpm_indices = pmc->map->lpm_reg_index; +diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig +--- a/drivers/platform/x86/lenovo/Kconfig ++++ b/drivers/platform/x86/lenovo/Kconfig +@@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA + To compile this driver as a module, choose M here: the module + will be called lenovo-wmi-camera. + ++config LENOVO_YB9_KBDOCK ++ tristate "Lenovo Yoga Book 9 keyboard dock detection" ++ depends on ACPI_WMI ++ depends on DMI ++ depends on INPUT ++ help ++ Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9 ++ 14IAH10. The detachable Bluetooth keyboard magnetically attaches to ++ either screen; this driver reports SW_TABLET_MODE input events based ++ on the attachment state and exposes the raw position in sysfs. ++ ++ To compile this driver as a module, choose M here: the module will be ++ called lenovo-yb9-kbdock. ++ + config LENOVO_YMC + tristate "Lenovo Yoga Tablet Mode Control" + depends on ACPI_WMI +diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile +--- a/drivers/platform/x86/lenovo/Makefile ++++ b/drivers/platform/x86/lenovo/Makefile +@@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o + obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o + + lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o ++lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o + lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o + lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o + lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o +diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c +--- a/drivers/platform/x86/lenovo/ideapad-laptop.c ++++ b/drivers/platform/x86/lenovo/ideapad-laptop.c +@@ -2564,8 +2564,8 @@ module_init(ideapad_laptop_init) + + static void __exit ideapad_laptop_exit(void) + { +- ideapad_wmi_driver_unregister(); + platform_driver_unregister(&ideapad_acpi_driver); ++ ideapad_wmi_driver_unregister(); + } + module_exit(ideapad_laptop_exit) + +diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c +--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c ++++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c +@@ -38,6 +38,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -67,6 +68,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -186,6 +188,7 @@ enum tpacpi_hkey_event_t { + TP_HKEY_EV_AMT_TOGGLE = 0x131a, /* Toggle AMT on/off */ + TP_HKEY_EV_CAMERASHUTTER_TOGGLE = 0x131b, /* Toggle Camera Shutter */ + TP_HKEY_EV_DOUBLETAP_TOGGLE = 0x131c, /* Toggle trackpoint doubletap on/off */ ++ TP_HKEY_EV_USB_C_SECURITY = 0x131e, /* USB C Security (Fn+U, Fn+S) */ + TP_HKEY_EV_PROFILE_TOGGLE = 0x131f, /* Toggle platform profile in 2024 systems */ + TP_HKEY_EV_PROFILE_TOGGLE2 = 0x1401, /* Toggle platform profile in 2025 + systems */ + +@@ -309,6 +312,7 @@ struct tp_acpi_drv_struct { + + struct ibm_struct { + char *name; ++ acpi_device_class device_class; + + int (*read) (struct seq_file *); + int (*write) (char *); +@@ -374,6 +378,8 @@ static struct { + u32 has_adaptive_kbd:1; + u32 kbd_lang:1; + u32 trackpoint_doubletap_enable:1; ++ u32 usbc_security_supported:1; ++ bool usbc_security_enabled; + struct quirk_entry *quirks; + } tp_features; + +@@ -838,9 +844,8 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm) + } + + ibm->acpi->device->driver_data = ibm; +- scnprintf(acpi_device_class(ibm->acpi->device), +- sizeof(acpi_device_class(ibm->acpi->device)), +- "%s/%s", TPACPI_ACPI_EVENT_PREFIX, ibm->name); ++ scnprintf(ibm->device_class, sizeof(ibm->device_class), "%s/%s", ++ TPACPI_ACPI_EVENT_PREFIX, ibm->name); + + status = acpi_install_notify_handler(*ibm->acpi->handle, + ibm->acpi->type, dispatch_acpi_notify, ibm); +@@ -3869,7 +3874,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) + pr_err("unknown HKEY notification event %d\n", event); + /* forward it to userspace, maybe it knows how to handle it */ + acpi_bus_generate_netlink_event( +- ibm->acpi->device->pnp.device_class, ++ ibm->device_class, + dev_name(&ibm->acpi->device->dev), + event, 0); + return; +@@ -3949,7 +3954,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) + /* netlink events */ + if (send_acpi_ev) { + acpi_bus_generate_netlink_event( +- ibm->acpi->device->pnp.device_class, ++ ibm->device_class, + dev_name(&ibm->acpi->device->dev), + event, hkey); + } +@@ -8840,6 +8845,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = { + TPACPI_Q_LNV3('R', '0', 'T', TPACPI_FAN_NS), /* 11e Gen5 GL */ + TPACPI_Q_LNV3('R', '1', 'D', TPACPI_FAN_NS), /* 11e Gen5 GL-R */ + TPACPI_Q_LNV3('R', '0', 'V', TPACPI_FAN_NS), /* 11e Gen5 KL-Y */ ++ TPACPI_Q_LNV('H', '3', TPACPI_FAN_NS), /* Edge E330 */ + TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */ + TPACPI_Q_LNV3('R', '0', 'Q', TPACPI_FAN_DECRPM),/* L480 */ + TPACPI_Q_LNV('8', 'F', TPACPI_FAN_TPR), /* ThinkPad x120e */ +@@ -11285,6 +11291,114 @@ static struct ibm_struct hwdd_driver_data = { + .name = "hwdd", + }; + ++/************************************************************************* ++ * USB-C Security subdriver ++ * ++ * HKEY.USCS(0) is a read-only ACPI method; its argument is ignored. ++ * It always returns: ++ * bit 16 - USB-C security capability present on this SKU or not ++ * bit 0 - USB-C Security state (enable or disable) ++ * ++ * Hotkey ++ * ------ ++ * 0x131e (Fn+U, Fn+S): firmware toggles USBS before firing the event. ++ * The driver reads back the new state and notifies the sysfs attribute. ++ */ ++ ++/* USCS() return word bit layout */ ++#define USCS_CAP_BIT BIT(16) /* capability: feature present on SKU */ ++#define USCS_STATUS_BIT BIT(0) /* current security state */ ++ ++/* Protects USCS() ACPI method calls in usbc_security_query() */ ++static DEFINE_MUTEX(usbc_security_mutex); ++ ++/** ++ * usbc_security_query - read current USB-C security state via USCS() ++ * @enabled: out - true when security is ON (data connections blocked) ++ * ++ * Returns: ++ * 0 success, @enabled contains the current state ++ * -EIO ACPI evaluation failed ++ * -ENODEV capability bit absent; feature not present on this SKU* ++ */ ++static int usbc_security_query(bool *enabled) ++{ ++ int status; ++ ++ guard(mutex)(&usbc_security_mutex); ++ if (!acpi_evalf(hkey_handle, &status, "USCS", "dd", 0)) ++ return -EIO; ++ ++ if (!(status & USCS_CAP_BIT)) { ++ pr_debug("USCS cap bit absent (raw=0x%x)\n", status); ++ return -ENODEV; ++ } ++ ++ *enabled = status & USCS_STATUS_BIT; ++ return 0; ++} ++ ++/* sysfs: /sys/devices/platform/thinkpad_acpi/usb_c_security ---------- */ ++static ssize_t usb_c_security_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return sysfs_emit(buf, "%s\n", ++ str_enabled_disabled(tp_features.usbc_security_enabled)); ++} ++ ++static DEVICE_ATTR_RO(usb_c_security); ++ ++static struct attribute *usbc_security_attributes[] = { ++ &dev_attr_usb_c_security.attr, ++ NULL, ++}; ++ ++static umode_t usbc_security_attr_is_visible(struct kobject *kobj, ++ struct attribute *attr, int n) ++{ ++ return tp_features.usbc_security_supported ? attr->mode : 0; ++} ++ ++static const struct attribute_group usbc_security_attr_group = { ++ .is_visible = usbc_security_attr_is_visible, ++ .attrs = usbc_security_attributes, ++}; ++ ++static int tpacpi_usbc_security_init(struct ibm_init_struct *iibm) ++{ ++ int err; ++ ++ if (!acpi_has_method(hkey_handle, "USCS")) ++ return -ENODEV; ++ ++ err = usbc_security_query(&tp_features.usbc_security_enabled); ++ if (err == -ENODEV) ++ return 0; ++ if (err) ++ return err; ++ ++ tp_features.usbc_security_supported = true; ++ return 0; ++} ++ ++/* tpacpi_usbc_security_hotkey - handle Fn+U Fn+S hotkey (0x131e) */ ++static bool tpacpi_usbc_security_hotkey(void) ++{ ++ if (!tp_features.usbc_security_supported) ++ return false; ++ ++ if (usbc_security_query(&tp_features.usbc_security_enabled)) ++ return false; ++ ++ sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "usb_c_security"); ++ return true; ++} ++ ++static struct ibm_struct usbc_security_driver_data = { ++ .name = "usbc_security", ++}; ++ + /* --------------------------------------------------------------------- */ + + static struct attribute *tpacpi_driver_attributes[] = { +@@ -11345,6 +11459,7 @@ static const struct attribute_group *tpacpi_groups[] = { + &dprc_attr_group, + &auxmac_attr_group, + &hwdd_attr_group, ++ &usbc_security_attr_group, + NULL, + }; + +@@ -11499,6 +11614,8 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) + case TP_HKEY_EV_PROFILE_TOGGLE2: + platform_profile_cycle(); + return true; ++ case TP_HKEY_EV_USB_C_SECURITY: ++ return tpacpi_usbc_security_hotkey(); + } + + return false; +@@ -11964,6 +12081,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { + .init = tpacpi_hwdd_init, + .data = &hwdd_driver_data, + }, ++ { ++ .init = tpacpi_usbc_security_init, ++ .data = &usbc_security_driver_data, ++ }, + }; + + static int __init set_ibm_param(const char *val, const struct kernel_param *kp) +diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/lenovo/yb9-kbdock.c +@@ -0,0 +1,323 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Lenovo Yoga Book 9 keyboard-dock detection ++ * ++ * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically ++ * attaches to the bottom screen in one of two positions. The EC tracks ++ * attachment state in a 2-bit field called BKBD and signals changes via WMI ++ * event 0xEB on the WM10 ACPI device (_UID "GMZN"). ++ * ++ * BKBD values: ++ * 0 = keyboard detached ++ * 1 = keyboard docked on the top half of the bottom screen ++ * 2 = keyboard docked on the bottom half of the bottom screen ++ * 3 = reserved / not observed ++ * ++ * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes): ++ * ++ * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...) ++ * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly. ++ * The notify callback receives BKBD as an integer; no separate query needed. ++ * ++ * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...) ++ * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector) ++ * WmiDataId(2) uint32 Status = BKBD value ++ * Used on probe and resume to read initial state. ++ * ++ * The event driver (LENOVO_BTKBD_EVENT) fires a notifier chain on each WMI ++ * event. The block driver (LENOVO_FEATURE_STATUS_DATA) owns the input_dev ++ * and registers a notifier_block to receive those events, eliminating the ++ * need for shared global state or a mutex. ++ * ++ * SW_TABLET_MODE=1 is reported when the keyboard is detached; ++ * SW_TABLET_MODE=0 when docked in either position (keyboard present). ++ * The raw BKBD value is exposed via the sysfs attribute "keyboard_position". ++ * ++ * Copyright (C) 2026 Dave Carey ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285" ++#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6" ++ ++/* BKBD encoding */ ++#define BKBD_DETACHED 0 ++ ++/* LENOVO_FEATURE_STATUS_DATA feature selector */ ++#define YB9_FEATURE_STATUS_ID 0x00060000u ++ ++/* ++ * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {uint32 IDs, uint32 Status}. ++ * IDs is always 0x00060000; Status holds the BKBD value (0–3). ++ */ ++struct lenovo_feature_status { ++ __le32 id; ++ __le32 status; ++} __packed; ++ ++/* ------------------------------------------------------------------ ++ * Notifier chain — event driver fires it, block driver listens ++ * ------------------------------------------------------------------ */ ++ ++static BLOCKING_NOTIFIER_HEAD(yb9_kbdock_chain_head); ++ ++static void devm_yb9_kbdock_unregister_notifier(void *data) ++{ ++ struct notifier_block *nb = data; ++ ++ blocking_notifier_chain_unregister(&yb9_kbdock_chain_head, nb); ++} ++ ++static int devm_yb9_kbdock_register_notifier(struct device *dev, ++ struct notifier_block *nb) ++{ ++ int ret; ++ ++ ret = blocking_notifier_chain_register(&yb9_kbdock_chain_head, nb); ++ if (ret < 0) ++ return ret; ++ ++ return devm_add_action_or_reset(dev, devm_yb9_kbdock_unregister_notifier, nb); ++} ++ ++/* ------------------------------------------------------------------ ++ * Block WMI driver — LENOVO_FEATURE_STATUS_DATA ++ * (owns input_dev, sysfs, PM resume) ++ * ------------------------------------------------------------------ */ ++ ++struct yb9_kbdock_data { ++ struct wmi_device *wdev; ++ struct input_dev *input_dev; ++ struct notifier_block nb; ++ spinlock_t lock; /* protects input_report_switch + input_sync */ ++}; ++ ++static int yb9_kbdock_query(struct yb9_kbdock_data *d, u32 *bkbd) ++{ ++ struct wmi_buffer out; ++ int ret; ++ ++ ret = wmidev_query_block(d->wdev, 0, &out, ++ sizeof(struct lenovo_feature_status)); ++ if (ret) ++ return ret; ++ ++ struct lenovo_feature_status *fs __free(kfree) = out.data; ++ ++ if (le32_to_cpu(fs->id) != YB9_FEATURE_STATUS_ID) ++ return -EIO; ++ ++ *bkbd = le32_to_cpu(fs->status); ++ return 0; ++} ++ ++static void yb9_kbdock_report(struct yb9_kbdock_data *d, u32 bkbd) ++{ ++ int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0; ++ ++ spin_lock(&d->lock); ++ input_report_switch(d->input_dev, SW_TABLET_MODE, tablet); ++ input_sync(d->input_dev); ++ spin_unlock(&d->lock); ++ dev_dbg(&d->wdev->dev, "BKBD=%u SW_TABLET_MODE=%d\n", bkbd, tablet); ++} ++ ++static int yb9_kbdock_sync(struct yb9_kbdock_data *d) ++{ ++ u32 bkbd; ++ int ret; ++ ++ ret = yb9_kbdock_query(d, &bkbd); ++ if (ret) ++ return ret; ++ ++ yb9_kbdock_report(d, bkbd); ++ return 0; ++} ++ ++static int yb9_kbdock_nb_call(struct notifier_block *nb, ++ unsigned long bkbd, void *unused) ++{ ++ struct yb9_kbdock_data *d = ++ container_of(nb, struct yb9_kbdock_data, nb); ++ ++ yb9_kbdock_report(d, bkbd); ++ return NOTIFY_DONE; ++} ++ ++static ssize_t keyboard_position_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct yb9_kbdock_data *d = dev_get_drvdata(dev); ++ u32 bkbd; ++ int ret; ++ ++ ret = yb9_kbdock_query(d, &bkbd); ++ if (ret) ++ return ret; ++ return sysfs_emit(buf, "%u\n", bkbd); ++} ++static DEVICE_ATTR_RO(keyboard_position); ++ ++static const struct attribute * const yb9_kbdock_attrs[] = { ++ &dev_attr_keyboard_position.attr, ++ NULL, ++}; ++ATTRIBUTE_GROUPS(yb9_kbdock); ++ ++static int yb9_kbdock_resume(struct device *dev) ++{ ++ struct yb9_kbdock_data *d = dev_get_drvdata(dev); ++ ++ return yb9_kbdock_sync(d); ++} ++static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume); ++ ++static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx) ++{ ++ struct yb9_kbdock_data *d; ++ struct input_dev *input_dev; ++ int ret; ++ ++ d = devm_kzalloc(&wdev->dev, sizeof(*d), GFP_KERNEL); ++ if (!d) ++ return -ENOMEM; ++ ++ d->wdev = wdev; ++ spin_lock_init(&d->lock); ++ ++ input_dev = devm_input_allocate_device(&wdev->dev); ++ if (!input_dev) ++ return -ENOMEM; ++ ++ input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch"; ++ input_dev->phys = YB9_KBDOCK_QUERY_GUID "/input0"; ++ input_dev->id.bustype = BUS_HOST; ++ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); ++ ++ ret = input_register_device(input_dev); ++ if (ret) ++ return ret; ++ ++ d->input_dev = input_dev; ++ d->nb.notifier_call = yb9_kbdock_nb_call; ++ ++ ret = devm_yb9_kbdock_register_notifier(&wdev->dev, &d->nb); ++ if (ret) ++ return ret; ++ ++ dev_set_drvdata(&wdev->dev, d); ++ return yb9_kbdock_sync(d); ++} ++ ++static const struct wmi_device_id yb9_kbdock_block_id_table[] = { ++ { .guid_string = YB9_KBDOCK_QUERY_GUID }, ++ { } ++}; ++ ++static struct wmi_driver yb9_kbdock_block_driver = { ++ .driver = { ++ .name = "lenovo-yb9-kbdock", ++ .dev_groups = yb9_kbdock_groups, ++ .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops), ++ }, ++ .id_table = yb9_kbdock_block_id_table, ++ .no_singleton = true, ++ .probe = yb9_kbdock_block_probe, ++}; ++ ++/* ------------------------------------------------------------------ ++ * Event WMI driver — LENOVO_BTKBD_EVENT ++ * (fires the notifier chain on each WMI event) ++ * ------------------------------------------------------------------ */ ++ ++static void yb9_kbdock_notify_new(struct wmi_device *wdev, ++ const struct wmi_buffer *data) ++{ ++ /* ++ * _WED(0xEB) returns EC.BKBD directly as a 32-bit integer ++ * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status). ++ * Short-buffer guard is handled by .min_event_size below. ++ */ ++ u32 bkbd = le32_to_cpu(*(const __le32 *)data->data); ++ ++ blocking_notifier_call_chain(&yb9_kbdock_chain_head, bkbd, NULL); ++} ++ ++static const struct wmi_device_id yb9_kbdock_event_id_table[] = { ++ { .guid_string = YB9_KBDOCK_EVENT_GUID }, ++ { } ++}; ++MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table); ++ ++static struct wmi_driver yb9_kbdock_event_driver = { ++ .driver = { ++ .name = "lenovo-yb9-kbdock-event", ++ }, ++ .id_table = yb9_kbdock_event_id_table, ++ .no_singleton = true, ++ .notify_new = yb9_kbdock_notify_new, ++ .min_event_size = sizeof(__le32), ++}; ++ ++/* ------------------------------------------------------------------ ++ * Module init / exit ++ * ------------------------------------------------------------------ */ ++ ++static const struct dmi_system_id yb9_kbdock_dmi_table[] __initconst = { ++ { ++ /* Lenovo Yoga Book 9 14IAH10 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), ++ }, ++ }, ++ { } ++}; ++ ++static int __init yb9_kbdock_init(void) ++{ ++ int ret; ++ ++ if (!dmi_check_system(yb9_kbdock_dmi_table)) ++ return -ENODEV; ++ ++ ret = wmi_driver_register(&yb9_kbdock_event_driver); ++ if (ret) ++ return ret; ++ ++ ret = wmi_driver_register(&yb9_kbdock_block_driver); ++ if (ret) { ++ wmi_driver_unregister(&yb9_kbdock_event_driver); ++ return ret; ++ } ++ ++ return 0; ++} ++module_init(yb9_kbdock_init); ++ ++static void __exit yb9_kbdock_exit(void) ++{ ++ wmi_driver_unregister(&yb9_kbdock_block_driver); ++ wmi_driver_unregister(&yb9_kbdock_event_driver); ++} ++module_exit(yb9_kbdock_exit); ++ ++MODULE_AUTHOR("Dave Carey "); ++MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c +--- a/drivers/platform/x86/lenovo/ymc.c ++++ b/drivers/platform/x86/lenovo/ymc.c +@@ -28,6 +28,22 @@ static bool force; + module_param(force, bool, 0444); + MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type"); + ++static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = { ++ { ++ /* ++ * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the ++ * yb9-kbdock driver. Suppress lenovo-ymc on this machine to ++ * avoid userspace seeing two input nodes that both advertise ++ * the SW_TABLET_MODE capability. ++ */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), ++ }, ++ }, ++ { } ++}; ++ + static const struct dmi_system_id allowed_chasis_types_dmi_table[] = { + { + .matches = { +@@ -107,6 +123,9 @@ static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx) + struct input_dev *input_dev; + int err; + ++ if (dmi_check_system(lenovo_ymc_nosupport_dmi_table)) ++ return -ENODEV; ++ + if (!dmi_check_system(allowed_chasis_types_dmi_table)) { + if (force) + dev_info(&wdev->dev, "Force loading Lenovo YMC support\n"); +diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c +--- a/drivers/platform/x86/lg-laptop.c ++++ b/drivers/platform/x86/lg-laptop.c +@@ -8,8 +8,11 @@ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + + #include ++#include + #include + #include ++#include ++#include + #include + #include + #include +@@ -17,10 +20,12 @@ + #include + #include + #include ++#include + #include + #include + #include + #include ++#include + + #include + +@@ -57,13 +62,18 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); + #define LG_ADDRESS_SPACE_DEBUG_MSG_START_ADR 0x3E8 + #define LG_ADDRESS_SPACE_DEBUG_MSG_END_ADR 0x5E8 + +-#define WMI_EVENT_GUID0 "E4FB94F9-7F2B-4173-AD1A-CD1D95086248" +-#define WMI_EVENT_GUID1 "023B133E-49D1-4E10-B313-698220140DC2" +-#define WMI_EVENT_GUID2 "37BE1AC0-C3F2-4B1F-BFBE-8FDEAF2814D6" +-#define WMI_EVENT_GUID3 "911BAD44-7DF8-4FBB-9319-BABA1C4B293B" +-#define WMI_METHOD_WMAB "C3A72B38-D3EF-42D3-8CBB-D5A57049F66D" +-#define WMI_METHOD_WMBB "2B4F501A-BD3C-4394-8DCF-00A7D2BC8210" +-#define WMI_EVENT_GUID WMI_EVENT_GUID0 ++#define LG_NOTIFY_TABLET_MODE_OFF 0x50 ++#define LG_NOTIFY_TABLET_MODE_ON 0x51 ++#define LG_NOTIFY_HOTKEY 0x80 ++#define LG_NOTIFY_THERMAL 0x81 ++#define LG_NOTIFY_MISC 0x82 ++ ++#define LG_OREP_READ_EC 0 ++#define LG_OREP_WRITE_EC 1 ++#define LG_OREP_DEBUG 2 ++#define LG_OREP_UPDATE_SYSTEM_STATE 3 ++#define LG_OREP_INTERCEPT_WMI_EVENTS 4 ++#define LG_OREP_WAKE_ON_LAN 6 + + #define SB_GGOV_METHOD "\\_SB.GGOV" + #define GOV_TLED 0x2020008 +@@ -78,42 +88,95 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); + #define WMBB_USB_CHARGE 0x10B + #define WMBB_BATT_LIMIT 0x10C + ++#define KBD_LED_BRIGHTNESS_MASK GENMASK(4, 0) ++#define KBD_LED_BRIGHTNESS_OFF 0x0 ++#define KBD_LED_BRIGHTNESS_HALF 0x2 ++#define KBD_LED_BRIGHTNESS_FULL 0x4 ++#define KBD_LED_MODE_MASK GENMASK(6, 5) ++#define KBD_LED_MODE_OFF 0x0 ++#define KBD_LED_MODE_ON 0x1 ++#define KBD_LED_STATUS BIT(7) ++#define KBD_LED_MAGIC_MASK GENMASK(15, 8) ++/* Exact purpose is unknown, maybe some sort of brightness limit? */ ++#define KBD_LED_MAGIC 0x05 ++ + #define FAN_MODE_LOWER GENMASK(1, 0) + #define FAN_MODE_UPPER GENMASK(5, 4) + + #define PLATFORM_NAME "lg-laptop" + +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID0); +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID1); +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID2); +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID3); +-MODULE_ALIAS("wmi:" WMI_METHOD_WMAB); +-MODULE_ALIAS("wmi:" WMI_METHOD_WMBB); ++struct lg_wmab_buffer_result { ++ __le32 value; ++ __le32 status; ++} __packed; + + static struct platform_device *pf_device; +-static struct input_dev *wmi_input_dev; +- +-static u32 inited; +-#define INIT_INPUT_WMI_0 0x01 +-#define INIT_INPUT_WMI_2 0x02 +-#define INIT_INPUT_ACPI 0x04 +-#define INIT_SPARSE_KEYMAP 0x80 + + static int battery_limit_use_wmbb; ++static bool kbd_backlight_available; + static struct led_classdev kbd_backlight; + static enum led_brightness get_kbd_backlight_level(struct device *dev); + + static const struct key_entry wmi_keymap[] = { +- {KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */ +- {KE_KEY, 0x74, {KEY_F21} }, /* Touchpad toggle (F5) */ +- {KE_KEY, 0xf020000, {KEY_F14} }, /* Read mode (F9) */ +- {KE_KEY, 0x10000000, {KEY_F16} },/* Keyboard backlight (F8) - pressing +- * this key both sends an event and +- * changes backlight level. +- */ ++ /* Placeholder value send by multiple hotkeys handled by ACPI */ ++ { KE_IGNORE, 0x0, { KEY_UNKNOWN }}, ++ /* LG control panel */ ++ { KE_KEY, 0x70, { KEY_F15 }}, ++ /* Touchpad toggle */ ++ { KE_KEY, 0x74, { KEY_F21 }}, ++ /* Mute Audio, already handled by ACPI */ ++ { KE_IGNORE, 0x78, { KEY_MUTE }}, ++ /* Read mode */ ++ { KE_KEY, 0xf020000, { KEY_F14 }}, ++ /* Open settings */ ++ { KE_KEY, 0xf070002, { KEY_CONFIG }}, ++ /* Keyboard backlight - pressing this key both sends an event and changes backlight level */ ++ { KE_KEY, 0x10000000, { KEY_F16 }}, ++ /* Hotkey combination pressed */ ++ { KE_IGNORE, 0x30010000, { KEY_UNKNOWN }}, ++ /* Hotkey combination released */ ++ { KE_IGNORE, 0x30010001, { KEY_UNKNOWN }}, ++ /* Change fan mode */ ++ { KE_KEY, 0x30010051, { KEY_PERFORMANCE }}, ++ /* Disable camera */ ++ { KE_KEY, 0x40020000, { KEY_CAMERA_ACCESS_TOGGLE }}, ++ /* Mute microphone */ ++ { KE_KEY, 0x40020001, { KEY_MICMUTE }}, ++ /* Fn-Lock */ ++ { KE_KEY, 0x40030001, { KEY_FN_ESC }}, + {KE_END, 0} + }; + ++static int lg_laptop_execute_orep(acpi_handle handle, u64 command, u64 value, ++ unsigned long long *result) ++{ ++ union acpi_object objs[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = command, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = value, ++ }, ++ } ++ }; ++ struct acpi_object_list args = { ++ .count = ARRAY_SIZE(objs), ++ .pointer = objs, ++ }; ++ acpi_status status; ++ ++ status = acpi_evaluate_integer(handle, "OREP", &args, result); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ return 0; ++} ++ + static int ggov(u32 arg0) + { + union acpi_object args[1]; +@@ -154,30 +217,97 @@ static int ggov(u32 arg0) + return res; + } + +-static union acpi_object *lg_wmab(struct device *dev, u32 method, u32 arg1, u32 arg2) ++static int lg_wmab_get(struct device *dev, u32 method, u32 *result) + { +- union acpi_object args[3]; +- acpi_status status; +- struct acpi_object_list arg; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; ++ union acpi_object in[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = method, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = WM_GET, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = 0, ++ }, ++ }, ++ }; ++ struct lg_wmab_buffer_result *buffer_result; ++ struct acpi_object_list input = { ++ .count = ARRAY_SIZE(in), ++ .pointer = in, ++ }; ++ acpi_status status; + +- args[0].type = ACPI_TYPE_INTEGER; +- args[0].integer.value = method; +- args[1].type = ACPI_TYPE_INTEGER; +- args[1].integer.value = arg1; +- args[2].type = ACPI_TYPE_INTEGER; +- args[2].integer.value = arg2; ++ status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, &buffer); ++ if (ACPI_FAILURE(status)) ++ return -EIO; + +- arg.count = 3; +- arg.pointer = args; ++ union acpi_object *obj __free(kfree) = buffer.pointer; + +- status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &arg, &buffer); +- if (ACPI_FAILURE(status)) { +- dev_err(dev, "WMAB: call failed.\n"); +- return NULL; ++ if (!obj) ++ return -ENODATA; ++ ++ switch (obj->type) { ++ case ACPI_TYPE_INTEGER: ++ *result = obj->integer.value; ++ return 0; ++ case ACPI_TYPE_BUFFER: ++ if (obj->buffer.length != sizeof(*buffer_result)) ++ return -EPROTO; ++ ++ buffer_result = (struct lg_wmab_buffer_result *)obj->buffer.pointer; ++ if (get_unaligned_le32(&buffer_result->status)) ++ return -EIO; ++ ++ *result = get_unaligned_le32(&buffer_result->value); ++ return 0; ++ default: ++ return -EPROTO; + } ++} + +- return buffer.pointer; ++static int lg_wmab_set(struct device *dev, u32 method, u32 arg) ++{ ++ union acpi_object in[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = method, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = WM_SET, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = arg, ++ }, ++ }, ++ }; ++ struct acpi_object_list input = { ++ .count = ARRAY_SIZE(in), ++ .pointer = in, ++ }; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, NULL); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ return 0; + } + + static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u32 arg2) +@@ -211,72 +341,11 @@ static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u + return (union acpi_object *)buffer.pointer; + } + +-static void wmi_notify(union acpi_object *obj, void *context) +-{ +- long data = (long)context; +- +- pr_debug("event guid %li\n", data); +- if (!obj) +- return; +- +- if (obj->type == ACPI_TYPE_INTEGER) { +- int eventcode = obj->integer.value; +- struct key_entry *key; +- +- if (eventcode == 0x10000000) { +- led_classdev_notify_brightness_hw_changed( +- &kbd_backlight, get_kbd_backlight_level(kbd_backlight.dev->parent)); +- } else { +- key = sparse_keymap_entry_from_scancode( +- wmi_input_dev, eventcode); +- if (key && key->type == KE_KEY) +- sparse_keymap_report_entry(wmi_input_dev, +- key, 1, true); +- } +- } +- +- pr_debug("Type: %i Eventcode: 0x%llx\n", obj->type, +- obj->integer.value); +-} +- +-static void wmi_input_setup(void) +-{ +- acpi_status status; +- +- wmi_input_dev = input_allocate_device(); +- if (wmi_input_dev) { +- wmi_input_dev->name = "LG WMI hotkeys"; +- wmi_input_dev->phys = "wmi/input0"; +- wmi_input_dev->id.bustype = BUS_HOST; +- +- if (sparse_keymap_setup(wmi_input_dev, wmi_keymap, NULL) || +- input_register_device(wmi_input_dev)) { +- pr_info("Cannot initialize input device"); +- input_free_device(wmi_input_dev); +- return; +- } +- +- inited |= INIT_SPARSE_KEYMAP; +- status = wmi_install_notify_handler(WMI_EVENT_GUID0, wmi_notify, +- (void *)0); +- if (ACPI_SUCCESS(status)) +- inited |= INIT_INPUT_WMI_0; +- +- status = wmi_install_notify_handler(WMI_EVENT_GUID2, wmi_notify, +- (void *)2); +- if (ACPI_SUCCESS(status)) +- inited |= INIT_INPUT_WMI_2; +- } else { +- pr_info("Cannot allocate input device"); +- } +-} +- + static ssize_t fan_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) + { + unsigned long value; +- union acpi_object *r; + int ret; + + ret = kstrtoul(buffer, 10, &value); +@@ -285,10 +354,10 @@ static ssize_t fan_mode_store(struct device *dev, + if (value >= 3) + return -EINVAL; + +- r = lg_wmab(dev, WM_FAN_MODE, WM_SET, +- FIELD_PREP(FAN_MODE_LOWER, value) | +- FIELD_PREP(FAN_MODE_UPPER, value)); +- kfree(r); ++ ret = lg_wmab_set(dev, WM_FAN_MODE, FIELD_PREP(FAN_MODE_LOWER, value) | ++ FIELD_PREP(FAN_MODE_UPPER, value)); ++ if (ret < 0) ++ return ret; + + return count; + } +@@ -296,22 +365,14 @@ static ssize_t fan_mode_store(struct device *dev, + static ssize_t fan_mode_show(struct device *dev, + struct device_attribute *attr, char *buffer) + { +- unsigned int mode; +- union acpi_object *r; ++ u32 mode; ++ int ret; + +- r = lg_wmab(dev, WM_FAN_MODE, WM_GET, 0); +- if (!r) +- return -EIO; ++ ret = lg_wmab_get(dev, WM_FAN_MODE, &mode); ++ if (ret < 0) ++ return ret; + +- if (r->type != ACPI_TYPE_INTEGER) { +- kfree(r); +- return -EIO; +- } +- +- mode = FIELD_GET(FAN_MODE_LOWER, r->integer.value); +- kfree(r); +- +- return sysfs_emit(buffer, "%d\n", mode); ++ return sysfs_emit(buffer, "%lu\n", FIELD_GET(FAN_MODE_LOWER, mode)); + } + + static ssize_t usb_charge_store(struct device *dev, +@@ -361,41 +422,30 @@ static ssize_t reader_mode_store(struct device *dev, + const char *buffer, size_t count) + { + bool value; +- union acpi_object *r; + int ret; + + ret = kstrtobool(buffer, &value); + if (ret) + return ret; + +- r = lg_wmab(dev, WM_READER_MODE, WM_SET, value); +- if (!r) +- return -EIO; ++ ret = lg_wmab_set(dev, WM_READER_MODE, value); ++ if (ret < 0) ++ return ret; + +- kfree(r); + return count; + } + + static ssize_t reader_mode_show(struct device *dev, + struct device_attribute *attr, char *buffer) + { +- unsigned int status; +- union acpi_object *r; ++ u32 status; ++ int ret; + +- r = lg_wmab(dev, WM_READER_MODE, WM_GET, 0); +- if (!r) +- return -EIO; ++ ret = lg_wmab_get(dev, WM_READER_MODE, &status); ++ if (ret < 0) ++ return ret; + +- if (r->type != ACPI_TYPE_INTEGER) { +- kfree(r); +- return -EIO; +- } +- +- status = !!r->integer.value; +- +- kfree(r); +- +- return sysfs_emit(buffer, "%d\n", status); ++ return sysfs_emit(buffer, "%d\n", !!status); + } + + static ssize_t fn_lock_store(struct device *dev, +@@ -403,40 +453,30 @@ static ssize_t fn_lock_store(struct device *dev, + const char *buffer, size_t count) + { + bool value; +- union acpi_object *r; + int ret; + + ret = kstrtobool(buffer, &value); + if (ret) + return ret; + +- r = lg_wmab(dev, WM_FN_LOCK, WM_SET, value); +- if (!r) +- return -EIO; ++ ret = lg_wmab_set(dev, WM_FN_LOCK, value); ++ if (ret < 0) ++ return ret; + +- kfree(r); + return count; + } + + static ssize_t fn_lock_show(struct device *dev, + struct device_attribute *attr, char *buffer) + { +- unsigned int status; +- union acpi_object *r; ++ u32 status; ++ int ret; + +- r = lg_wmab(dev, WM_FN_LOCK, WM_GET, 0); +- if (!r) +- return -EIO; ++ ret = lg_wmab_get(dev, WM_FN_LOCK, &status); ++ if (ret < 0) ++ return ret; + +- if (r->type != ACPI_TYPE_BUFFER) { +- kfree(r); +- return -EIO; +- } +- +- status = !!r->buffer.pointer[0]; +- kfree(r); +- +- return sysfs_emit(buffer, "%d\n", status); ++ return sysfs_emit(buffer, "%d\n", !!status); + } + + static ssize_t charge_control_end_threshold_store(struct device *dev, +@@ -453,14 +493,18 @@ static ssize_t charge_control_end_threshold_store(struct device *dev, + if (value == 100 || value == 80) { + union acpi_object *r; + +- if (battery_limit_use_wmbb) ++ if (battery_limit_use_wmbb) { + r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_SET, value); +- else +- r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_SET, value); +- if (!r) +- return -EIO; ++ if (!r) ++ return -EIO; ++ ++ kfree(r); ++ } else { ++ ret = lg_wmab_set(&pf_device->dev, WM_BATT_LIMIT, value); ++ if (ret < 0) ++ return ret; ++ } + +- kfree(r); + return count; + } + +@@ -471,8 +515,9 @@ static ssize_t charge_control_end_threshold_show(struct device *device, + struct device_attribute *attr, + char *buf) + { +- unsigned int status; + union acpi_object *r; ++ u32 status; ++ int ret; + + if (battery_limit_use_wmbb) { + r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_GET, 0); +@@ -485,19 +530,13 @@ static ssize_t charge_control_end_threshold_show(struct device *device, + } + + status = r->buffer.pointer[0x10]; ++ kfree(r); + } else { +- r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_GET, 0); +- if (!r) +- return -EIO; +- +- if (r->type != ACPI_TYPE_INTEGER) { +- kfree(r); +- return -EIO; +- } +- +- status = r->integer.value; ++ ret = lg_wmab_get(&pf_device->dev, WM_BATT_LIMIT, &status); ++ if (ret < 0) ++ return ret; + } +- kfree(r); ++ + if (status != 80 && status != 100) + status = 0; + +@@ -563,10 +602,7 @@ static const struct attribute_group dev_attribute_group = { + static void tpad_led_set(struct led_classdev *cdev, + enum led_brightness brightness) + { +- union acpi_object *r; +- +- r = lg_wmab(cdev->dev->parent, WM_TLED, WM_SET, brightness > LED_OFF); +- kfree(r); ++ lg_wmab_set(cdev->dev->parent, WM_TLED, brightness > LED_OFF); + } + + static enum led_brightness tpad_led_get(struct led_classdev *cdev) +@@ -579,47 +615,50 @@ static LED_DEVICE(tpad_led, 1, 0); + static void kbd_backlight_set(struct led_classdev *cdev, + enum led_brightness brightness) + { +- u32 val; +- union acpi_object *r; ++ /* Must always be written */ ++ u32 value = KBD_LED_STATUS; ++ u32 mode, bright; + +- val = 0x22; +- if (brightness <= LED_OFF) +- val = 0; +- if (brightness >= LED_FULL) +- val = 0x24; +- r = lg_wmab(cdev->dev->parent, WM_KEY_LIGHT, WM_SET, val); +- kfree(r); ++ if (brightness <= LED_OFF) { ++ mode = KBD_LED_MODE_OFF; ++ bright = KBD_LED_BRIGHTNESS_OFF; ++ } else { ++ mode = KBD_LED_MODE_ON; ++ if (brightness >= LED_FULL) ++ bright = KBD_LED_BRIGHTNESS_FULL; ++ else ++ bright = KBD_LED_BRIGHTNESS_HALF; ++ } ++ ++ value |= FIELD_PREP(KBD_LED_BRIGHTNESS_MASK, bright); ++ value |= FIELD_PREP(KBD_LED_MODE_MASK, mode); ++ ++ lg_wmab_set(cdev->dev->parent, WM_KEY_LIGHT, value); + } + + static enum led_brightness get_kbd_backlight_level(struct device *dev) + { +- union acpi_object *r; +- int val; ++ u32 value; ++ int ret; + +- r = lg_wmab(dev, WM_KEY_LIGHT, WM_GET, 0); +- +- if (!r) ++ ret = lg_wmab_get(dev, WM_KEY_LIGHT, &value); ++ if (ret < 0) + return LED_OFF; + +- if (r->type != ACPI_TYPE_BUFFER || r->buffer.pointer[1] != 0x05) { +- kfree(r); ++ if (FIELD_GET(KBD_LED_MAGIC_MASK, value) != KBD_LED_MAGIC) + return LED_OFF; +- } + +- switch (r->buffer.pointer[0] & 0x27) { +- case 0x24: +- val = LED_FULL; +- break; +- case 0x22: +- val = LED_HALF; +- break; ++ if (FIELD_GET(KBD_LED_MODE_MASK, value) == KBD_LED_MODE_OFF) ++ return LED_OFF; ++ ++ switch (FIELD_GET(KBD_LED_BRIGHTNESS_MASK, value)) { ++ case KBD_LED_BRIGHTNESS_FULL: ++ return LED_FULL; ++ case KBD_LED_BRIGHTNESS_HALF: ++ return LED_HALF; + default: +- val = LED_OFF; ++ return LED_OFF; + } +- +- kfree(r); +- +- return val; + } + + static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) +@@ -629,26 +668,163 @@ static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) + + static LED_DEVICE(kbd_backlight, 255, LED_BRIGHT_HW_CHANGED); + +-static void wmi_input_destroy(void) +-{ +- if (inited & INIT_INPUT_WMI_2) +- wmi_remove_notify_handler(WMI_EVENT_GUID2); +- +- if (inited & INIT_INPUT_WMI_0) +- wmi_remove_notify_handler(WMI_EVENT_GUID0); +- +- if (inited & INIT_SPARSE_KEYMAP) +- input_unregister_device(wmi_input_dev); +- +- inited &= ~(INIT_INPUT_WMI_0 | INIT_INPUT_WMI_2 | INIT_SPARSE_KEYMAP); +-} +- + static struct platform_driver pf_driver = { + .driver = { + .name = PLATFORM_NAME, + } + }; + ++static int lg_laptop_get_event_data(acpi_handle handle, u32 value, u32 *data) ++{ ++ union acpi_object objs[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = value, ++ }, ++ } ++ }; ++ struct acpi_object_list args = { ++ .count = ARRAY_SIZE(objs), ++ .pointer = objs, ++ }; ++ unsigned long long result; ++ acpi_status status; ++ ++ status = acpi_evaluate_integer(handle, "_WED", &args, &result); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ if (result > U32_MAX) ++ return -EPROTO; ++ ++ *data = result; ++ ++ return 0; ++} ++ ++static void lg_laptop_handle_input_event(struct input_dev *input_dev, u32 value, u32 data) ++{ ++ unsigned int kbd_brightness; ++ ++ switch (value) { ++ case LG_NOTIFY_HOTKEY: ++ sparse_keymap_report_event(input_dev, data, 1, true); ++ break; ++ case LG_NOTIFY_THERMAL: ++ /* Currently not supported */ ++ break; ++ case LG_NOTIFY_MISC: ++ switch (data) { ++ case 0x10000000: ++ if (!kbd_backlight_available) ++ break; ++ ++ kbd_brightness = get_kbd_backlight_level(kbd_backlight.dev->parent); ++ led_classdev_notify_brightness_hw_changed(&kbd_backlight, kbd_brightness); ++ break; ++ default: ++ sparse_keymap_report_event(input_dev, data, 1, true); ++ } ++ break; ++ default: ++ break; ++ } ++} ++ ++static void lg_laptop_notify_handler(acpi_handle handle, u32 value, void *context) ++{ ++ struct input_dev *input_dev = context; ++ u32 data; ++ int ret; ++ ++ switch (value) { ++ case LG_NOTIFY_TABLET_MODE_OFF: ++ case LG_NOTIFY_TABLET_MODE_ON: ++ /* Already handled by intel-hid */ ++ return; ++ case LG_NOTIFY_HOTKEY: ++ case LG_NOTIFY_THERMAL: ++ case LG_NOTIFY_MISC: ++ ret = lg_laptop_get_event_data(handle, value, &data); ++ if (ret < 0) { ++ dev_notice(input_dev->dev.parent, "Failed to get event data: %d\n", ret); ++ return; ++ } ++ ++ dev_dbg(input_dev->dev.parent, "Received event %u (%u)\n", value, data); ++ ++ lg_laptop_handle_input_event(input_dev, value, data); ++ return; ++ default: ++ dev_notice(input_dev->dev.parent, "Received unknown event %u\n", value); ++ } ++}; ++ ++static void lg_laptop_remove_notify_handler(void *context) ++{ ++ acpi_handle handle = context; ++ ++ acpi_remove_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler); ++} ++ ++static void lg_laptop_reenable_wmi_events(void *context) ++{ ++ acpi_handle handle = context; ++ unsigned long long dummy; ++ ++ lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 0, &dummy); ++} ++ ++static int lg_laptop_input_init(struct device *dev, acpi_handle handle) ++{ ++ struct input_dev *input_dev; ++ unsigned long long result; ++ acpi_status status; ++ int ret; ++ ++ if (!acpi_has_method(handle, "_WED")) ++ return 0; ++ ++ input_dev = devm_input_allocate_device(dev); ++ if (!input_dev) ++ return -ENOMEM; ++ ++ input_dev->name = "LG WMI hotkeys"; ++ input_dev->phys = "wmi/input0"; ++ input_dev->id.bustype = BUS_HOST; ++ ret = sparse_keymap_setup(input_dev, wmi_keymap, NULL); ++ if (ret < 0) ++ return ret; ++ ++ ret = input_register_device(input_dev); ++ if (ret < 0) ++ return ret; ++ ++ status = acpi_install_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler, ++ input_dev); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ ret = devm_add_action_or_reset(dev, lg_laptop_remove_notify_handler, handle); ++ if (ret < 0) ++ return ret; ++ ++ if (acpi_has_method(handle, "OREP")) { ++ ret = lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 1, &result); ++ if (ret < 0) ++ return ret; ++ if (result) ++ return -EIO; ++ ++ ret = devm_add_action_or_reset(dev, lg_laptop_reenable_wmi_events, handle); ++ if (ret < 0) ++ return ret; ++ } ++ ++ return 0; ++} ++ + static acpi_status lg_laptop_address_space_write(struct device *dev, acpi_physical_address address, + size_t size, u64 value) + { +@@ -860,15 +1036,23 @@ static int acpi_probe(struct platform_device *pdev) + if (year >= 2019) + battery_limit_use_wmbb = 1; + ++ /* LEDs are optional */ ++ ret = devm_led_classdev_register(&pdev->dev, &kbd_backlight); ++ if (ret < 0) ++ kbd_backlight_available = false; ++ else ++ kbd_backlight_available = true; ++ ++ devm_led_classdev_register(&pdev->dev, &tpad_led); ++ ++ ret = lg_laptop_input_init(&pdev->dev, device->handle); ++ if (ret < 0) ++ goto out_platform_device; ++ + ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); + if (ret) + goto out_platform_device; + +- /* LEDs are optional */ +- led_classdev_register(&pf_device->dev, &kbd_backlight); +- led_classdev_register(&pf_device->dev, &tpad_led); +- +- wmi_input_setup(); + battery_hook_register(&battery_hook); + + return 0; +@@ -884,11 +1068,7 @@ static void acpi_remove(struct platform_device *pdev) + { + sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group); + +- led_classdev_unregister(&tpad_led); +- led_classdev_unregister(&kbd_backlight); +- + battery_hook_unregister(&battery_hook); +- wmi_input_destroy(); + platform_device_unregister(pf_device); + pf_device = NULL; + platform_driver_unregister(&pf_driver); +diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c +--- a/drivers/platform/x86/msi-ec.c ++++ b/drivers/platform/x86/msi-ec.c +@@ -1055,6 +1055,7 @@ static struct msi_ec_conf CONF12 __initdata = { + + static const char * const ALLOWED_FW_13[] __initconst = { + "1594EMS1.109", // MSI Prestige 16 Studio A13VE ++ "17L1EMS1.107", // MSI Katana GF76 11UEK + NULL + }; + +@@ -1130,6 +1131,86 @@ static struct msi_ec_conf CONF13 __initdata = { + }, + }; + ++static const char * const ALLOWED_FW_14[] __initconst = { ++ "1824EMS1.108", // Raider 18 HX AI A2XWJG (MS-1824) ++ "182LIMS1.108", // Vector A18 HX A9WHG ++ "182LIMS1.111", // MSI Raider A18 HX A9WJG / Vector A18 HX A9WHG ++ "182KIMS1.113", // Raider A18 HX A7VIG ++ NULL ++}; ++ ++static struct msi_ec_conf CONF14 __initdata = { ++ .allowed_fw = ALLOWED_FW_14, ++ .charge_control = { ++ .address = 0xd7, ++ .offset_start = 0x8a, ++ .offset_end = 0x80, ++ .range_min = 0x8a, ++ .range_max = 0xe4, ++ }, ++ .webcam = { ++ .address = 0x2e, ++ .block_address = 0x2f, ++ .bit = 1, ++ }, ++ .fn_win_swap = { ++ .address = 0xe8, ++ .bit = 4, ++ }, ++ .cooler_boost = { ++ .address = 0x98, ++ .bit = 7, ++ }, ++ .shift_mode = { ++ .address = 0xd2, ++ .modes = { ++ { SM_ECO_NAME, 0xc2 }, ++ { SM_COMFORT_NAME, 0xc1 }, ++ { SM_TURBO_NAME, 0xc4 }, ++ MSI_EC_MODE_NULL ++ }, ++ }, ++ .super_battery = { ++ .address = 0xeb, ++ .mask = 0x0f, ++ }, ++ .fan_mode = { ++ .address = 0xd4, ++ .modes = { ++ { FM_AUTO_NAME, 0x0d }, ++ { FM_SILENT_NAME, 0x1d }, ++ { FM_ADVANCED_NAME, 0x8d }, ++ MSI_EC_MODE_NULL ++ }, ++ }, ++ .cpu = { ++ .rt_temp_address = 0x68, ++ .rt_fan_speed_address = 0x71, ++ .rt_fan_speed_base_min = 0x00, ++ .rt_fan_speed_base_max = 0x96, ++ .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, ++ .bs_fan_speed_base_min = 0x00, ++ .bs_fan_speed_base_max = 0x0f, ++ }, ++ .gpu = { ++ .rt_temp_address = 0x80, ++ .rt_fan_speed_address = 0x89, ++ }, ++ .leds = { ++ .micmute_led_address = 0x2c, ++ .mute_led_address = 0x2d, ++ .bit = 1, ++ }, ++ .kbd_bl = { ++ .bl_mode_address = MSI_EC_ADDR_UNSUPP, ++ .bl_modes = { 0x00, 0x08 }, ++ .max_mode = 1, ++ .bl_state_address = MSI_EC_ADDR_UNSUPP, ++ .state_base_value = 0x80, ++ .max_state = 3, ++ }, ++}; ++ + static struct msi_ec_conf *CONFIGS[] __initdata = { + &CONF0, + &CONF1, +@@ -1145,6 +1226,7 @@ static struct msi_ec_conf *CONFIGS[] __initdata = { + &CONF11, + &CONF12, + &CONF13, ++ &CONF14, + NULL + }; + +diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c +--- a/drivers/platform/x86/msi-wmi.c ++++ b/drivers/platform/x86/msi-wmi.c +@@ -46,6 +46,12 @@ enum msi_scancodes { + WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */ + WIND_KEY_TURBO, /* Fn+F10 turbo mode toggle */ + WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */ ++ /* MSI Claw keys */ ++ CLAW_KEY_VOLUMEDOWN = 0x21, ++ CLAW_KEY_CENTER = 0x29, /* MSI M-Center main menu */ ++ CLAW_KEY_QUICK_LONG = 0x2a, /* MSI M-Center quick access long hold */ ++ CLAW_KEY_VOLUMEUP = 0x32, ++ CLAW_KEY_QUICK_SHORT = 0x58, /* MSI M-Center quick access short press */ + }; + static struct key_entry msi_wmi_keymap[] = { + { KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, +@@ -69,6 +75,15 @@ static struct key_entry msi_wmi_keymap[] = { + { KE_KEY, WIND_KEY_TURBO, {KEY_PROG1} }, + { KE_KEY, WIND_KEY_ECO, {KEY_PROG2} }, + ++ /* These are MSI Claw keys, used for MSI M-Center in Windows */ ++ { KE_KEY, CLAW_KEY_CENTER, {KEY_F15} }, ++ ++ /* These MSI Claw keys work without WMI. Ignore them to avoid double keycodes */ ++ { KE_IGNORE, CLAW_KEY_QUICK_SHORT }, ++ { KE_IGNORE, CLAW_KEY_QUICK_LONG }, ++ { KE_IGNORE, CLAW_KEY_VOLUMEUP }, ++ { KE_IGNORE, CLAW_KEY_VOLUMEDOWN }, ++ + { KE_END, 0 } + }; + +@@ -172,44 +187,62 @@ static const struct backlight_ops msi_backlight_ops = { + + static void msi_wmi_notify(union acpi_object *obj, void *context) + { +- struct key_entry *key; ++ struct key_entry *key = NULL; ++ int eventcode = 0; + +- if (obj && obj->type == ACPI_TYPE_INTEGER) { +- int eventcode = obj->integer.value; ++ if (!obj) ++ return; ++ ++ switch (obj->type) { ++ case ACPI_TYPE_INTEGER: ++ eventcode = obj->integer.value; + pr_debug("Eventcode: 0x%x\n", eventcode); +- key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, +- eventcode); +- if (!key) { +- pr_info("Unknown key pressed - %x\n", eventcode); ++ break; ++ case ACPI_TYPE_BUFFER: ++ /* Field returns u8[2] here, but is u32 by spec. Allow "oversized" buffers. */ ++ if (obj->buffer.length < 2) ++ return; ++ ++ /* pointer[0] is key ID, pointer[1] is active state. We don't get release ++ * events, so ignore the active state and treat as autorelease. ++ */ ++ eventcode = obj->buffer.pointer[0]; ++ pr_debug("Eventcode: 0x%x\n", eventcode); ++ break; ++ default: ++ pr_info("Unknown event received\n"); ++ return; ++ } ++ ++ key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, eventcode); ++ ++ if (!key) { ++ pr_info("Unknown key pressed - 0x%x\n", eventcode); ++ return; ++ } ++ ++ if (event_wmi->quirk_last_pressed) { ++ ktime_t cur = ktime_get_real(); ++ ktime_t diff = ktime_sub(cur, last_pressed); ++ /* Ignore event if any event happened in a 50 ms ++ * timeframe -> Key press may result in 10-20 GPEs ++ */ ++ if (ktime_to_us(diff) < 1000 * 50) { ++ pr_debug("Suppressed key event 0x%X - Last press was %lld us ago\n", ++ key->code, ktime_to_us(diff)); + return; + } ++ last_pressed = cur; ++ } + +- if (event_wmi->quirk_last_pressed) { +- ktime_t cur = ktime_get_real(); +- ktime_t diff = ktime_sub(cur, last_pressed); +- /* Ignore event if any event happened in a 50 ms +- timeframe -> Key press may result in 10-20 GPEs */ +- if (ktime_to_us(diff) < 1000 * 50) { +- pr_debug("Suppressed key event 0x%X - " +- "Last press was %lld us ago\n", +- key->code, ktime_to_us(diff)); +- return; +- } +- last_pressed = cur; +- } ++ /* Brightness is served via acpi video driver */ ++ if (key->type == KE_KEY && ++ (backlight || (key->code == MSI_KEY_BRIGHTNESSUP || ++ key->code == MSI_KEY_BRIGHTNESSDOWN))) ++ return; + +- if (key->type == KE_KEY && +- /* Brightness is served via acpi video driver */ +- (backlight || +- (key->code != MSI_KEY_BRIGHTNESSUP && +- key->code != MSI_KEY_BRIGHTNESSDOWN))) { +- pr_debug("Send key: 0x%X - Input layer keycode: %d\n", +- key->code, key->keycode); +- sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, +- true); +- } +- } else +- pr_info("Unknown event received\n"); ++ pr_debug("Send key: 0x%X - Input layer keycode: %d\n", key->code, key->keycode); ++ sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, true); + } + + static int __init msi_wmi_backlight_setup(void) +diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c +--- a/drivers/platform/x86/oxpec.c ++++ b/drivers/platform/x86/oxpec.c +@@ -289,6 +289,13 @@ static const struct dmi_system_id dmi_table[] = { + }, + .driver_data = (void *)oxp_x1, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), ++ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X2Mini PRO"), ++ }, ++ .driver_data = (void *)oxp_fly, ++ }, + {}, + }; + +diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c +--- a/drivers/platform/x86/panasonic-laptop.c ++++ b/drivers/platform/x86/panasonic-laptop.c +@@ -159,8 +159,6 @@ MODULE_LICENSE("GPL"); + #define ECO_MODE_ON 0x80 + + #define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support" +-#define ACPI_PCC_DEVICE_NAME "Hotkey" +-#define ACPI_PCC_CLASS "pcc" + + #define ACPI_PCC_INPUT_PHYS "panasonic/hkey0" + +@@ -1026,8 +1024,6 @@ static int acpi_pcc_hotkey_probe(struct platform_device *pdev) + pcc->device = device; + pcc->handle = device->handle; + device->driver_data = pcc; +- strscpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_PCC_CLASS); + + result = acpi_pcc_init_input(pcc); + if (result) { +diff --git a/drivers/platform/x86/redmi-wmi.c b/drivers/platform/x86/redmi-wmi.c +--- a/drivers/platform/x86/redmi-wmi.c ++++ b/drivers/platform/x86/redmi-wmi.c +@@ -29,24 +29,24 @@ static const struct key_entry redmi_wmi_keymap[] = { + {KE_KEY, 0x00011801, {KEY_ASSISTANT}}, + {KE_KEY, 0x00011901, {KEY_ASSISTANT}}, + +- /* Keyboard backlight */ +- {KE_IGNORE, 0x00000501, {}}, +- {KE_IGNORE, 0x00800501, {}}, +- {KE_IGNORE, 0x00050501, {}}, +- {KE_IGNORE, 0x000a0501, {}}, ++ /* Keyboard backlight: Off / Auto / Low / High (new state in byte 2) */ ++ {KE_KEY, 0x00000501, {KEY_KBDILLUMTOGGLE}}, ++ {KE_KEY, 0x00800501, {KEY_KBDILLUMTOGGLE}}, ++ {KE_KEY, 0x00050501, {KEY_KBDILLUMTOGGLE}}, ++ {KE_KEY, 0x000a0501, {KEY_KBDILLUMTOGGLE}}, + + /* Xiaomi G Command Center */ + {KE_KEY, 0x00010a01, {KEY_VENDOR}}, + +- /* OEM preset power mode */ +- {KE_IGNORE, 0x00011601, {}}, +- {KE_IGNORE, 0x00021601, {}}, +- {KE_IGNORE, 0x00031601, {}}, +- {KE_IGNORE, 0x00041601, {}}, ++ /* OEM preset power mode: 1=Balanced 2=Silent 3=Turbo 4=Full speed */ ++ {KE_KEY, 0x00011601, {KEY_PERFORMANCE}}, ++ {KE_KEY, 0x00021601, {KEY_PERFORMANCE}}, ++ {KE_KEY, 0x00031601, {KEY_PERFORMANCE}}, ++ {KE_KEY, 0x00041601, {KEY_PERFORMANCE}}, + +- /* Fn Lock state */ +- {KE_IGNORE, 0x00000701, {}}, +- {KE_IGNORE, 0x00010701, {}}, ++ /* Fn Lock state: 1=on 0=off */ ++ {KE_KEY, 0x00000701, {KEY_FN_ESC}}, ++ {KE_KEY, 0x00010701, {KEY_FN_ESC}}, + + /* Fn+`/1/2/3/4 */ + {KE_KEY, 0x00011101, {KEY_F13}}, +diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c +--- a/drivers/platform/x86/samsung-galaxybook.c ++++ b/drivers/platform/x86/samsung-galaxybook.c +@@ -1438,6 +1438,7 @@ static const struct acpi_device_id galaxybook_device_ids[] = { + { "SAM0428" }, + { "SAM0429" }, + { "SAM0430" }, ++ { "SAMB430" }, + {} + }; + MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids); +diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c +--- a/drivers/platform/x86/sony-laptop.c ++++ b/drivers/platform/x86/sony-laptop.c +@@ -1264,7 +1264,7 @@ static void sony_nc_notify(acpi_handle ah, u32 event, void *data) + ev_type = HOTKEY; + sony_laptop_report_input_event(real_ev); + } +- acpi_bus_generate_netlink_event(sony_nc_acpi_device->pnp.device_class, ++ acpi_bus_generate_netlink_event("sony/hotkey", + dev_name(&sony_nc_acpi_device->dev), ev_type, real_ev); + } + +@@ -3157,8 +3157,6 @@ static int sony_nc_probe(struct platform_device *pdev) + return -ENODEV; + + sony_nc_acpi_device = device; +- strscpy(acpi_device_class(device), "sony/hotkey"); +- + sony_nc_acpi_handle = device->handle; + + /* read device status */ +@@ -4523,7 +4521,6 @@ static int sony_pic_probe(struct platform_device *pdev) + return -ENODEV; + + spic_dev.acpi_dev = device; +- strscpy(acpi_device_class(device), "sony/hotkey"); + sony_pic_detect_device_type(&spic_dev); + mutex_init(&spic_dev.lock); + +diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c +--- a/drivers/platform/x86/topstar-laptop.c ++++ b/drivers/platform/x86/topstar-laptop.c +@@ -299,8 +299,6 @@ static int topstar_acpi_probe(struct platform_device *pdev) + + platform_set_drvdata(pdev, topstar); + +- strscpy(acpi_device_name(device), "Topstar TPSACPI"); +- strscpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS); + topstar->device = device; + + err = topstar_acpi_init(topstar); +diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c +--- a/drivers/platform/x86/toshiba_acpi.c ++++ b/drivers/platform/x86/toshiba_acpi.c +@@ -2505,8 +2505,7 @@ static void toshiba_acpi_kbd_bl_work(struct work_struct *work) + LED_FULL : LED_OFF); + + /* Emulate the keyboard backlight event */ +- acpi_bus_generate_netlink_event(toshiba_acpi->acpi_dev->pnp.device_class, +- dev_name(&toshiba_acpi->acpi_dev->dev), ++ acpi_bus_generate_netlink_event("", dev_name(&toshiba_acpi->acpi_dev->dev), + 0x92, 0); + } + +@@ -3250,8 +3249,7 @@ static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *data) + break; + } + +- acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class, +- dev_name(&acpi_dev->dev), ++ acpi_bus_generate_netlink_event("", dev_name(&acpi_dev->dev), + event, (event == 0x80) ? + dev->last_key_event : 0); + } +diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c +--- a/drivers/platform/x86/toshiba_bluetooth.c ++++ b/drivers/platform/x86/toshiba_bluetooth.c +@@ -252,10 +252,8 @@ static int toshiba_bt_rfkill_probe(struct platform_device *pdev) + platform_set_drvdata(pdev, bt_dev); + + result = toshiba_bluetooth_sync_status(bt_dev); +- if (result) { +- kfree(bt_dev); +- return result; +- } ++ if (result) ++ goto err_free_bt_dev; + + bt_dev->rfk = rfkill_alloc("Toshiba Bluetooth", + &pdev->dev, +diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c +--- a/drivers/platform/x86/toshiba_haps.c ++++ b/drivers/platform/x86/toshiba_haps.c +@@ -136,9 +136,7 @@ static void toshiba_haps_notify(acpi_handle handle, u32 event, void *data) + + pr_debug("Received event: 0x%x\n", event); + +- acpi_bus_generate_netlink_event(device->pnp.device_class, +- dev_name(&device->dev), +- event, 0); ++ acpi_bus_generate_netlink_event("", dev_name(&device->dev), event, 0); + } + + static void toshiba_haps_remove(struct platform_device *pdev) +diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c +--- a/drivers/platform/x86/uniwill/uniwill-acpi.c ++++ b/drivers/platform/x86/uniwill/uniwill-acpi.c +@@ -95,6 +95,9 @@ + + #define EC_ADDR_MAIN_FAN_RPM_2 0x0465 + ++#define EC_ADDR_SCREEN_STATUS 0x0466 ++#define SCREEN_SUSPENDED BIT(6) ++ + #define EC_ADDR_SECOND_FAN_RPM_1 0x046C + + #define EC_ADDR_SECOND_FAN_RPM_2 0x046D +@@ -109,9 +112,35 @@ + + #define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7 + ++#define EC_ADDR_OEM_9 0x0726 ++#define AC_AUTO_BOOT_ENABLE BIT(3) ++ + #define EC_ADDR_PROJECT_ID 0x0740 ++#define PROJECT_ID_NONE 0x00 ++#define PROJECT_ID_GI 0x01 ++#define PROJECT_ID_GJ 0x02 ++#define PROJECT_ID_GK 0x03 ++#define PROJECT_ID_GICN 0x04 ++#define PROJECT_ID_GJCN 0x05 ++#define PROJECT_ID_GK5CN_X 0x06 ++#define PROJECT_ID_GK7CN_S 0x07 ++#define PROJECT_ID_GK7CPCS_GK5CQ7Z 0x08 ++#define PROJECT_ID_PF 0x09 ++#define PROJECT_ID_GK5CP_4X_5X_6X 0x0A ++#define PROJECT_ID_IDP 0x0B ++#define PROJECT_ID_IDY_6Y 0x0C ++#define PROJECT_ID_IDY_7Y 0x0D ++#define PROJECT_ID_PF4MU_PF4MN_PF5MU 0x0E ++#define PROJECT_ID_CML_GAMING 0x0F ++#define PROJECT_ID_GK7NXXR 0x10 ++#define PROJECT_ID_GM5MU1Y 0x11 + #define PROJECT_ID_PH4TRX1 0x12 ++#define PROJECT_ID_PH4TUX1 0x13 ++#define PROJECT_ID_PH4TQX1 0x14 + #define PROJECT_ID_PH6TRX1 0x15 ++#define PROJECT_ID_PH6TQXX 0x16 ++#define PROJECT_ID_PHXAXXX 0x17 ++#define PROJECT_ID_PHXPXXX 0x18 + + #define EC_ADDR_AP_OEM 0x0741 + #define ENABLE_MANUAL_CTRL BIT(0) +@@ -214,6 +243,7 @@ + #define FAN_TABLE_OFFICE_MODE BIT(2) + #define FAN_V3 BIT(3) + #define DEFAULT_MODE BIT(4) ++#define ENABLE_CHINA_MODE BIT(6) + + #define EC_ADDR_PL1_SETTING 0x0783 + +@@ -225,11 +255,12 @@ + #define FAN_CURVE_LENGTH 5 + + #define EC_ADDR_KBD_STATUS 0x078C +-#define KBD_WHITE_ONLY BIT(0) // ~single color +-#define KBD_SINGLE_COLOR_OFF BIT(1) ++/* Unreliable on some models, use the device descriptor instead. */ ++#define KBD_WHITE_ONLY BIT(0) ++#define KBD_POWER_OFF BIT(1) + #define KBD_TURBO_LEVEL_MASK GENMASK(3, 2) + #define KBD_APPLY BIT(4) +-#define KBD_BRIGHTNESS GENMASK(7, 5) ++#define KBD_BRIGHTNESS_MASK GENMASK(7, 5) + + #define EC_ADDR_FAN_CTRL 0x078E + #define FAN3P5 BIT(1) +@@ -318,7 +349,9 @@ + #define FAN_TABLE_LENGTH 16 + + #define LED_CHANNELS 3 +-#define LED_MAX_BRIGHTNESS 200 ++ ++#define KBD_LED_CHANNELS 3 ++#define KBD_LED_MAX_INTENSITY 50 + + #define UNIWILL_FEATURE_FN_LOCK BIT(0) + #define UNIWILL_FEATURE_SUPER_KEY BIT(1) +@@ -333,6 +366,9 @@ + #define UNIWILL_FEATURE_SECONDARY_FAN BIT(9) + #define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(10) + #define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11) ++#define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12) ++#define UNIWILL_FEATURE_AC_AUTO_BOOT BIT(13) ++#define UNIWILL_FEATURE_USB_POWERSHARE BIT(14) + + enum usb_c_power_priority_options { + USB_C_POWER_PRIORITY_CHARGING = 0, +@@ -344,6 +380,7 @@ struct uniwill_data { + acpi_handle handle; + struct regmap *regmap; + unsigned int features; ++ u8 project_id; + struct acpi_battery_hook hook; + struct mutex battery_lock; /* Protects the list of currently registered batteries */ + union { +@@ -357,11 +394,25 @@ struct uniwill_data { + bool last_fn_lock_state; + bool last_super_key_enable_state; + bool last_touchpad_toggle_enable_state; ++ bool last_usb_powershare_high_state; + struct mutex super_key_lock; /* Protects the toggling of the super key lock state */ + struct list_head batteries; + struct mutex led_lock; /* Protects writes to the lightbar registers */ ++ u8 lightbar_max_brightness; + struct led_classdev_mc led_mc_cdev; + struct mc_subled led_mc_subled_info[LED_CHANNELS]; ++ bool kbd_led_single_color; ++ u8 kbd_led_max_brightness; ++ unsigned int last_kbd_status; ++ union { ++ struct { ++ /* Protects writes to the RGB keyboard backlight registers */ ++ struct mutex kbd_rgb_led_lock; ++ struct led_classdev_mc kbd_led_mc_cdev; ++ struct mc_subled kbd_led_mc_subled_info[KBD_LED_CHANNELS]; ++ }; ++ struct led_classdev kbd_led_cdev; ++ }; + struct mutex input_lock; /* Protects input sequence during notify */ + struct input_dev *input_device; + struct notifier_block nb; +@@ -376,6 +427,9 @@ struct uniwill_battery_entry { + + struct uniwill_device_descriptor { + unsigned int features; ++ bool kbd_led_single_color; ++ u8 kbd_led_max_brightness; ++ u8 lightbar_max_brightness; + /* Executed during driver probing */ + int (*probe)(struct uniwill_data *data); + }; +@@ -427,6 +481,9 @@ static const struct key_entry uniwill_keymap[] = { + { KE_KEY, UNIWILL_OSD_KBDILLUMDOWN, { KEY_KBDILLUMDOWN }}, + { KE_KEY, UNIWILL_OSD_KBDILLUMUP, { KEY_KBDILLUMUP }}, + ++ /* Reported when the EC changed the keyboard backlight brightness */ ++ { KE_IGNORE, UNIWILL_OSD_BACKLIGHT_LEVEL_CHANGE, { KEY_UNKNOWN }}, ++ + /* Reported when the user wants to toggle the microphone mute status */ + { KE_KEY, UNIWILL_OSD_MIC_MUTE, { KEY_MICMUTE }}, + +@@ -435,11 +492,6 @@ static const struct key_entry uniwill_keymap[] = { + + /* Reported when the user wants to toggle the brightness of the keyboard */ + { KE_KEY, UNIWILL_OSD_KBDILLUMTOGGLE, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL0, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL1, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL2, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL3, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL4, { KEY_KBDILLUMTOGGLE }}, + + /* FIXME: find out the exact meaning of those events */ + { KE_IGNORE, UNIWILL_OSD_BAT_CHARGE_FULL_24_H, { KEY_UNKNOWN }}, +@@ -448,6 +500,9 @@ static const struct key_entry uniwill_keymap[] = { + /* Reported when the user wants to toggle the benchmark mode status */ + { KE_IGNORE, UNIWILL_OSD_BENCHMARK_MODE_TOGGLE, { KEY_UNKNOWN }}, + ++ /* Reported when the screen is enabled/disabled during resume/suspend */ ++ { KE_IGNORE, UNIWILL_OSD_SCREEN_STATE_CHANGED, { KEY_UNKNOWN }}, ++ + /* Reported when the user wants to toggle the webcam */ + { KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }}, + +@@ -540,6 +595,7 @@ static const struct regmap_bus uniwill_ec_bus = { + static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case EC_ADDR_OEM_9: + case EC_ADDR_AP_OEM: + case EC_ADDR_LIGHTBAR_AC_CTRL: + case EC_ADDR_LIGHTBAR_AC_RED: +@@ -547,6 +603,11 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) + case EC_ADDR_LIGHTBAR_AC_BLUE: + case EC_ADDR_BIOS_OEM: + case EC_ADDR_TRIGGER: ++ case EC_ADDR_RGB_RED: ++ case EC_ADDR_RGB_GREEN: ++ case EC_ADDR_RGB_BLUE: ++ case EC_ADDR_BIOS_OEM_2: ++ case EC_ADDR_KBD_STATUS: + case EC_ADDR_OEM_4: + case EC_ADDR_CHARGE_CTRL: + case EC_ADDR_LIGHTBAR_BAT_CTRL: +@@ -574,6 +635,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) + case EC_ADDR_SECOND_FAN_RPM_1: + case EC_ADDR_SECOND_FAN_RPM_2: + case EC_ADDR_BAT_ALERT: ++ case EC_ADDR_OEM_9: + case EC_ADDR_PROJECT_ID: + case EC_ADDR_AP_OEM: + case EC_ADDR_LIGHTBAR_AC_CTRL: +@@ -583,8 +645,14 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) + case EC_ADDR_BIOS_OEM: + case EC_ADDR_PWM_1: + case EC_ADDR_PWM_2: ++ case EC_ADDR_SUPPORT_2: + case EC_ADDR_TRIGGER: + case EC_ADDR_SWITCH_STATUS: ++ case EC_ADDR_RGB_RED: ++ case EC_ADDR_RGB_GREEN: ++ case EC_ADDR_RGB_BLUE: ++ case EC_ADDR_BIOS_OEM_2: ++ case EC_ADDR_KBD_STATUS: + case EC_ADDR_OEM_4: + case EC_ADDR_CHARGE_CTRL: + case EC_ADDR_LIGHTBAR_BAT_CTRL: +@@ -616,8 +684,10 @@ static bool uniwill_volatile_reg(struct device *dev, unsigned int reg) + case EC_ADDR_BIOS_OEM: + case EC_ADDR_PWM_1: + case EC_ADDR_PWM_2: ++ case EC_ADDR_SUPPORT_2: + case EC_ADDR_TRIGGER: + case EC_ADDR_SWITCH_STATUS: ++ case EC_ADDR_KBD_STATUS: + case EC_ADDR_OEM_4: + case EC_ADDR_CHARGE_CTRL: + case EC_ADDR_USB_C_POWER_PRIORITY: +@@ -1077,6 +1147,109 @@ static int usb_c_power_priority_init(struct uniwill_data *data) + return 0; + } + ++static ssize_t ac_auto_boot_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ unsigned int regval; ++ bool enable; ++ int ret; ++ ++ ret = kstrtobool(buf, &enable); ++ if (ret < 0) ++ return ret; ++ ++ if (enable) ++ regval = AC_AUTO_BOOT_ENABLE; ++ else ++ regval = 0; ++ ++ ret = regmap_update_bits(data->regmap, EC_ADDR_OEM_9, AC_AUTO_BOOT_ENABLE, regval); ++ if (ret < 0) ++ return ret; ++ ++ return count; ++} ++ ++static ssize_t ac_auto_boot_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ unsigned int regval; ++ int ret; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_OEM_9, ®val); ++ if (ret < 0) ++ return ret; ++ ++ return sysfs_emit(buf, "%d\n", !!(regval & AC_AUTO_BOOT_ENABLE)); ++} ++ ++static DEVICE_ATTR_RW(ac_auto_boot); ++ ++static int uniwill_write_usb_powershare_high(struct uniwill_data *data, bool status) ++{ ++ unsigned int value; ++ ++ if (status) ++ value = TRIGGER_USB_CHARGING; ++ else ++ value = 0; ++ ++ /* ++ * Normaly this RMW-sequence could also trigger the super key toggle, ++ * but the EC seems to take care that those bits are always read as 0. ++ */ ++ return regmap_update_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_USB_CHARGING, value); ++} ++ ++static ssize_t usb_powershare_high_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ bool enable; ++ int ret; ++ ++ ret = kstrtobool(buf, &enable); ++ if (ret < 0) ++ return ret; ++ ++ ret = uniwill_write_usb_powershare_high(data, enable); ++ if (ret < 0) ++ return ret; ++ ++ return count; ++} ++ ++static int uniwill_read_usb_powershare_high(struct uniwill_data *data, bool *status) ++{ ++ unsigned int value; ++ int ret; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_TRIGGER, &value); ++ if (ret < 0) ++ return ret; ++ ++ *status = !!(value & TRIGGER_USB_CHARGING); ++ ++ return 0; ++} ++ ++static ssize_t usb_powershare_high_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ bool status; ++ int ret; ++ ++ ret = uniwill_read_usb_powershare_high(data, &status); ++ if (ret < 0) ++ return ret; ++ ++ return sysfs_emit(buf, "%d\n", status); ++} ++ ++static DEVICE_ATTR_RW(usb_powershare_high); ++ + static struct attribute *uniwill_attrs[] = { + /* Keyboard-related */ + &dev_attr_fn_lock.attr, +@@ -1088,6 +1261,8 @@ static struct attribute *uniwill_attrs[] = { + /* Power-management-related */ + &dev_attr_ctgp_offset.attr, + &dev_attr_usb_c_power_priority.attr, ++ &dev_attr_ac_auto_boot.attr, ++ &dev_attr_usb_powershare_high.attr, + NULL + }; + +@@ -1127,6 +1302,16 @@ static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *a + return attr->mode; + } + ++ if (attr == &dev_attr_ac_auto_boot.attr) { ++ if (uniwill_device_supports(data, UNIWILL_FEATURE_AC_AUTO_BOOT)) ++ return attr->mode; ++ } ++ ++ if (attr == &dev_attr_usb_powershare_high.attr) { ++ if (uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) ++ return attr->mode; ++ } ++ + return 0; + } + +@@ -1332,7 +1517,7 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br + + for (int i = 0; i < LED_CHANNELS; i++) { + /* Prevent the brightness values from overflowing */ +- value = min(LED_MAX_BRIGHTNESS, data->led_mc_subled_info[i].brightness); ++ value = min(data->lightbar_max_brightness, data->led_mc_subled_info[i].brightness); + ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); + if (ret < 0) + return ret; +@@ -1401,14 +1586,14 @@ static int uniwill_led_init(struct uniwill_data *data) + return ret; + + data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; +- data->led_mc_cdev.led_cdev.max_brightness = LED_MAX_BRIGHTNESS; ++ data->led_mc_cdev.led_cdev.max_brightness = data->lightbar_max_brightness; + data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT; + data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set; + + if (value & LIGHTBAR_S0_OFF) + data->led_mc_cdev.led_cdev.brightness = 0; + else +- data->led_mc_cdev.led_cdev.brightness = LED_MAX_BRIGHTNESS; ++ data->led_mc_cdev.led_cdev.brightness = data->lightbar_max_brightness; + + for (int i = 0; i < LED_CHANNELS; i++) { + data->led_mc_subled_info[i].color_index = color_indices[i]; +@@ -1421,7 +1606,7 @@ static int uniwill_led_init(struct uniwill_data *data) + * Make sure that the initial intensity value is not greater than + * the maximum brightness. + */ +- value = min(LED_MAX_BRIGHTNESS, value); ++ value = min(data->lightbar_max_brightness, value); + ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); + if (ret < 0) + return ret; +@@ -1441,6 +1626,246 @@ static int uniwill_led_init(struct uniwill_data *data) + &init_data); + } + ++static int uniwill_notify_kbd_led(struct uniwill_data *data, int brightness) ++{ ++ struct led_classdev *led_cdev; ++ int ret; ++ ++ if (data->kbd_led_single_color) ++ led_cdev = &data->kbd_led_cdev; ++ else ++ led_cdev = &data->kbd_led_mc_cdev.led_cdev; ++ ++ guard(mutex)(&led_cdev->led_access); ++ ++ /* Sync the LED brightness with the actual hardware state */ ++ ret = led_update_brightness(led_cdev); ++ if (ret < 0) ++ return ret; ++ ++ led_classdev_notify_brightness_hw_changed(led_cdev, brightness); ++ ++ return 0; ++} ++ ++#define KBD_LED_MASK (KBD_BRIGHTNESS_MASK | KBD_APPLY | KBD_POWER_OFF) ++ ++static int uniwill_kbd_led_write_brightness(struct uniwill_data *data, int brightness) ++{ ++ /* KBD_POWER_OFF is always implicitly cleared */ ++ unsigned int regval = FIELD_PREP(KBD_BRIGHTNESS_MASK, brightness) | KBD_APPLY; ++ ++ /* We must ensure that the "apply" bit is always written */ ++ return regmap_write_bits(data->regmap, EC_ADDR_KBD_STATUS, KBD_LED_MASK, regval); ++} ++ ++static int uniwill_kbd_led_read_brightness(struct uniwill_data *data) ++{ ++ unsigned int regval; ++ int ret; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); ++ if (ret < 0) ++ return ret; ++ ++ return min(FIELD_GET(KBD_BRIGHTNESS_MASK, regval), data->kbd_led_max_brightness); ++} ++ ++static int uniwill_kbd_led_brightness_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); ++ ++ return uniwill_kbd_led_write_brightness(data, brightness); ++} ++ ++static enum led_brightness uniwill_kbd_led_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); ++ ++ return uniwill_kbd_led_read_brightness(data); ++} ++ ++static const unsigned int uniwill_kbd_led_channel_to_reg[KBD_LED_CHANNELS] = { ++ EC_ADDR_RGB_RED, ++ EC_ADDR_RGB_GREEN, ++ EC_ADDR_RGB_BLUE, ++}; ++ ++static int uniwill_kbd_led_mc_brightness_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); ++ struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); ++ unsigned int min_intensity = 0; ++ unsigned int regval; ++ int ret; ++ ++ guard(mutex)(&data->kbd_rgb_led_lock); ++ ++ /* ++ * The EC interprets a RGB value of 0x000000 as a command to restore ++ * the device-specfic default RGB value. Work around this by writing ++ * a RGB value of 0x010101 (faint white) instead. ++ */ ++ if (data->kbd_led_mc_subled_info[0].intensity == 0 && ++ data->kbd_led_mc_subled_info[1].intensity == 0 && ++ data->kbd_led_mc_subled_info[2].intensity == 0) ++ min_intensity = 1; ++ ++ for (int i = 0; i < KBD_LED_CHANNELS; i++) { ++ regval = max(data->kbd_led_mc_subled_info[i].intensity, min_intensity); ++ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); ++ if (ret < 0) ++ return ret; ++ } ++ ++ ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); ++ if (ret < 0) ++ return ret; ++ ++ return uniwill_kbd_led_write_brightness(data, brightness); ++} ++ ++static enum led_brightness uniwill_kbd_led_mc_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); ++ struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); ++ ++ return uniwill_kbd_led_read_brightness(data); ++} ++ ++static int uniwill_white_kbd_led_init(struct uniwill_data *data) ++{ ++ struct led_init_data init_data = { ++ .default_label = "white:" LED_FUNCTION_KBD_BACKLIGHT, ++ .devicename = DRIVER_NAME, ++ .devname_mandatory = true, ++ }; ++ ++ data->kbd_led_cdev.max_brightness = data->kbd_led_max_brightness; ++ data->kbd_led_cdev.color = LED_COLOR_ID_WHITE; ++ data->kbd_led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; ++ data->kbd_led_cdev.brightness_set_blocking = uniwill_kbd_led_brightness_set; ++ data->kbd_led_cdev.brightness_get = uniwill_kbd_led_brightness_get; ++ ++ return devm_led_classdev_register_ext(data->dev, &data->kbd_led_cdev, &init_data); ++} ++ ++static int uniwill_rgb_kbd_led_init(struct uniwill_data *data) ++{ ++ unsigned int color_indices[KBD_LED_CHANNELS] = { ++ LED_COLOR_ID_RED, ++ LED_COLOR_ID_GREEN, ++ LED_COLOR_ID_BLUE, ++ }; ++ struct led_init_data init_data = { ++ .default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT, ++ .devicename = DRIVER_NAME, ++ .devname_mandatory = true, ++ }; ++ bool intensity_all_zeros = true; ++ bool needs_trigger = false; ++ unsigned int regval; ++ int ret; ++ ++ for (int i = 0; i < KBD_LED_CHANNELS; i++) { ++ data->kbd_led_mc_subled_info[i].color_index = color_indices[i]; ++ ++ ret = regmap_read(data->regmap, uniwill_kbd_led_channel_to_reg[i], ®val); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * Make sure that the initial intensity value is not greater than ++ * the maximum intensity. ++ */ ++ if (regval > KBD_LED_MAX_INTENSITY) { ++ regval = KBD_LED_MAX_INTENSITY; ++ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); ++ if (ret < 0) ++ return ret; ++ ++ needs_trigger = true; ++ } ++ ++ if (regval) ++ intensity_all_zeros = false; ++ ++ data->kbd_led_mc_subled_info[i].intensity = regval; ++ data->kbd_led_mc_subled_info[i].max_intensity = KBD_LED_MAX_INTENSITY; ++ data->kbd_led_mc_subled_info[i].channel = i; ++ } ++ ++ /* See uniwill_kbd_led_mc_brightness_set() for an explaination. */ ++ if (intensity_all_zeros) { ++ for (int i = 0; i < KBD_LED_CHANNELS; i++) { ++ data->kbd_led_mc_subled_info[i].intensity = 1; ++ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], 1); ++ if (ret < 0) ++ return ret; ++ } ++ ++ needs_trigger = true; ++ } ++ ++ if (needs_trigger) { ++ ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, ++ RGB_APPLY_COLOR); ++ if (ret < 0) ++ return ret; ++ } ++ ++ ret = devm_mutex_init(data->dev, &data->kbd_rgb_led_lock); ++ if (ret < 0) ++ return ret; ++ ++ data->kbd_led_mc_cdev.led_cdev.max_brightness = data->kbd_led_max_brightness; ++ data->kbd_led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; ++ data->kbd_led_mc_cdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; ++ data->kbd_led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_kbd_led_mc_brightness_set; ++ data->kbd_led_mc_cdev.led_cdev.brightness_get = uniwill_kbd_led_mc_brightness_get; ++ data->kbd_led_mc_cdev.subled_info = data->kbd_led_mc_subled_info; ++ data->kbd_led_mc_cdev.num_colors = KBD_LED_CHANNELS; ++ ++ return devm_led_classdev_multicolor_register_ext(data->dev, &data->kbd_led_mc_cdev, ++ &init_data); ++} ++ ++static int uniwill_kbd_led_init(struct uniwill_data *data) ++{ ++ unsigned int regval; ++ int ret; ++ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return 0; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, ®val); ++ if (ret < 0) ++ return ret; ++ ++ if (!(regval & CHINA_MODE)) { ++ ret = regmap_set_bits(data->regmap, EC_ADDR_BIOS_OEM_2, ENABLE_CHINA_MODE); ++ if (ret < 0) ++ return ret; ++ } ++ ++ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); ++ if (ret < 0) ++ return ret; ++ ++ regval |= KBD_APPLY; ++ regval &= ~KBD_POWER_OFF; ++ ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); ++ if (ret < 0) ++ return ret; ++ ++ if (data->kbd_led_single_color) ++ return uniwill_white_kbd_led_init(data); ++ ++ return uniwill_rgb_kbd_led_init(data); ++} ++ + static unsigned int uniwill_sanitize_battery_threshold(unsigned int value) + { + /* 0 means "charging threshold not active" */ +@@ -1789,6 +2214,31 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action + sysfs_notify(&data->dev->kobj, NULL, "fn_lock"); + + return NOTIFY_OK; ++ case UNIWILL_OSD_KB_LED_LEVEL0: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 0)); ++ case UNIWILL_OSD_KB_LED_LEVEL1: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 1)); ++ case UNIWILL_OSD_KB_LED_LEVEL2: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 2)); ++ case UNIWILL_OSD_KB_LED_LEVEL3: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 3)); ++ case UNIWILL_OSD_KB_LED_LEVEL4: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 4)); + default: + mutex_lock(&data->input_lock); + sparse_keymap_report_event(data->input_device, action, 1, true); +@@ -1842,6 +2292,7 @@ static int uniwill_ec_init(struct uniwill_data *data) + if (ret < 0) + return ret; + ++ data->project_id = value; + dev_dbg(data->dev, "Project ID: %u\n", value); + + ret = regmap_set_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); +@@ -1885,6 +2336,9 @@ static int uniwill_probe(struct platform_device *pdev) + return ret; + + data->features = device_descriptor.features; ++ data->kbd_led_single_color = device_descriptor.kbd_led_single_color; ++ data->kbd_led_max_brightness = device_descriptor.kbd_led_max_brightness; ++ data->lightbar_max_brightness = device_descriptor.lightbar_max_brightness; + + /* + * Some devices might need to perform some device-specific initialization steps +@@ -1905,6 +2359,10 @@ static int uniwill_probe(struct platform_device *pdev) + if (ret < 0) + return ret; + ++ ret = uniwill_kbd_led_init(data); ++ if (ret < 0) ++ return ret; ++ + ret = uniwill_hwmon_init(data); + if (ret < 0) + return ret; +@@ -1976,6 +2434,43 @@ static int uniwill_suspend_battery(struct uniwill_data *data) + return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl); + } + ++static int uniwill_suspend_kbd_led(struct uniwill_data *data) ++{ ++ unsigned int regval; ++ int ret; ++ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return 0; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * Save the current keyboard backlight settings in order to restore them ++ * during resume. We cannot use the regmap code for that since this register ++ * needs to be declared as volatile because the brightness can be changed ++ * by the EC. ++ */ ++ data->last_kbd_status = regval; ++ FIELD_MODIFY(KBD_BRIGHTNESS_MASK, ®val, 0); ++ regval |= KBD_APPLY | KBD_POWER_OFF; ++ ++ return regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); ++} ++ ++static int uniwill_suspend_usb_powershare(struct uniwill_data *data) ++{ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) ++ return 0; ++ ++ /* ++ * EC_ADDR_TRIGGER is marked as volatile, so we have to restore it ++ * ourselves. ++ */ ++ return uniwill_read_usb_powershare_high(data, &data->last_usb_powershare_high_state); ++} ++ + static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data) + { + if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) +@@ -2006,6 +2501,14 @@ static int uniwill_suspend(struct device *dev) + if (ret < 0) + return ret; + ++ ret = uniwill_suspend_kbd_led(data); ++ if (ret < 0) ++ return ret; ++ ++ ret = uniwill_suspend_usb_powershare(data); ++ if (ret < 0) ++ return ret; ++ + ret = uniwill_suspend_nvidia_ctgp(data); + if (ret < 0) + return ret; +@@ -2052,6 +2555,31 @@ static int uniwill_resume_battery(struct uniwill_data *data) + return 0; + } + ++static int uniwill_resume_kbd_led(struct uniwill_data *data) ++{ ++ int ret; ++ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return 0; ++ ++ ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, data->last_kbd_status | KBD_APPLY); ++ if (ret < 0) ++ return ret; ++ ++ if (data->kbd_led_single_color) ++ return 0; ++ ++ return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); ++} ++ ++static int uniwill_resume_usb_powershare(struct uniwill_data *data) ++{ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) ++ return 0; ++ ++ return uniwill_write_usb_powershare_high(data, data->last_usb_powershare_high_state); ++} ++ + static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data) + { + if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) +@@ -2096,6 +2624,14 @@ static int uniwill_resume(struct device *dev) + if (ret < 0) + return ret; + ++ ret = uniwill_resume_kbd_led(data); ++ if (ret < 0) ++ return ret; ++ ++ ret = uniwill_resume_usb_powershare(data); ++ if (ret < 0) ++ return ret; ++ + ret = uniwill_resume_nvidia_ctgp(data); + if (ret < 0) + return ret; +@@ -2126,13 +2662,30 @@ static struct platform_driver uniwill_driver = { + .shutdown = uniwill_shutdown, + }; + ++static struct uniwill_device_descriptor machenike_l16p_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_GPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | ++ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++ .kbd_led_single_color = false, ++ .kbd_led_max_brightness = 4, ++}; ++ + static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata = { + .features = UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_LIGHTBAR | + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN, ++ .lightbar_max_brightness = 36, + }; + + static struct uniwill_device_descriptor lapac71h_descriptor __initdata = { +@@ -2156,6 +2709,7 @@ static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = { + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN, ++ .lightbar_max_brightness = 200, + }; + + /* +@@ -2198,15 +2752,6 @@ static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __ini + UNIWILL_FEATURE_USB_C_POWER_PRIORITY, + }; + +-static struct uniwill_device_descriptor tux_featureset_3_descriptor __initdata = { +- .features = UNIWILL_FEATURE_FN_LOCK | +- UNIWILL_FEATURE_SUPER_KEY | +- UNIWILL_FEATURE_BATTERY_CHARGE_MODES | +- UNIWILL_FEATURE_CPU_TEMP | +- UNIWILL_FEATURE_PRIMARY_FAN | +- UNIWILL_FEATURE_SECONDARY_FAN, +-}; +- + static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | +@@ -2218,6 +2763,30 @@ static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __ini + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, + }; + ++static struct uniwill_device_descriptor tux_featureset_4_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_BATTERY_CHARGE_MODES | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++}; ++ ++static struct uniwill_device_descriptor tux_featureset_4_nvidia_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_BATTERY_CHARGE_MODES | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_GPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++}; ++ + static int phxtxx1_probe(struct uniwill_data *data) + { + unsigned int value; +@@ -2277,7 +2846,37 @@ static struct uniwill_device_descriptor pf5pu1g_descriptor __initdata = { + UNIWILL_FEATURE_PRIMARY_FAN, + }; + ++static struct uniwill_device_descriptor x4sp4nal_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_BATTERY_CHARGE_MODES | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++ .kbd_led_single_color = true, ++ .kbd_led_max_brightness = 2, ++}; ++ + static const struct dmi_system_id uniwill_dmi_table[] __initconst = { ++ { ++ .ident = "AiStone X4SP4NAL", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "AiStone"), ++ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X4SP4NAL"), ++ }, ++ .driver_data = &x4sp4nal_descriptor, ++ }, ++ { ++ .ident = "MACHENIKE L16 Pro", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "MACHENIKE"), ++ DMI_EXACT_MATCH(DMI_BOARD_NAME, "L16P"), ++ }, ++ .driver_data = &machenike_l16p_descriptor, ++ }, + { + .ident = "XMG FUSION 15 (L19)", + .matches = { +@@ -2310,6 +2909,13 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + }, + .driver_data = &lapqc71a_lapqc71b_descriptor, + }, ++ { ++ .ident = "Avell A60 MUV", ++ .matches = { ++ DMI_MATCH(DMI_PRODUCT_NAME, "A60 MUV"), ++ }, ++ .driver_data = &lapqc71a_lapqc71b_descriptor, ++ }, + { + .ident = "Intel NUC x15", + .matches = { +@@ -2388,7 +2994,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9", +@@ -2396,7 +3002,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", +@@ -2404,7 +3010,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", +@@ -2412,7 +3018,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel", +@@ -2420,7 +3026,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 15 Gen10 AMD", +@@ -2428,7 +3034,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", +@@ -2436,7 +3042,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", +@@ -2444,7 +3050,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 15 Gen10 Intel", +@@ -2452,7 +3058,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 16 Gen10 Intel", +@@ -2460,7 +3066,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Polaris 15 Gen1 AMD", +@@ -2620,7 +3226,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6", +@@ -2628,7 +3234,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", +@@ -2636,7 +3242,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", +@@ -2644,7 +3250,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6", +@@ -2652,7 +3258,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen7 AMD", +@@ -2660,7 +3266,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen7 Intel", +@@ -2668,7 +3274,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen7 Intel", +@@ -2676,7 +3282,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Book BA15 Gen10 AMD", +@@ -2745,6 +3351,12 @@ static int __init uniwill_init(void) + if (force) { + /* Assume that the device supports all features except the charge limit */ + device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT; ++ /* Some models only have a (white) single color keyboard backlight */ ++ device_descriptor.kbd_led_single_color = false; ++ /* Some models only support 3 brightness levels */ ++ device_descriptor.kbd_led_max_brightness = 4; ++ /* Some models only support 36 brightness levels per color component */ ++ device_descriptor.lightbar_max_brightness = 200; + pr_warn("Enabling potentially unsupported features\n"); + } + +diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h +--- a/drivers/platform/x86/uniwill/uniwill-wmi.h ++++ b/drivers/platform/x86/uniwill/uniwill-wmi.h +@@ -113,6 +113,8 @@ + + #define UNIWILL_OSD_BENCHMARK_MODE_TOGGLE 0xC0 + ++#define UNIWILL_OSD_SCREEN_STATE_CHANGED 0xCC ++ + #define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF + + #define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0 +diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c +--- a/drivers/platform/x86/xo15-ebook.c ++++ b/drivers/platform/x86/xo15-ebook.c +@@ -19,13 +19,10 @@ + + #define MODULE_NAME "xo15-ebook" + +-#define XO15_EBOOK_CLASS MODULE_NAME + #define XO15_EBOOK_TYPE_UNKNOWN 0x00 + #define XO15_EBOOK_NOTIFY_STATUS 0x80 + +-#define XO15_EBOOK_SUBCLASS "ebook" + #define XO15_EBOOK_HID "XO15EBK" +-#define XO15_EBOOK_DEVICE_NAME "EBook Switch" + + MODULE_DESCRIPTION("OLPC XO-1.5 ebook switch driver"); + MODULE_LICENSE("GPL"); +@@ -105,12 +102,9 @@ static int ebook_switch_probe(struct platform_device *pdev) + if (!id) + return dev_err_probe(dev, -ENODEV, "Unsupported hid\n"); + +- strscpy(acpi_device_name(device), XO15_EBOOK_DEVICE_NAME); +- strscpy(acpi_device_class(device), XO15_EBOOK_CLASS "/" XO15_EBOOK_SUBCLASS); +- + snprintf(button->phys, sizeof(button->phys), "%s/button/input0", id->id); + +- input->name = acpi_device_name(device); ++ input->name = "EBook Switch"; + input->phys = button->phys; + input->id.bustype = BUS_HOST; + +diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c +--- a/drivers/power/supply/surface_battery.c ++++ b/drivers/power/supply/surface_battery.c +@@ -852,9 +852,14 @@ static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { + }; + + static const struct ssam_device_id surface_battery_match[] = { +- { SSAM_SDEV(BAT, SAM, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, +- { SSAM_SDEV(BAT, KIP, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, +- { }, ++ { ++ SSAM_SDEV(BAT, SAM, 0x01, 0x00), ++ .driver_data = (unsigned long)&spwr_psy_props_bat1, ++ }, { ++ SSAM_SDEV(BAT, KIP, 0x01, 0x00), ++ .driver_data = (unsigned long)&spwr_psy_props_bat2_sb3, ++ }, ++ { } + }; + MODULE_DEVICE_TABLE(ssam, surface_battery_match); + +diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c +--- a/drivers/power/supply/surface_charger.c ++++ b/drivers/power/supply/surface_charger.c +@@ -260,8 +260,11 @@ static const struct spwr_psy_properties spwr_psy_props_adp1 = { + }; + + static const struct ssam_device_id surface_ac_match[] = { +- { SSAM_SDEV(BAT, SAM, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, +- { }, ++ { ++ SSAM_SDEV(BAT, SAM, 0x01, 0x01), ++ .driver_data = (unsigned long)&spwr_psy_props_adp1, ++ }, ++ { } + }; + MODULE_DEVICE_TABLE(ssam, surface_ac_match); + +diff --git a/include/linux/amd-pmf-io.h b/include/linux/amd-pmf-io.h +--- a/include/linux/amd-pmf-io.h ++++ b/include/linux/amd-pmf-io.h +@@ -52,15 +52,6 @@ struct amd_sfh_info { + u32 laptop_placement; + }; + +-enum laptop_placement { +- LP_UNKNOWN = 0, +- ON_TABLE, +- ON_LAP_MOTION, +- IN_BAG, +- OUT_OF_BAG, +- LP_UNDEFINED, +-}; +- + /** + * struct amd_pmf_npu_metrics: Get NPU metrics data from PMF driver + * @npuclk_freq: NPU clock frequency [MHz] +@@ -69,6 +60,7 @@ enum laptop_placement { + * @mpnpuclk_freq: MPNPU [MHz] + * @npu_reads: NPU read bandwidth [MB/sec] + * @npu_writes: NPU write bandwidth [MB/sec] ++ * @npu_temp: NPU temperature [C] + */ + struct amd_pmf_npu_metrics { + u16 npuclk_freq; +@@ -77,6 +69,7 @@ struct amd_pmf_npu_metrics { + u16 mpnpuclk_freq; + u16 npu_reads; + u16 npu_writes; ++ u16 npu_temp; + }; + + int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op); +diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h +--- a/include/linux/platform_data/x86/asus-wmi.h ++++ b/include/linux/platform_data/x86/asus-wmi.h +@@ -203,6 +203,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval); + int asus_hid_register_listener(struct asus_hid_listener *cdev); + void asus_hid_unregister_listener(struct asus_hid_listener *cdev); + int asus_hid_event(enum asus_hid_event event); ++bool asus_wmi_custom_fan_curve_is_enabled(void); + #else + static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status) + { +@@ -234,6 +235,10 @@ static inline int asus_hid_event(enum asus_hid_event event) + { + return -ENODEV; + } ++static inline bool asus_wmi_custom_fan_curve_is_enabled(void) ++{ ++ return false; ++} + #endif + + #endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */ +diff --git a/include/uapi/linux/amd-pmf.h b/include/uapi/linux/amd-pmf.h +new file mode 100644 +--- /dev/null ++++ b/include/uapi/linux/amd-pmf.h +@@ -0,0 +1,204 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ ++/* ++ * AMD Platform Management Framework (PMF) UAPI Header ++ * ++ * Copyright (c) 2026, Advanced Micro Devices, Inc. ++ * All Rights Reserved. ++ * ++ * This file defines the user-space API for interacting with the AMD PMF ++ * driver. It provides ioctl interfaces to query platform-specific metrics ++ * such as power source, slider position, platform type, laptop placement, ++ * and various BIOS input/output parameters. ++ */ ++ ++#ifndef _UAPI_LINUX_AMD_PMF_H ++#define _UAPI_LINUX_AMD_PMF_H ++ ++#include ++#include ++#include ++ ++/** ++ * AMD_PMF_IOC_MAGIC - Magic number for AMD PMF ioctl commands ++ * ++ * This magic number uniquely identifies AMD PMF ioctl operations. ++ */ ++#define AMD_PMF_IOC_MAGIC 'p' ++ ++/** ++ * IOCTL_AMD_PMF_POPULATE_DATA - ioctl command to retrieve PMF metrics data ++ * ++ * This ioctl command is used to populate the amd_pmf_info structure ++ * with the requested PMF metrics information. ++ */ ++#define IOCTL_AMD_PMF_POPULATE_DATA _IOWR(AMD_PMF_IOC_MAGIC, 0x00, __u64) ++ ++#define AMD_PMF_BIOS_PARAMS_MAX 10 ++ ++/* AMD PMF feature flags - bitmask indicating supported features */ ++#define AMD_PMF_FEAT_AUTO_MODE BIT(0) ++#define AMD_PMF_FEAT_STATIC_POWER_SLIDER BIT(1) ++#define AMD_PMF_FEAT_POLICY_BUILDER BIT(2) ++#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC BIT(3) ++#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC BIT(4) ++ ++/** ++ * enum amd_pmf_laptop_placement - Describes the physical placement of the laptop ++ * @AMD_PMF_LP_UNKNOWN: Placement cannot be determined ++ * @AMD_PMF_ON_TABLE: Laptop is placed on a stable surface like a table or desk ++ * @AMD_PMF_ON_LAP_MOTION: Laptop is on a lap with detected motion ++ * @AMD_PMF_IN_BAG: Laptop is detected to be inside a bag or case ++ * @AMD_PMF_OUT_OF_BAG: Laptop has been removed from bag or case ++ * @AMD_PMF_LP_UNDEFINED: Placement state is undefined ++ * ++ * This enumeration represents the physical placement state of the laptop ++ * as detected by platform sensors. Used for adaptive power management ++ * and thermal policies. ++ */ ++enum amd_pmf_laptop_placement { ++ AMD_PMF_LP_UNKNOWN, ++ AMD_PMF_ON_TABLE, ++ AMD_PMF_ON_LAP_MOTION, ++ AMD_PMF_IN_BAG, ++ AMD_PMF_OUT_OF_BAG, ++ AMD_PMF_LP_UNDEFINED, ++}; ++ ++/** ++ * enum amd_pmf_ta_slider - Trusted Application power slider positions ++ * @AMD_PMF_TA_BEST_BATTERY: Maximum battery savings, minimal performance ++ * @AMD_PMF_TA_BETTER_BATTERY: Balanced towards battery life ++ * @AMD_PMF_TA_BETTER_PERFORMANCE: Balanced towards performance ++ * @AMD_PMF_TA_BEST_PERFORMANCE: Maximum performance, higher power consumption ++ * @AMD_PMF_TA_MAX: Sentinel value indicating maximum enum value ++ * ++ * This enumeration defines the power slider positions used by the ++ * AMD PMF Trusted Application for dynamic power management decisions. ++ * These correspond to the Windows power slider UI positions. ++ */ ++enum amd_pmf_ta_slider { ++ AMD_PMF_TA_BEST_BATTERY, ++ AMD_PMF_TA_BETTER_BATTERY, ++ AMD_PMF_TA_BETTER_PERFORMANCE, ++ AMD_PMF_TA_BEST_PERFORMANCE, ++ AMD_PMF_TA_MAX, ++}; ++ ++/** ++ * enum amd_pmf_platform_type - Describes the physical form factor orientation ++ * @AMD_PMF_PTYPE_UNKNOWN: Platform type cannot be determined ++ * @AMD_PMF_LID_CLOSE: Laptop lid is closed ++ * @AMD_PMF_CLAMSHELL: Traditional laptop mode with keyboard and screen ++ * @AMD_PMF_FLAT: Device is lying flat on a surface ++ * @AMD_PMF_TENT: Device is in tent mode (keyboard folded back, standing) ++ * @AMD_PMF_STAND: Device is propped up in stand orientation ++ * @AMD_PMF_TABLET: Device is in tablet mode with keyboard hidden ++ * @AMD_PMF_BOOK: Device is in book reading orientation ++ * @AMD_PMF_PRESENTATION: Device is in presentation mode ++ * @AMD_PMF_PULL_FWD: Screen is pulled forward towards user ++ * @AMD_PMF_PTYPE_INVALID: Invalid platform type marker ++ * ++ * This enumeration describes the current physical orientation or form ++ * factor of convertible/2-in-1 devices. Used for optimizing power and ++ * thermal management based on device posture. ++ */ ++enum amd_pmf_platform_type { ++ AMD_PMF_PTYPE_UNKNOWN, ++ AMD_PMF_LID_CLOSE, ++ AMD_PMF_CLAMSHELL, ++ AMD_PMF_FLAT, ++ AMD_PMF_TENT, ++ AMD_PMF_STAND, ++ AMD_PMF_TABLET, ++ AMD_PMF_BOOK, ++ AMD_PMF_PRESENTATION, ++ AMD_PMF_PULL_FWD, ++ AMD_PMF_PTYPE_INVALID = 0xf, ++}; ++ ++static inline const char *amd_pmf_get_platform_type(unsigned int platform_type) ++{ ++ switch (platform_type) { ++ case AMD_PMF_CLAMSHELL: ++ return "CLAMSHELL"; ++ case AMD_PMF_LID_CLOSE: ++ return "LID_CLOSE"; ++ case AMD_PMF_FLAT: ++ return "FLAT"; ++ case AMD_PMF_TENT: ++ return "TENT"; ++ case AMD_PMF_STAND: ++ return "STAND"; ++ case AMD_PMF_TABLET: ++ return "TABLET"; ++ case AMD_PMF_BOOK: ++ return "BOOK"; ++ case AMD_PMF_PRESENTATION: ++ return "PRESENTATION"; ++ case AMD_PMF_PULL_FWD: ++ return "PULL_FWD"; ++ default: ++ return "UNKNOWN"; ++ } ++} ++ ++static inline const char *amd_pmf_get_laptop_placement(unsigned int device_state) ++{ ++ switch (device_state) { ++ case AMD_PMF_ON_TABLE: ++ return "ON_TABLE"; ++ case AMD_PMF_ON_LAP_MOTION: ++ return "ON_LAP_MOTION"; ++ case AMD_PMF_IN_BAG: ++ return "IN_BAG"; ++ case AMD_PMF_OUT_OF_BAG: ++ return "OUT_OF_BAG"; ++ default: ++ return "UNKNOWN"; ++ } ++} ++ ++static inline const char *amd_pmf_get_slider_position(unsigned int state) ++{ ++ switch (state) { ++ case AMD_PMF_TA_BEST_PERFORMANCE: ++ return "PERFORMANCE"; ++ case AMD_PMF_TA_BETTER_PERFORMANCE: ++ return "BALANCED"; ++ case AMD_PMF_TA_BEST_BATTERY: ++ return "POWER_SAVER"; ++ case AMD_PMF_TA_BETTER_BATTERY: ++ return "BALANCED_BATTERY"; ++ default: ++ return "Unknown TA Slider State"; ++ } ++} ++ ++struct amd_pmf_info { ++ __u64 size; ++ ++ /* Feature info */ ++ __u32 features_supported; ++ ++ /* Power and state info */ ++ __u32 platform_type; ++ __u32 power_source; ++ __u32 laptop_placement; ++ __u32 lid_state; ++ __u32 user_presence; ++ __u32 slider_position; ++ ++ /* Thermal and power metrics */ ++ __s32 skin_temp; ++ __u32 gfx_busy; ++ __s32 ambient_light; ++ __u32 avg_c0_residency; ++ __u32 max_c0_residency; ++ __u32 socket_power; ++ ++ /* BIOS parameters */ ++ __u32 bios_input[AMD_PMF_BIOS_PARAMS_MAX]; ++ __u32 bios_output[AMD_PMF_BIOS_PARAMS_MAX]; ++}; ++ ++#endif /* _UAPI_LINUX_AMD_PMF_H */ diff --git a/pkgbuilds/linux-omarchy-bore/0801-amd-pmf-util-unbind-use-after-free.patch b/pkgbuilds/linux-omarchy-bore/0801-amd-pmf-util-unbind-use-after-free.patch new file mode 100644 index 0000000..029ebd2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0801-amd-pmf-util-unbind-use-after-free.patch @@ -0,0 +1,34 @@ +diff --git a/drivers/platform/x86/amd/pmf/util.c b/drivers/platform/x86/amd/pmf/util.c +index 1111111..2222222 100644 +--- a/drivers/platform/x86/amd/pmf/util.c ++++ b/drivers/platform/x86/amd/pmf/util.c +@@ -79,9 +79,9 @@ static int amd_pmf_populate_data(struct amd_pmf_dev *pdev, struct amd_pmf_info * + + static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) + { +- struct amd_pmf_dev *pdev = filp->private_data; + void __user *argp = (void __user *)arg; + struct amd_pmf_info info = {}; ++ struct amd_pmf_dev *pdev; + size_t copy_size; + __u64 user_size; + int ret; +@@ -94,6 +94,10 @@ static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long + return -EFAULT; + + guard(mutex)(&pmf_util_lock); ++ pdev = pmf_dev_handle; ++ if (!pdev) ++ return -ENODEV; ++ + ret = amd_pmf_populate_data(pdev, &info); + if (ret) + return ret; +@@ -115,7 +119,6 @@ static int amd_pmf_open(struct inode *inode, struct file *filp) + if (!pmf_dev_handle) + return -ENODEV; + +- filp->private_data = pmf_dev_handle; + return 0; + } + diff --git a/pkgbuilds/linux-omarchy-bore/0850-futex-wait-multiple.patch b/pkgbuilds/linux-omarchy-bore/0850-futex-wait-multiple.patch new file mode 100644 index 0000000..d4ab07f --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0850-futex-wait-multiple.patch @@ -0,0 +1,142 @@ +diff --git a/include/uapi/linux/futex.h b/include/uapi/linux/futex.h +--- a/include/uapi/linux/futex.h ++++ b/include/uapi/linux/futex.h +@@ -22,6 +22,7 @@ + #define FUTEX_WAIT_REQUEUE_PI 11 + #define FUTEX_CMP_REQUEUE_PI 12 + #define FUTEX_LOCK_PI2 13 ++#define FUTEX_WAIT_MULTIPLE 31 + + #define FUTEX_PRIVATE_FLAG 128 + #define FUTEX_CLOCK_REALTIME 256 +@@ -126,6 +127,18 @@ struct futex_waitv { + __u32 __reserved; + }; + ++/** ++ * struct futex_wait_block - Block of futexes to be waited for ++ * @uaddr: User address of the futex ++ * @val: Futex value expected by userspace ++ * @bitset: Bitset for the optional bitmasked wakeup ++ */ ++struct futex_wait_block { ++ __u32 __user *uaddr; ++ __u32 val; ++ __u32 bitset; ++}; ++ + /* + * Support for robust futexes: the kernel cleans up held futexes at + * thread exit time. +diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c +--- a/kernel/futex/syscalls.c ++++ b/kernel/futex/syscalls.c +@@ -169,6 +169,7 @@ static __always_inline bool futex_cmd_has_timeout(u32 cmd) + case FUTEX_LOCK_PI2: + case FUTEX_WAIT_BITSET: + case FUTEX_WAIT_REQUEUE_PI: ++ case FUTEX_WAIT_MULTIPLE: + return true; + } + return false; +@@ -181,13 +182,79 @@ futex_init_timeout(u32 cmd, u32 op, struct timespec64 *ts, ktime_t *t) + return -EINVAL; + + *t = timespec64_to_ktime(*ts); +- if (cmd == FUTEX_WAIT) ++ if (cmd == FUTEX_WAIT || cmd == FUTEX_WAIT_MULTIPLE) + *t = ktime_add_safe(ktime_get(), *t); + else if (cmd != FUTEX_LOCK_PI && !(op & FUTEX_CLOCK_REALTIME)) + *t = timens_ktime_to_host(CLOCK_MONOTONIC, *t); + return 0; + } + ++/** ++ * futex_read_wait_block - Read an array of futex_wait_block from userspace ++ * @uaddr: Userspace address of the block ++ * @count: Number of blocks to be read ++ * ++ * This function creates and allocate an array of futex_q (we zero it to ++ * initialize the fields) and then, for each futex_wait_block element from ++ * userspace, fill a futex_q element with proper values. ++ */ ++inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) ++{ ++ unsigned int i; ++ struct futex_vector *futexv; ++ struct futex_wait_block fwb; ++ struct futex_wait_block __user *entry = ++ (struct futex_wait_block __user *)uaddr; ++ ++ if (!count || count > FUTEX_WAITV_MAX) ++ return ERR_PTR(-EINVAL); ++ ++ futexv = kcalloc(count, sizeof(*futexv), GFP_KERNEL); ++ if (!futexv) ++ return ERR_PTR(-ENOMEM); ++ ++ for (i = 0; i < count; i++) { ++ if (copy_from_user(&fwb, &entry[i], sizeof(fwb))) { ++ kfree(futexv); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ futexv[i].w.flags = FUTEX_32; ++ futexv[i].w.val = fwb.val; ++ futexv[i].w.uaddr = (uintptr_t) (fwb.uaddr); ++ futexv[i].q = futex_q_init; ++ } ++ ++ return futexv; ++} ++ ++int futex_wait_multiple(struct futex_vector *vs, unsigned int count, ++ struct hrtimer_sleeper *to); ++ ++int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) ++{ ++ int ret; ++ struct futex_vector *vs; ++ struct hrtimer_sleeper *to = NULL, timeout; ++ ++ to = futex_setup_timer(abs_time, &timeout, 0, 0); ++ ++ vs = futex_read_wait_block(uaddr, count); ++ ++ if (IS_ERR(vs)) ++ return PTR_ERR(vs); ++ ++ ret = futex_wait_multiple(vs, count, abs_time ? to : NULL); ++ kfree(vs); ++ ++ if (to) { ++ hrtimer_cancel(&to->timer); ++ destroy_hrtimer_on_stack(&to->timer); ++ } ++ ++ return ret; ++} ++ + SYSCALL_DEFINE6(futex, u32 __user *, uaddr, int, op, u32, val, + const struct __kernel_timespec __user *, utime, + u32 __user *, uaddr2, u32, val3) +@@ -207,6 +274,9 @@ SYSCALL_DEFINE6(futex, u32 __user *, uaddr, int, op, u32, val, + tp = &t; + } + ++ if (cmd == FUTEX_WAIT_MULTIPLE) ++ return futex_opcode_31(tp, uaddr, val); ++ + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } + +@@ -521,6 +591,9 @@ SYSCALL_DEFINE6(futex_time32, u32 __user *, uaddr, int, op, u32, val, + tp = &t; + } + ++ if (cmd == FUTEX_WAIT_MULTIPLE) ++ return futex_opcode_31(tp, uaddr, val); ++ + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } + #endif /* CONFIG_COMPAT_32BIT_TIME */ diff --git a/pkgbuilds/linux-omarchy-bore/0851-futex-wait-multiple-fixes.patch b/pkgbuilds/linux-omarchy-bore/0851-futex-wait-multiple-fixes.patch new file mode 100644 index 0000000..671934a --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0851-futex-wait-multiple-fixes.patch @@ -0,0 +1,35 @@ +diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c +--- a/kernel/futex/syscalls.c ++++ b/kernel/futex/syscalls.c +@@ -198,7 +198,7 @@ + * initialize the fields) and then, for each futex_wait_block element from + * userspace, fill a futex_q element with proper values. + */ +-inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) ++static inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) + { + unsigned int i; + struct futex_vector *futexv; +@@ -231,19 +231,18 @@ + int futex_wait_multiple(struct futex_vector *vs, unsigned int count, + struct hrtimer_sleeper *to); + +-int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) ++static int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) + { + int ret; + struct futex_vector *vs; + struct hrtimer_sleeper *to = NULL, timeout; + +- to = futex_setup_timer(abs_time, &timeout, 0, 0); +- + vs = futex_read_wait_block(uaddr, count); +- + if (IS_ERR(vs)) + return PTR_ERR(vs); + ++ to = futex_setup_timer(abs_time, &timeout, 0, 0); ++ + ret = futex_wait_multiple(vs, count, abs_time ? to : NULL); + kfree(vs); + diff --git a/pkgbuilds/linux-omarchy-bore/0852-futex-wait-multiple-abi-fixes.patch b/pkgbuilds/linux-omarchy-bore/0852-futex-wait-multiple-abi-fixes.patch new file mode 100644 index 0000000..b0d6ba3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0852-futex-wait-multiple-abi-fixes.patch @@ -0,0 +1,88 @@ +diff --git a/include/uapi/linux/futex.h b/include/uapi/linux/futex.h +--- a/include/uapi/linux/futex.h ++++ b/include/uapi/linux/futex.h +@@ -134,7 +134,7 @@ + * @bitset: Bitset for the optional bitmasked wakeup + */ + struct futex_wait_block { +- __u32 __user *uaddr; ++ __aligned_u64 uaddr; + __u32 val; + __u32 bitset; + }; +diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c +--- a/kernel/futex/syscalls.c ++++ b/kernel/futex/syscalls.c +@@ -198,7 +198,8 @@ + * initialize the fields) and then, for each futex_wait_block element from + * userspace, fill a futex_q element with proper values. + */ +-static inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) ++static inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count, ++ unsigned int flags) + { + unsigned int i; + struct futex_vector *futexv; +@@ -219,10 +220,17 @@ + return ERR_PTR(-EFAULT); + } + +- futexv[i].w.flags = FUTEX_32; ++ if (!fwb.bitset) { ++ kfree(futexv); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ futexv[i].w.flags = flags; + futexv[i].w.val = fwb.val; +- futexv[i].w.uaddr = (uintptr_t) (fwb.uaddr); ++ futexv[i].w.uaddr = fwb.uaddr; + futexv[i].q = futex_q_init; ++ futexv[i].q.bitset = fwb.bitset; ++ futexv[i].q.wake = futex_wake_mark; + } + + return futexv; +@@ -231,17 +239,21 @@ + int futex_wait_multiple(struct futex_vector *vs, unsigned int count, + struct hrtimer_sleeper *to); + +-static int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) ++static int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count, int op) + { + int ret; + struct futex_vector *vs; + struct hrtimer_sleeper *to = NULL, timeout; ++ unsigned int flags = futex_to_flags(op); ++ ++ if (flags & FLAGS_CLOCKRT) ++ return -ENOSYS; + +- vs = futex_read_wait_block(uaddr, count); ++ vs = futex_read_wait_block(uaddr, count, flags); + if (IS_ERR(vs)) + return PTR_ERR(vs); + +- to = futex_setup_timer(abs_time, &timeout, 0, 0); ++ to = futex_setup_timer(abs_time, &timeout, flags, current->timer_slack_ns); + + ret = futex_wait_multiple(vs, count, abs_time ? to : NULL); + kfree(vs); +@@ -274,7 +286,7 @@ + } + + if (cmd == FUTEX_WAIT_MULTIPLE) +- return futex_opcode_31(tp, uaddr, val); ++ return futex_opcode_31(tp, uaddr, val, op); + + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } +@@ -591,7 +603,7 @@ + } + + if (cmd == FUTEX_WAIT_MULTIPLE) +- return futex_opcode_31(tp, uaddr, val); ++ return futex_opcode_31(tp, uaddr, val, op); + + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } diff --git a/pkgbuilds/linux-omarchy-bore/8201-xe-shrinker-return-freed-page-count.patch b/pkgbuilds/linux-omarchy-bore/8201-xe-shrinker-return-freed-page-count.patch new file mode 100644 index 0000000..f340536 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8201-xe-shrinker-return-freed-page-count.patch @@ -0,0 +1,132 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -54,13 +54,14 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea + write_unlock(&shrinker->lock); + } + +-static s64 __xe_shrinker_walk(struct xe_device *xe, ++static int __xe_shrinker_walk(struct xe_device *xe, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, +- unsigned long to_scan, unsigned long *scanned) ++ unsigned long to_scan, unsigned long *scanned, ++ unsigned long *freed) + { + unsigned int mem_type; +- s64 freed = 0, lret; ++ s64 lret; + + for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) { + struct ttm_resource_manager *man = ttm_manager_type(&xe->ttm, mem_type); +@@ -82,7 +83,7 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, + if (lret < 0) + return lret; + +- freed += lret; ++ *freed += lret; + if (*scanned >= to_scan) + break; + } +@@ -90,7 +91,7 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, + xe_assert(xe, !IS_ERR(ttm_bo)); + } + +- return freed; ++ return 0; + } + + /* +@@ -99,40 +100,35 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, + * add writeback. This avoids stalls and explicit writebacks with light or + * moderate memory pressure. + */ +-static s64 xe_shrinker_walk(struct xe_device *xe, ++static int xe_shrinker_walk(struct xe_device *xe, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, +- unsigned long to_scan, unsigned long *scanned) ++ unsigned long to_scan, unsigned long *scanned, ++ unsigned long *freed) + { + bool no_wait_gpu = true; + struct xe_bo_shrink_flags save_flags = flags; +- s64 lret, freed; ++ int ret; + + swap(no_wait_gpu, ctx->no_wait_gpu); + save_flags.writeback = false; +- lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned); ++ ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, freed); + swap(no_wait_gpu, ctx->no_wait_gpu); +- if (lret < 0 || *scanned >= to_scan) +- return lret; ++ if (ret || *scanned >= to_scan) ++ return ret; + +- freed = lret; + if (!ctx->no_wait_gpu) { +- lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned); +- if (lret < 0) +- return lret; +- freed += lret; +- if (*scanned >= to_scan) +- return freed; ++ ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, ++ freed); ++ if (ret || *scanned >= to_scan) ++ return ret; + } + +- if (flags.writeback) { +- lret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned); +- if (lret < 0) +- return lret; +- freed += lret; +- } ++ if (flags.writeback) ++ ret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned, ++ freed); + +- return freed; ++ return ret; + } + + static unsigned long +@@ -214,7 +210,6 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + bool runtime_pm; + bool purgeable; + bool can_backup = !!(sc->gfp_mask & __GFP_FS); +- s64 lret; + + nr_to_scan = sc->nr_to_scan; + +@@ -225,12 +220,9 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + /* Might need runtime PM. Try to wake early if it looks like it. */ + runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup); + +- if (purgeable && nr_scanned < nr_to_scan) { +- lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, +- nr_to_scan, &nr_scanned); +- if (lret >= 0) +- freed += lret; +- } ++ if (purgeable && nr_scanned < nr_to_scan) ++ xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; + if (nr_scanned >= nr_to_scan || !can_backup) +@@ -242,10 +234,8 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + + shrink_flags.purge = false; + +- lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, +- nr_to_scan, &nr_scanned); +- if (lret >= 0) +- freed += lret; ++ xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; + out: diff --git a/pkgbuilds/linux-omarchy-bore/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch b/pkgbuilds/linux-omarchy-bore/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch new file mode 100644 index 0000000..29ccecf --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch @@ -0,0 +1,149 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -54,13 +54,33 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea + write_unlock(&shrinker->lock); + } + +-static int __xe_shrinker_walk(struct xe_device *xe, ++static bool __xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker) ++{ ++ struct xe_device *xe = shrinker->xe; ++ ++ if (xe_pm_runtime_get_if_active(xe)) ++ return true; ++ ++ if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) { ++ xe_pm_runtime_get(xe); ++ return true; ++ } ++ ++ queue_work(xe->unordered_wq, &shrinker->pm_worker); ++ ++ return false; ++} ++ ++static int __xe_shrinker_walk(struct xe_shrinker *shrinker, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, + unsigned long to_scan, unsigned long *scanned, + unsigned long *freed) + { ++ struct xe_device *xe = shrinker->xe; + unsigned int mem_type; ++ bool rpm = false; ++ int ret = 0; + s64 lret; + + for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) { +@@ -75,23 +95,36 @@ static int __xe_shrinker_walk(struct xe_device *xe, + if (!man || !man->use_tt) + continue; + ++ if (mem_type != XE_PL_SYSTEM && !rpm && ++ xe_device_is_l2_flush_optimized(xe)) { ++ if (!__xe_shrinker_runtime_pm_get(shrinker)) ++ break; ++ rpm = true; ++ } ++ + ttm_bo_lru_for_each_reserved_guarded(&curs, man, &arg, ttm_bo) { + if (!ttm_bo_shrink_suitable(ttm_bo, ctx)) + continue; + + lret = xe_bo_shrink(ctx, ttm_bo, flags, scanned); +- if (lret < 0) +- return lret; ++ if (lret < 0) { ++ ret = lret; ++ goto out; ++ } + + *freed += lret; + if (*scanned >= to_scan) +- break; ++ goto out; + } + /* Trylocks should never error, just fail. */ + xe_assert(xe, !IS_ERR(ttm_bo)); + } + +- return 0; ++out: ++ if (rpm) ++ xe_pm_runtime_put(xe); ++ ++ return ret; + } + + /* +@@ -100,7 +133,7 @@ static int __xe_shrinker_walk(struct xe_device *xe, + * add writeback. This avoids stalls and explicit writebacks with light or + * moderate memory pressure. + */ +-static int xe_shrinker_walk(struct xe_device *xe, ++static int xe_shrinker_walk(struct xe_shrinker *shrinker, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, + unsigned long to_scan, unsigned long *scanned, +@@ -112,20 +145,21 @@ static int xe_shrinker_walk(struct xe_device *xe, + + swap(no_wait_gpu, ctx->no_wait_gpu); + save_flags.writeback = false; +- ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, freed); ++ ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned, ++ freed); + swap(no_wait_gpu, ctx->no_wait_gpu); + if (ret || *scanned >= to_scan) + return ret; + + if (!ctx->no_wait_gpu) { +- ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, ++ ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned, + freed); + if (ret || *scanned >= to_scan) + return ret; + } + + if (flags.writeback) +- ret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned, ++ ret = __xe_shrinker_walk(shrinker, ctx, flags, to_scan, scanned, + freed); + + return ret; +@@ -176,16 +210,7 @@ static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force, + return false; + } + +- if (!xe_pm_runtime_get_if_active(xe)) { +- if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) { +- xe_pm_runtime_get(xe); +- return true; +- } +- queue_work(xe->unordered_wq, &shrinker->pm_worker); +- return false; +- } +- +- return true; ++ return __xe_shrinker_runtime_pm_get(shrinker); + } + + static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) +@@ -221,7 +246,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup); + + if (purgeable && nr_scanned < nr_to_scan) +- xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ xe_shrinker_walk(shrinker, &ctx, shrink_flags, + nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; +@@ -234,7 +259,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + + shrink_flags.purge = false; + +- xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ xe_shrinker_walk(shrinker, &ctx, shrink_flags, + nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; diff --git a/pkgbuilds/linux-omarchy-bore/8203-xe-shrinker-release-through-the-put-helper.patch b/pkgbuilds/linux-omarchy-bore/8203-xe-shrinker-release-through-the-put-helper.patch new file mode 100644 index 0000000..6af456f --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8203-xe-shrinker-release-through-the-put-helper.patch @@ -0,0 +1,39 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -71,6 +71,12 @@ static bool __xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker) + return false; + } + ++static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) ++{ ++ if (runtime_pm) ++ xe_pm_runtime_put(shrinker->xe); ++} ++ + static int __xe_shrinker_walk(struct xe_shrinker *shrinker, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, +@@ -121,8 +127,7 @@ static int __xe_shrinker_walk(struct xe_shrinker *shrinker, + } + + out: +- if (rpm) +- xe_pm_runtime_put(xe); ++ xe_shrinker_runtime_pm_put(shrinker, rpm); + + return ret; + } +@@ -213,12 +218,6 @@ static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force, + return __xe_shrinker_runtime_pm_get(shrinker); + } + +-static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) +-{ +- if (runtime_pm) +- xe_pm_runtime_put(shrinker->xe); +-} +- + static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc) + { + struct xe_shrinker *shrinker = to_xe_shrinker(shrink); diff --git a/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.patch b/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.patch new file mode 100644 index 0000000..0a31aa0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.patch @@ -0,0 +1,381 @@ +diff --git c/include/linux/mmzone.h i/include/linux/mmzone.h +--- c/include/linux/mmzone.h ++++ i/include/linux/mmzone.h +@@ -1465,6 +1465,39 @@ struct memory_failure_stats { + }; + #endif + ++/* ++ * Per-pgdat state machine for the kswapd "opportunistic compaction" hint. ++ * ++ * wakeup_kswapd() collapses the gfp flags of all wakers that arrive between ++ * two kswapd runs into a single tri-state, which kswapd then forwards to the ++ * shrinkers via shrink_control::opportunistic_compaction: ++ * ++ * KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION ++ * Initial state after kswapd consumes the previous value. No waker has ++ * been observed yet for the upcoming run. ++ * ++ * KSWAPD_NO_OPPORTUNISTIC_COMPACTION ++ * At least one waker is an order-0 allocation, or a high-order ++ * allocation that cannot tolerate failure (i.e., not eligible for ++ * opportunistic behaviour). Shrinkers must do their normal best-effort ++ * work; the hint is cleared. ++ * ++ * KSWAPD_OPPORTUNISTIC_COMPACTION ++ * All wakers seen so far are high-order allocations that may fail ++ * (__GFP_NORETRY or __GFP_RETRY_MAYFAIL, without __GFP_NOFAIL). Shrinkers ++ * may skip work that is unlikely to produce a contiguous high-order ++ * block (e.g., evicting working-set pages). ++ * ++ * The state is sticky in the "NO" direction within a single kswapd run: once ++ * any non-eligible waker is observed, subsequent eligible wakers cannot ++ * upgrade it back to KSWAPD_OPPORTUNISTIC_COMPACTION. ++ */ ++enum kswapd_opportunistic_compaction_type { ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION = 0, ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION, ++ KSWAPD_OPPORTUNISTIC_COMPACTION, ++}; ++ + /* + * On NUMA machines, each NUMA node would have a pg_data_t to describe + * it's memory layout. On UMA machines there is a single pglist_data which +@@ -1529,6 +1562,13 @@ typedef struct pglist_data { + #endif + struct task_struct *kswapd; /* Protected by kswapd_lock */ + int kswapd_order; ++ /* ++ * Aggregated opportunistic-compaction hint for the next kswapd run. ++ * Updated by wakeup_kswapd() based on the gfp flags / order of each ++ * waker, and consumed (and reset) by kswapd before balance_pgdat(). ++ * See enum kswapd_opportunistic_compaction_type for the state machine. ++ */ ++ atomic_t kswapd_opportunistic_compaction; + enum zone_type kswapd_highest_zoneidx; + + atomic_t kswapd_failures; /* Number of 'reclaimed == 0' runs */ +diff --git c/include/linux/shrinker.h i/include/linux/shrinker.h +--- c/include/linux/shrinker.h ++++ i/include/linux/shrinker.h +@@ -37,6 +37,26 @@ struct shrink_control { + /* current node being shrunk (for NUMA aware shrinkers) */ + int nid; + ++ /* ++ * Opportunistic compaction hint. ++ * ++ * Set by the reclaim path to tell shrinkers that this pass is ++ * driven by an order > 0 allocation that the caller is willing to ++ * have fail (e.g., __GFP_NORETRY / __GFP_RETRY_MAYFAIL without ++ * __GFP_NOFAIL). Such allocations only really benefit from ++ * shrinking when doing so frees up a contiguous, high-order block; ++ * thrashing working sets in the hope of producing one is typically ++ * counter-productive. ++ * ++ * Shrinkers that can produce naturally-aligned high-order folios ++ * (see shrink_control::order) should treat this as a hint to skip ++ * costly work that is unlikely to help compaction (for example, ++ * evicting hot/working-set pages just to free single pages). ++ * ++ * Only meaningful when @order > 0; ignored otherwise. ++ */ ++ bool opportunistic_compaction; ++ + /* + * How many objects scan_objects should scan and try to reclaim. + * This is reset before every call, so it is safe for callees +diff --git c/mm/internal.h i/mm/internal.h +--- c/mm/internal.h ++++ i/mm/internal.h +@@ -1767,7 +1767,7 @@ void __meminit __init_page_from_nid(unsigned long pfn, int nid); + + /* shrinker related functions */ + unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, +- int priority); ++ int priority, bool opportunistic_compaction); + + int shmem_add_to_page_cache(struct folio *folio, + struct address_space *mapping, +diff --git c/mm/shrinker.c i/mm/shrinker.c +--- c/mm/shrinker.c ++++ i/mm/shrinker.c +@@ -474,7 +474,7 @@ static unsigned long do_shrink_slab(struct shrink_control *shrinkctl, + + #ifdef CONFIG_MEMCG + static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, +- struct mem_cgroup *memcg, int priority) ++ struct mem_cgroup *memcg, int priority, bool opportunistic_compaction) + { + struct shrinker_info *info; + unsigned long ret, freed = 0; +@@ -536,6 +536,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + .gfp_mask = gfp_mask, + .nid = nid, + .memcg = memcg, ++ .opportunistic_compaction = opportunistic_compaction, + }; + struct shrinker *shrinker; + int shrinker_id = calc_shrinker_id(index, offset); +@@ -595,7 +596,8 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + } + #else /* !CONFIG_MEMCG */ + static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, +- struct mem_cgroup *memcg, int priority) ++ struct mem_cgroup *memcg, int priority, ++ bool opportunistic_compaction) + { + return 0; + } +@@ -607,6 +609,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + * @nid: node whose slab caches to target + * @memcg: memory cgroup whose slab caches to target + * @priority: the reclaim priority ++ * @opportunistic_compaction: do compaction opportunistically (e.g., do not swap working sets) + * + * Call the shrink functions to age shrinkable caches. + * +@@ -622,7 +625,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + * Returns the number of reclaimed slab objects. + */ + unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, +- int priority) ++ int priority, bool opportunistic_compaction) + { + unsigned long ret, freed = 0; + struct shrinker *shrinker; +@@ -635,7 +638,8 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, + * oom. + */ + if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg)) +- return shrink_slab_memcg(gfp_mask, nid, memcg, priority); ++ return shrink_slab_memcg(gfp_mask, nid, memcg, priority, ++ opportunistic_compaction); + + /* + * lockless algorithm of global shrink. +@@ -664,6 +668,7 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, + .gfp_mask = gfp_mask, + .nid = nid, + .memcg = memcg, ++ .opportunistic_compaction = opportunistic_compaction, + }; + + if (!shrinker_try_get(shrinker)) +diff --git c/mm/vmscan.c i/mm/vmscan.c +--- c/mm/vmscan.c ++++ i/mm/vmscan.c +@@ -96,6 +96,14 @@ struct scan_control { + /* Swappiness value for proactive reclaim. Always use sc_swappiness()! */ + int *proactive_swappiness; + ++ /* ++ * Opportunistic compaction hint snapshotted from the pgdat at the ++ * start of this reclaim pass. Forwarded to shrinkers through ++ * shrink_control::opportunistic_compaction so they can skip ++ * non-productive work for failable high-order allocations. ++ */ ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction; ++ + /* Can active folios be deactivated as part of reclaim? */ + #define DEACTIVATE_ANON 1 + #define DEACTIVATE_FILE 2 +@@ -198,6 +206,29 @@ struct scan_control { + */ + int vm_swappiness = 60; + ++/* ++ * Is @gfp_flags a high-order allocation that is eligible for the ++ * "opportunistic compaction" treatment in kswapd / shrinkers? ++ * ++ * The caller must be willing to tolerate failure (__GFP_NORETRY or ++ * __GFP_RETRY_MAYFAIL) and must not have set __GFP_NOFAIL. For such ++ * allocations there is little value in burning working-set pages just to ++ * scrape together a single high-order block: if compaction can't easily ++ * succeed, the caller would rather see the allocation fail. ++ */ ++static bool gfp_opportunistic_compaction(gfp_t gfp_flags) ++{ ++ return (gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)) && ++ !(gfp_flags & __GFP_NOFAIL); ++} ++ ++static bool sc_opportunistic_compaction(struct scan_control *sc) ++{ ++ return sc->order && (sc->kswapd_opportunistic_compaction == ++ KSWAPD_OPPORTUNISTIC_COMPACTION || (!current_is_kswapd() && ++ gfp_opportunistic_compaction(sc->gfp_mask))); ++} ++ + static int sc_swappiness(struct scan_control *sc, struct mem_cgroup *memcg) + { + if (sc->proactive && sc->proactive_swappiness) +@@ -411,7 +442,7 @@ static unsigned long drop_slab_node(int nid) + + memcg = mem_cgroup_iter(NULL, NULL, NULL); + do { +- freed += shrink_slab(GFP_KERNEL, nid, memcg, 0); ++ freed += shrink_slab(GFP_KERNEL, nid, memcg, 0, false); + } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL); + + return freed; +@@ -5081,6 +5112,7 @@ static int shrink_one(struct lruvec *lruvec, struct scan_control *sc) + unsigned long reclaimed = sc->nr_reclaimed; + struct mem_cgroup *memcg = lruvec_memcg(lruvec); + struct pglist_data *pgdat = lruvec_pgdat(lruvec); ++ bool opportunistic_compaction = sc_opportunistic_compaction(sc); + + /* lru_gen_age_node() called mem_cgroup_calculate_protection() */ + if (mem_cgroup_below_min(NULL, memcg)) +@@ -5096,7 +5128,8 @@ static int shrink_one(struct lruvec *lruvec, struct scan_control *sc) + + need_rotate = try_to_shrink_lruvec(lruvec, sc); + +- shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, sc->priority); ++ shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, sc->priority, ++ opportunistic_compaction); + + if (!sc->proactive) + vmpressure(sc->gfp_mask, sc->order, memcg, false, +@@ -6163,6 +6196,7 @@ static void shrink_node_memcgs(pg_data_t *pgdat, struct scan_control *sc) + struct lruvec *lruvec = mem_cgroup_lruvec(memcg, pgdat); + unsigned long reclaimed; + unsigned long scanned; ++ bool opportunistic_compaction = sc_opportunistic_compaction(sc); + + /* + * This loop can become CPU-bound when target memcgs +@@ -6200,7 +6234,7 @@ static void shrink_node_memcgs(pg_data_t *pgdat, struct scan_control *sc) + shrink_lruvec(lruvec, sc); + + shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, +- sc->priority); ++ sc->priority, opportunistic_compaction); + + /* Record the group's reclaim efficiency */ + if (!sc->proactive) +@@ -7133,8 +7167,14 @@ clear_reclaim_active(pg_data_t *pgdat, int highest_zoneidx) + * found to have free_pages <= high_wmark_pages(zone), any page in that zone + * or lower is eligible for reclaim until at least one usable zone is + * balanced. ++ * ++ * @kswapd_opportunistic_compaction is the aggregated hint produced by ++ * wakeup_kswapd() for this run; it is propagated into scan_control so that ++ * shrinkers can skip costly work that is unlikely to help compaction when ++ * all wakers are failable high-order allocations. + */ +-static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx) ++static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx, ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction) + { + int i; + unsigned long nr_soft_reclaimed; +@@ -7148,6 +7188,7 @@ static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx) + .gfp_mask = GFP_KERNEL, + .order = order, + .may_unmap = 1, ++ .kswapd_opportunistic_compaction = kswapd_opportunistic_compaction, + }; + + trace_mm_vmscan_balance_pgdat_begin(pgdat->node_id, order, +@@ -7372,8 +7413,10 @@ static enum zone_type kswapd_highest_zoneidx(pg_data_t *pgdat, + return curr_idx == MAX_NR_ZONES ? prev_highest_zoneidx : curr_idx; + } + +-static void kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, +- unsigned int highest_zoneidx) ++static void ++kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, ++ unsigned int highest_zoneidx, ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction) + { + long remaining = 0; + DEFINE_WAIT(wait); +@@ -7419,6 +7462,11 @@ static void kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_o + + if (READ_ONCE(pgdat->kswapd_order) < reclaim_order) + WRITE_ONCE(pgdat->kswapd_order, reclaim_order); ++ ++ if (kswapd_opportunistic_compaction == ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION) ++ atomic_set(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION); + } + + finish_wait(&pgdat->kswapd_wait, &wait); +@@ -7475,6 +7523,7 @@ static int kswapd(void *p) + unsigned int highest_zoneidx = MAX_NR_ZONES - 1; + pg_data_t *pgdat = (pg_data_t *)p; + struct task_struct *tsk = current; ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction; + + /* + * Tell the memory management that we're a "memory allocator", +@@ -7492,6 +7541,8 @@ static int kswapd(void *p) + set_freezable(); + + WRITE_ONCE(pgdat->kswapd_order, 0); ++ atomic_set(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, MAX_NR_ZONES); + atomic_set(&pgdat->nr_writeback_throttled, 0); + for ( ; ; ) { +@@ -7500,13 +7551,18 @@ static int kswapd(void *p) + alloc_order = reclaim_order = READ_ONCE(pgdat->kswapd_order); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); ++ kswapd_opportunistic_compaction = ++ atomic_read(&pgdat->kswapd_opportunistic_compaction); + + kswapd_try_sleep: + kswapd_try_to_sleep(pgdat, alloc_order, reclaim_order, +- highest_zoneidx); ++ highest_zoneidx, kswapd_opportunistic_compaction); + + /* Read the new order and highest_zoneidx */ + alloc_order = READ_ONCE(pgdat->kswapd_order); ++ kswapd_opportunistic_compaction = ++ atomic_xchg(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); + WRITE_ONCE(pgdat->kswapd_order, 0); +@@ -7533,7 +7589,8 @@ static int kswapd(void *p) + trace_mm_vmscan_kswapd_wake(pgdat->node_id, highest_zoneidx, + alloc_order); + reclaim_order = balance_pgdat(pgdat, alloc_order, +- highest_zoneidx); ++ highest_zoneidx, ++ kswapd_opportunistic_compaction); + if (reclaim_order < alloc_order) + goto kswapd_try_sleep; + } +@@ -7571,6 +7628,28 @@ void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, + if (READ_ONCE(pgdat->kswapd_order) < order) + WRITE_ONCE(pgdat->kswapd_order, order); + ++ /* ++ * Fold this waker into the per-pgdat opportunistic-compaction hint ++ * that kswapd will pick up at the start of its next run. ++ * ++ * The state is sticky in the "NO" direction: once any waker in this ++ * batch is order-0 or a non-failable high-order allocation, the hint ++ * stays cleared until kswapd consumes it. Only when every waker so ++ * far is a failable high-order allocation do we set ++ * KSWAPD_OPPORTUNISTIC_COMPACTION, asking shrinkers to skip work ++ * that won't realistically help compaction. ++ */ ++ if (atomic_read(&pgdat->kswapd_opportunistic_compaction) != ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION) { ++ if (!order || !gfp_opportunistic_compaction(gfp_flags)) ++ atomic_set(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION); ++ else if (order && gfp_opportunistic_compaction(gfp_flags)) ++ atomic_cmpxchg(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION, ++ KSWAPD_OPPORTUNISTIC_COMPACTION); ++ } ++ + if (!waitqueue_active(&pgdat->kswapd_wait)) + return; + diff --git a/pkgbuilds/linux-omarchy-bore/8205-mm-hint-uses-allocation-order.patch b/pkgbuilds/linux-omarchy-bore/8205-mm-hint-uses-allocation-order.patch new file mode 100644 index 0000000..f579b27 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8205-mm-hint-uses-allocation-order.patch @@ -0,0 +1,236 @@ +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h +@@ -1680,7 +1680,7 @@ enum kswapd_clear_hopeless_reason { + }; + + void wakeup_kswapd(struct zone *zone, gfp_t gfp_mask, int order, +- enum zone_type highest_zoneidx); ++ bool opportunistic, enum zone_type highest_zoneidx); + void kswapd_try_clear_hopeless(struct pglist_data *pgdat, + unsigned int order, int highest_zoneidx); + void kswapd_clear_hopeless(pg_data_t *pgdat, enum kswapd_clear_hopeless_reason reason); +diff --git a/include/linux/swap.h b/include/linux/swap.h +--- a/include/linux/swap.h ++++ b/include/linux/swap.h +@@ -348,7 +348,8 @@ extern void swap_setup(void); + /* linux/mm/vmscan.c */ + extern unsigned long zone_reclaimable_pages(struct zone *zone); + extern unsigned long try_to_free_pages(struct zonelist *zonelist, int order, +- gfp_t gfp_mask, nodemask_t *mask); ++ int alloc_order, gfp_t gfp_mask, ++ nodemask_t *mask); + unsigned long lruvec_lru_size(struct lruvec *lruvec, enum lru_list lru, int zone_idx); + + #define MEMCG_RECLAIM_MAY_SWAP (1 << 1) +diff --git a/mm/internal.h b/mm/internal.h +--- a/mm/internal.h ++++ b/mm/internal.h +@@ -1765,6 +1765,28 @@ void __meminit __init_single_page(struct page *page, unsigned long pfn, + unsigned long zone, int nid); + void __meminit __init_page_from_nid(unsigned long pfn, int nid); + ++/* ++ * Is this allocation eligible for the "opportunistic compaction" treatment ++ * in kswapd and the shrinkers? ++ * ++ * The caller must be willing to tolerate failure (__GFP_NORETRY or ++ * __GFP_RETRY_MAYFAIL) and must not have set __GFP_NOFAIL. For such ++ * allocations there is little value in burning working-set pages just to ++ * scrape together a single high-order block: if compaction can't easily ++ * succeed, the caller would rather see the allocation fail. ++ * ++ * @order is the order the caller asked for, not the order reclaim was ++ * promoted to. defrag_mode raises the reclaim order to pageblock_order, ++ * which says nothing about what the caller can tolerate. ++ */ ++static inline bool gfp_opportunistic_compaction(gfp_t gfp_flags, int order) ++{ ++ if (!order || (gfp_flags & __GFP_NOFAIL)) ++ return false; ++ ++ return !!(gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)); ++} ++ + /* shrinker related functions */ + unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, + int priority, bool opportunistic_compaction); +diff --git a/mm/migrate.c b/mm/migrate.c +--- a/mm/migrate.c ++++ b/mm/migrate.c +@@ -2727,7 +2727,7 @@ int migrate_misplaced_folio_prepare(struct folio *folio, + return -EAGAIN; + + wakeup_kswapd(pgdat->node_zones + z, 0, +- folio_order(folio), ZONE_MOVABLE); ++ folio_order(folio), false, ZONE_MOVABLE); + return -EAGAIN; + } + +diff --git a/mm/page_alloc.c b/mm/page_alloc.c +--- a/mm/page_alloc.c ++++ b/mm/page_alloc.c +@@ -3422,7 +3422,7 @@ struct page *rmqueue(struct zone *preferred_zone, + if ((alloc_flags & ALLOC_KSWAPD) && + unlikely(test_bit(ZONE_BOOSTED_WATERMARK, &zone->flags))) { + clear_bit(ZONE_BOOSTED_WATERMARK, &zone->flags); +- wakeup_kswapd(zone, 0, 0, zone_idx(zone)); ++ wakeup_kswapd(zone, 0, 0, false, zone_idx(zone)); + } + + VM_BUG_ON_PAGE(page && bad_range(zone, page), page); +@@ -4441,6 +4441,7 @@ static unsigned int check_retry_zonelist(unsigned int seq) + /* Perform direct synchronous page reclaim */ + static unsigned long + __perform_reclaim(gfp_t gfp_mask, unsigned int order, ++ unsigned int alloc_order, + const struct alloc_context *ac) + { + unsigned int noreclaim_flag; +@@ -4453,7 +4454,7 @@ __perform_reclaim(gfp_t gfp_mask, unsigned int order, + fs_reclaim_acquire(gfp_mask); + noreclaim_flag = memalloc_noreclaim_save(); + +- progress = try_to_free_pages(ac->zonelist, order, gfp_mask, ++ progress = try_to_free_pages(ac->zonelist, order, alloc_order, gfp_mask, + ac->nodemask); + + memalloc_noreclaim_restore(noreclaim_flag); +@@ -4480,7 +4481,7 @@ __alloc_pages_direct_reclaim(gfp_t gfp_mask, unsigned int order, + reclaim_order = max(order, pageblock_order); + + psi_memstall_enter(&pflags); +- *did_some_progress = __perform_reclaim(gfp_mask, reclaim_order, ac); ++ *did_some_progress = __perform_reclaim(gfp_mask, reclaim_order, order, ac); + if (unlikely(!(*did_some_progress))) + goto out; + +@@ -4512,19 +4513,24 @@ static void wake_all_kswapds(unsigned int order, gfp_t gfp_mask, + pg_data_t *last_pgdat = NULL; + enum zone_type highest_zoneidx = ac->highest_zoneidx; + unsigned int reclaim_order; ++ bool opportunistic; + + if (defrag_mode) + reclaim_order = max(order, pageblock_order); + else + reclaim_order = order; + ++ /* Classify what the caller asked for, not what reclaim was raised to. */ ++ opportunistic = gfp_opportunistic_compaction(gfp_mask, order); ++ + for_each_zone_zonelist_nodemask(zone, z, ac->zonelist, highest_zoneidx, + ac->nodemask) { + if (!managed_zone(zone)) + continue; + if (last_pgdat == zone->zone_pgdat) + continue; +- wakeup_kswapd(zone, gfp_mask, reclaim_order, highest_zoneidx); ++ wakeup_kswapd(zone, gfp_mask, reclaim_order, opportunistic, ++ highest_zoneidx); + last_pgdat = zone->zone_pgdat; + } + } +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -160,6 +160,12 @@ struct scan_control { + /* Allocation order */ + s8 order; + ++ /* ++ * The order the allocation asked for. @order above may have been ++ * raised to pageblock_order by defrag_mode. ++ */ ++ s8 alloc_order; ++ + /* Scan (total_size >> priority) pages at once */ + s8 priority; + +@@ -206,27 +212,19 @@ struct scan_control { + */ + int vm_swappiness = 60; + +-/* +- * Is @gfp_flags a high-order allocation that is eligible for the +- * "opportunistic compaction" treatment in kswapd / shrinkers? +- * +- * The caller must be willing to tolerate failure (__GFP_NORETRY or +- * __GFP_RETRY_MAYFAIL) and must not have set __GFP_NOFAIL. For such +- * allocations there is little value in burning working-set pages just to +- * scrape together a single high-order block: if compaction can't easily +- * succeed, the caller would rather see the allocation fail. +- */ +-static bool gfp_opportunistic_compaction(gfp_t gfp_flags) +-{ +- return (gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)) && +- !(gfp_flags & __GFP_NOFAIL); +-} +- + static bool sc_opportunistic_compaction(struct scan_control *sc) + { +- return sc->order && (sc->kswapd_opportunistic_compaction == +- KSWAPD_OPPORTUNISTIC_COMPACTION || (!current_is_kswapd() && +- gfp_opportunistic_compaction(sc->gfp_mask))); ++ if (!sc->order) ++ return false; ++ ++ if (sc->kswapd_opportunistic_compaction == ++ KSWAPD_OPPORTUNISTIC_COMPACTION) ++ return true; ++ ++ if (current_is_kswapd()) ++ return false; ++ ++ return gfp_opportunistic_compaction(sc->gfp_mask, sc->alloc_order); + } + + static int sc_swappiness(struct scan_control *sc, struct mem_cgroup *memcg) +@@ -6781,7 +6779,8 @@ static bool throttle_direct_reclaim(gfp_t gfp_mask, struct zonelist *zonelist, + } + + unsigned long try_to_free_pages(struct zonelist *zonelist, int order, +- gfp_t gfp_mask, nodemask_t *nodemask) ++ int alloc_order, gfp_t gfp_mask, ++ nodemask_t *nodemask) + { + unsigned long nr_reclaimed; + struct scan_control sc = { +@@ -6789,6 +6788,7 @@ unsigned long try_to_free_pages(struct zonelist *zonelist, int order, + .gfp_mask = current_gfp_context(gfp_mask), + .reclaim_idx = gfp_zone(gfp_mask), + .order = order, ++ .alloc_order = alloc_order, + .nodemask = nodemask, + .priority = DEF_PRIORITY, + .may_writepage = 1, +@@ -7608,7 +7608,7 @@ static int kswapd(void *p) + * needed. + */ + void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, +- enum zone_type highest_zoneidx) ++ bool opportunistic, enum zone_type highest_zoneidx) + { + pg_data_t *pgdat; + enum zone_type curr_idx; +@@ -7641,10 +7641,10 @@ void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, + */ + if (atomic_read(&pgdat->kswapd_opportunistic_compaction) != + KSWAPD_NO_OPPORTUNISTIC_COMPACTION) { +- if (!order || !gfp_opportunistic_compaction(gfp_flags)) ++ if (!opportunistic) + atomic_set(&pgdat->kswapd_opportunistic_compaction, + KSWAPD_NO_OPPORTUNISTIC_COMPACTION); +- else if (order && gfp_opportunistic_compaction(gfp_flags)) ++ else + atomic_cmpxchg(&pgdat->kswapd_opportunistic_compaction, + KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION, + KSWAPD_OPPORTUNISTIC_COMPACTION); +@@ -7942,6 +7942,7 @@ int node_reclaim(struct pglist_data *pgdat, gfp_t gfp_mask, unsigned int order) + .nr_to_reclaim = max(nr_pages, SWAP_CLUSTER_MAX), + .gfp_mask = current_gfp_context(gfp_mask), + .order = order, ++ .alloc_order = order, + .priority = NODE_RECLAIM_PRIORITY, + .may_writepage = !!(node_reclaim_mode & RECLAIM_WRITE), + .may_unmap = !!(node_reclaim_mode & RECLAIM_UNMAP), diff --git a/pkgbuilds/linux-omarchy-bore/8206-mm-carry-order-and-hint-in-one-word.patch b/pkgbuilds/linux-omarchy-bore/8206-mm-carry-order-and-hint-in-one-word.patch new file mode 100644 index 0000000..92b39ef --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8206-mm-carry-order-and-hint-in-one-word.patch @@ -0,0 +1,232 @@ +diff --git c/include/linux/mmzone.h i/include/linux/mmzone.h +--- c/include/linux/mmzone.h ++++ i/include/linux/mmzone.h +@@ -1498,6 +1498,32 @@ enum kswapd_opportunistic_compaction_type { + KSWAPD_OPPORTUNISTIC_COMPACTION, + }; + ++/* ++ * pgdat->kswapd_request holds the order and the hint above in one word. A ++ * waker folds both in with a single cmpxchg and kswapd takes both with a ++ * single exchange, so a run cannot serve one waker's order under another ++ * waker's hint. ++ */ ++#define KSWAPD_REQUEST_HINT_MASK 3 ++#define KSWAPD_REQUEST_ORDER_SHIFT 2 ++ ++static inline int kswapd_request(int order, ++ enum kswapd_opportunistic_compaction_type hint) ++{ ++ return (order << KSWAPD_REQUEST_ORDER_SHIFT) | hint; ++} ++ ++static inline int kswapd_request_order(int request) ++{ ++ return request >> KSWAPD_REQUEST_ORDER_SHIFT; ++} ++ ++static inline enum kswapd_opportunistic_compaction_type ++kswapd_request_hint(int request) ++{ ++ return request & KSWAPD_REQUEST_HINT_MASK; ++} ++ + /* + * On NUMA machines, each NUMA node would have a pg_data_t to describe + * it's memory layout. On UMA machines there is a single pglist_data which +@@ -1561,14 +1587,15 @@ typedef struct pglist_data { + struct mutex kswapd_lock; + #endif + struct task_struct *kswapd; /* Protected by kswapd_lock */ +- int kswapd_order; + /* +- * Aggregated opportunistic-compaction hint for the next kswapd run. +- * Updated by wakeup_kswapd() based on the gfp flags / order of each +- * waker, and consumed (and reset) by kswapd before balance_pgdat(). +- * See enum kswapd_opportunistic_compaction_type for the state machine. ++ * The next kswapd run's request: the highest order asked for since ++ * kswapd last consumed one, and the aggregated opportunistic- ++ * compaction hint, in one word. Updated by wakeup_kswapd() from the ++ * gfp flags and order of each waker, and consumed by kswapd before ++ * balance_pgdat(). See kswapd_request() and ++ * enum kswapd_opportunistic_compaction_type. + */ +- atomic_t kswapd_opportunistic_compaction; ++ atomic_t kswapd_request; + enum zone_type kswapd_highest_zoneidx; + + atomic_t kswapd_failures; /* Number of 'reclaimed == 0' runs */ +diff --git c/mm/mm_init.c i/mm/mm_init.c +--- c/mm/mm_init.c ++++ i/mm/mm_init.c +@@ -1553,7 +1553,8 @@ void __ref free_area_init_core_hotplug(struct pglist_data *pgdat) + * when it starts in the near future. + */ + pgdat->nr_zones = 0; +- pgdat->kswapd_order = 0; ++ atomic_set(&pgdat->kswapd_request, ++ kswapd_request(0, KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION)); + pgdat->kswapd_highest_zoneidx = 0; + pgdat->node_start_pfn = 0; + pgdat->node_present_pages = 0; +diff --git c/mm/vmscan.c i/mm/vmscan.c +--- c/mm/vmscan.c ++++ i/mm/vmscan.c +@@ -7413,6 +7413,49 @@ static enum zone_type kswapd_highest_zoneidx(pg_data_t *pgdat, + return curr_idx == MAX_NR_ZONES ? prev_highest_zoneidx : curr_idx; + } + ++/* ++ * How a caller votes on the opportunistic-compaction hint when it folds a ++ * request into pgdat->kswapd_request. ++ */ ++enum kswapd_vote { ++ KSWAPD_VOTE_NONE, ++ KSWAPD_VOTE_NO_OPPORTUNISTIC, ++ KSWAPD_VOTE_OPPORTUNISTIC, ++}; ++ ++/* ++ * Raise the requested order to @order and fold in @vote, in one step. ++ * ++ * The hint is sticky in the "NO" direction: once any waker votes against ++ * opportunistic treatment, the hint stays cleared until kswapd consumes the ++ * request. KSWAPD_VOTE_NONE raises the order and leaves the hint alone. ++ * ++ * Order and hint live in the same word, so a waker whose order kswapd ++ * observes always contributed its vote to the hint kswapd observes with it. ++ */ ++static void kswapd_request_fold(pg_data_t *pgdat, int order, ++ enum kswapd_vote vote) ++{ ++ int old = atomic_read(&pgdat->kswapd_request); ++ int new; ++ ++ do { ++ enum kswapd_opportunistic_compaction_type hint = ++ kswapd_request_hint(old); ++ int req_order = max(kswapd_request_order(old), order); ++ ++ if (vote == KSWAPD_VOTE_NO_OPPORTUNISTIC) ++ hint = KSWAPD_NO_OPPORTUNISTIC_COMPACTION; ++ else if (vote == KSWAPD_VOTE_OPPORTUNISTIC && ++ hint == KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION) ++ hint = KSWAPD_OPPORTUNISTIC_COMPACTION; ++ ++ new = kswapd_request(req_order, hint); ++ if (new == old) ++ return; ++ } while (!atomic_try_cmpxchg(&pgdat->kswapd_request, &old, new)); ++} ++ + static void + kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, + unsigned int highest_zoneidx, +@@ -7460,13 +7503,11 @@ kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, + kswapd_highest_zoneidx(pgdat, + highest_zoneidx)); + +- if (READ_ONCE(pgdat->kswapd_order) < reclaim_order) +- WRITE_ONCE(pgdat->kswapd_order, reclaim_order); +- +- if (kswapd_opportunistic_compaction == +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION) +- atomic_set(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION); ++ kswapd_request_fold(pgdat, reclaim_order, ++ kswapd_opportunistic_compaction == ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION ? ++ KSWAPD_VOTE_NO_OPPORTUNISTIC : ++ KSWAPD_VOTE_NONE); + } + + finish_wait(&pgdat->kswapd_wait, &wait); +@@ -7524,6 +7565,7 @@ static int kswapd(void *p) + pg_data_t *pgdat = (pg_data_t *)p; + struct task_struct *tsk = current; + enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction; ++ int request; + + /* + * Tell the memory management that we're a "memory allocator", +@@ -7540,32 +7582,30 @@ static int kswapd(void *p) + tsk->flags |= PF_MEMALLOC | PF_KSWAPD; + set_freezable(); + +- WRITE_ONCE(pgdat->kswapd_order, 0); +- atomic_set(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); ++ atomic_set(&pgdat->kswapd_request, ++ kswapd_request(0, KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION)); + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, MAX_NR_ZONES); + atomic_set(&pgdat->nr_writeback_throttled, 0); + for ( ; ; ) { + bool was_frozen; + +- alloc_order = reclaim_order = READ_ONCE(pgdat->kswapd_order); ++ request = atomic_read(&pgdat->kswapd_request); ++ alloc_order = reclaim_order = kswapd_request_order(request); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); +- kswapd_opportunistic_compaction = +- atomic_read(&pgdat->kswapd_opportunistic_compaction); ++ kswapd_opportunistic_compaction = kswapd_request_hint(request); + + kswapd_try_sleep: + kswapd_try_to_sleep(pgdat, alloc_order, reclaim_order, + highest_zoneidx, kswapd_opportunistic_compaction); + +- /* Read the new order and highest_zoneidx */ +- alloc_order = READ_ONCE(pgdat->kswapd_order); +- kswapd_opportunistic_compaction = +- atomic_xchg(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); ++ /* Take the new request and highest_zoneidx */ ++ request = atomic_xchg(&pgdat->kswapd_request, ++ kswapd_request(0, KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION)); ++ alloc_order = kswapd_request_order(request); ++ kswapd_opportunistic_compaction = kswapd_request_hint(request); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); +- WRITE_ONCE(pgdat->kswapd_order, 0); + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, MAX_NR_ZONES); + + if (kthread_freezable_should_stop(&was_frozen)) +@@ -7625,30 +7665,15 @@ void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, + if (curr_idx == MAX_NR_ZONES || curr_idx < highest_zoneidx) + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, highest_zoneidx); + +- if (READ_ONCE(pgdat->kswapd_order) < order) +- WRITE_ONCE(pgdat->kswapd_order, order); +- + /* +- * Fold this waker into the per-pgdat opportunistic-compaction hint +- * that kswapd will pick up at the start of its next run. +- * +- * The state is sticky in the "NO" direction: once any waker in this +- * batch is order-0 or a non-failable high-order allocation, the hint +- * stays cleared until kswapd consumes it. Only when every waker so +- * far is a failable high-order allocation do we set +- * KSWAPD_OPPORTUNISTIC_COMPACTION, asking shrinkers to skip work +- * that won't realistically help compaction. ++ * Fold this waker's order and its vote on the opportunistic-compaction ++ * hint into the request kswapd will pick up at the start of its next ++ * run. Both change together, so the run that serves this order also ++ * carries this waker's vote. + */ +- if (atomic_read(&pgdat->kswapd_opportunistic_compaction) != +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION) { +- if (!opportunistic) +- atomic_set(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION); +- else +- atomic_cmpxchg(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION, +- KSWAPD_OPPORTUNISTIC_COMPACTION); +- } ++ kswapd_request_fold(pgdat, order, ++ opportunistic ? KSWAPD_VOTE_OPPORTUNISTIC : ++ KSWAPD_VOTE_NO_OPPORTUNISTIC); + + if (!waitqueue_active(&pgdat->kswapd_wait)) + return; diff --git a/pkgbuilds/linux-omarchy-bore/8207-mm-classify-huge-page-allocations-as-failable.patch b/pkgbuilds/linux-omarchy-bore/8207-mm-classify-huge-page-allocations-as-failable.patch new file mode 100644 index 0000000..bfa7d1e --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8207-mm-classify-huge-page-allocations-as-failable.patch @@ -0,0 +1,28 @@ +diff --git c/mm/internal.h i/mm/internal.h +--- c/mm/internal.h ++++ i/mm/internal.h +@@ -1778,12 +1778,24 @@ void __meminit __init_page_from_nid(unsigned long pfn, int nid); + * @order is the order the caller asked for, not the order reclaim was + * promoted to. defrag_mode raises the reclaim order to pageblock_order, + * which says nothing about what the caller can tolerate. ++ * ++ * A transparent huge page allocation sets neither flag, yet a failed ++ * collapse or fault falls back to small pages. Recognise it by its mask. ++ * In this tree the complete GFP_TRANSHUGE_LIGHT set is carried by the ++ * huge page fault path, khugepaged and the large folio migration ++ * targets. The huge zero folio clears __GFP_MOVABLE and so keeps normal ++ * shrinker behaviour. The three other sites that combine __GFP_COMP ++ * with __GFP_NOMEMALLOC do not carry the movable, highmem and ++ * filesystem bits, so they do not match. + */ + static inline bool gfp_opportunistic_compaction(gfp_t gfp_flags, int order) + { + if (!order || (gfp_flags & __GFP_NOFAIL)) + return false; + ++ if ((gfp_flags & GFP_TRANSHUGE_LIGHT) == GFP_TRANSHUGE_LIGHT) ++ return true; ++ + return !!(gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)); + } + diff --git a/pkgbuilds/linux-omarchy-bore/8208-mm-thp-deferred-split-uses-hint.patch b/pkgbuilds/linux-omarchy-bore/8208-mm-thp-deferred-split-uses-hint.patch new file mode 100644 index 0000000..8de9983 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8208-mm-thp-deferred-split-uses-hint.patch @@ -0,0 +1,21 @@ +diff --git a/mm/huge_memory.c b/mm/huge_memory.c +--- a/mm/huge_memory.c ++++ b/mm/huge_memory.c +@@ -4476,6 +4476,17 @@ static unsigned long deferred_split_count(struct shrinker *shrink, + { + unsigned long count; + ++ /* ++ * Splitting a partially mapped folio during an opportunistic ++ * high-order pass frees its unmapped tail pages but shatters the ++ * folio, so the pass works against the contiguous block it is ++ * reclaiming for. Report nothing rather than SHRINK_EMPTY: for a ++ * memcg aware shrinker SHRINK_EMPTY clears the memcg shrinker bit, ++ * which would also skip the next normal pass. ++ */ ++ if (sc->opportunistic_compaction) ++ return 0; ++ + count = list_lru_shrink_count(&deferred_split_lru, sc); + return count ?: SHRINK_EMPTY; + } diff --git a/pkgbuilds/linux-omarchy-bore/8209-xe-shrinker-use-opportunistic-hint.patch b/pkgbuilds/linux-omarchy-bore/8209-xe-shrinker-use-opportunistic-hint.patch new file mode 100644 index 0000000..0552be0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8209-xe-shrinker-use-opportunistic-hint.patch @@ -0,0 +1,39 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -174,10 +174,17 @@ static unsigned long + xe_shrinker_count(struct shrinker *shrink, struct shrink_control *sc) + { + struct xe_shrinker *shrinker = to_xe_shrinker(shrink); +- unsigned long num_pages; ++ unsigned long num_pages = 0; + bool can_backup = !!(sc->gfp_mask & __GFP_FS); + +- num_pages = ttm_backup_bytes_avail() >> PAGE_SHIFT; ++ /* ++ * Skip accounting backup-able pages when this is an opportunistic ++ * high-order pass: TTM backup work shrinks at native page granularity ++ * and is unlikely to produce the contiguous block the caller wants, ++ * so don't advertise it as reclaimable for this hint. ++ */ ++ if (!sc->opportunistic_compaction) ++ num_pages = ttm_backup_bytes_avail() >> PAGE_SHIFT; + read_lock(&shrinker->lock); + + if (can_backup) +@@ -249,7 +256,14 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; +- if (nr_scanned >= nr_to_scan || !can_backup) ++ /* ++ * Stop after the purge pass for opportunistic high-order reclaim: ++ * the subsequent backup/writeback pass works at native page order ++ * and is unlikely to free a contiguous high-order block, so doing ++ * it here would just churn working sets for no compaction benefit. ++ */ ++ if (nr_scanned >= nr_to_scan || !can_backup || ++ sc->opportunistic_compaction) + goto out; + + /* If we didn't wake before, try to do it now if needed. */ diff --git a/pkgbuilds/linux-omarchy-bore/8210-xe-shrinker-single-backup-decision.patch b/pkgbuilds/linux-omarchy-bore/8210-xe-shrinker-single-backup-decision.patch new file mode 100644 index 0000000..9daf161 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8210-xe-shrinker-single-backup-decision.patch @@ -0,0 +1,75 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -170,27 +170,28 @@ static int xe_shrinker_walk(struct xe_shrinker *shrinker, + return ret; + } + ++static inline bool xe_shrinker_can_backup(const struct shrink_control *sc) ++{ ++ return (sc->gfp_mask & __GFP_FS) && !sc->opportunistic_compaction; ++} ++ + static unsigned long + xe_shrinker_count(struct shrinker *shrink, struct shrink_control *sc) + { + struct xe_shrinker *shrinker = to_xe_shrinker(shrink); + unsigned long num_pages = 0; +- bool can_backup = !!(sc->gfp_mask & __GFP_FS); + + /* +- * Skip accounting backup-able pages when this is an opportunistic +- * high-order pass: TTM backup work shrinks at native page granularity +- * and is unlikely to produce the contiguous block the caller wants, +- * so don't advertise it as reclaimable for this hint. ++ * Only advertise backup-able pages when the backup pass can run. TTM ++ * backup work shrinks at native page granularity and is unlikely to ++ * produce the contiguous block an opportunistic high-order caller ++ * wants, and it needs __GFP_FS. + */ +- if (!sc->opportunistic_compaction) +- num_pages = ttm_backup_bytes_avail() >> PAGE_SHIFT; + read_lock(&shrinker->lock); +- +- if (can_backup) +- num_pages = min_t(unsigned long, num_pages, shrinker->shrinkable_pages); +- else +- num_pages = 0; ++ if (xe_shrinker_can_backup(sc)) ++ num_pages = min_t(unsigned long, ++ ttm_backup_bytes_avail() >> PAGE_SHIFT, ++ shrinker->shrinkable_pages); + + num_pages += shrinker->purgeable_pages; + read_unlock(&shrinker->lock); +@@ -240,7 +241,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + }; + bool runtime_pm; + bool purgeable; +- bool can_backup = !!(sc->gfp_mask & __GFP_FS); ++ bool can_backup = xe_shrinker_can_backup(sc); + + nr_to_scan = sc->nr_to_scan; + +@@ -257,13 +258,15 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + + sc->nr_scanned = nr_scanned; + /* +- * Stop after the purge pass for opportunistic high-order reclaim: +- * the subsequent backup/writeback pass works at native page order +- * and is unlikely to free a contiguous high-order block, so doing +- * it here would just churn working sets for no compaction benefit. ++ * Stop after the purge pass when the backup pass may not run. The ++ * backup/writeback pass works at native page order and is unlikely to ++ * free a contiguous high-order block for an opportunistic caller, so ++ * doing it there would churn working sets for no compaction benefit. ++ * ++ * The per-walk runtime PM reference in __xe_shrinker_walk() is not ++ * affected. Only the outer backup-driven reference is. + */ +- if (nr_scanned >= nr_to_scan || !can_backup || +- sc->opportunistic_compaction) ++ if (nr_scanned >= nr_to_scan || !can_backup) + goto out; + + /* If we didn't wake before, try to do it now if needed. */ diff --git a/pkgbuilds/linux-omarchy-bore/9999-bpftool-strip-wformat-bootstrap.patch b/pkgbuilds/linux-omarchy-bore/9999-bpftool-strip-wformat-bootstrap.patch new file mode 100644 index 0000000..ff35b72 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/9999-bpftool-strip-wformat-bootstrap.patch @@ -0,0 +1,12 @@ +diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile +--- a/tools/bpf/bpftool/Makefile ++++ b/tools/bpf/bpftool/Makefile +@@ -99,7 +99,7 @@ endif + HOST_LDFLAGS := $(LDFLAGS) + + # Remove warnings for libbpf bootstrap build +-LIBBPF_BOOTSTRAP_CFLAGS := $(filter-out -W -Wall -Wextra -Wformat -Wformat-signedness,$(HOST_CFLAGS)) ++LIBBPF_BOOTSTRAP_CFLAGS := $(filter-out -W -Wall -Wextra -Wformat%,$(HOST_CFLAGS)) + + INSTALL ?= install + RM ?= rm -f diff --git a/pkgbuilds/linux-omarchy-bore/PKGBUILD b/pkgbuilds/linux-omarchy-bore/PKGBUILD new file mode 100644 index 0000000..957dfe7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/PKGBUILD @@ -0,0 +1,732 @@ +# Maintainer: The Omarchy Authors +# Adopted from the Arch Linux linux package. + +pkgbase=linux-omarchy-bore +pkgver=7.2.5 +pkgrel=3 +pkgdesc='Omarchy Linux (BORE CPU scheduler, ADIOS I/O scheduler)' +url='https://omarchy.org' +arch=( + x86_64 +) +license=(GPL-2.0-only) +makedepends=( + bc + binutils + cpio + gettext + glibc + libelf + libgcc + openssl + pahole + perl + python + rust + rust-bindgen + rust-src + tar + xxhash + xz + zlib + zstd +) +options=( + !debug + !strip +) +_major=${pkgver%%.*} +if [[ $pkgver == *rc* ]]; then + _base=${pkgver%rc*} + _prev=${_base%.*}.$(( ${_base##*.} - 1 )) + _srcname=linux-${_prev%.0} + _rcpatch=patch-${pkgver//rc/-rc} +else + _srcname=linux-$pkgver +fi +source=( + https://cdn.kernel.org/pub/linux/kernel/v${_major}.x/${_srcname}.tar.{xz,sign} + ${_rcpatch:+https://cdn.kernel.org/pub/linux/kernel/v${_major}.x/stable-review/${_rcpatch}.xz} + ${_rcpatch:+https://cdn.kernel.org/pub/linux/kernel/v${_major}.x/stable-review/${_rcpatch}.sign} + 0010-archlinux-base.patch{,.sig} + 0110-bore-6.8.0.patch{,.sig} + 0120-tlbpull.patch{,.sig} + 0121-smp-preempt.patch{,.sig} + 0130-sched-detach-tasks.patch{,.sig} + 0131-sched-avg-idle.patch{,.sig} + 0140-sched-always-inline.patch{,.sig} + 0141-sched-urgent-fixes.patch{,.sig} + 0142-sched-itmt-no-debugfs-dependency.patch{,.sig} + 0143-sched-hybrid-cluster-balancing.patch{,.sig} + 0144-sched-nohz-idle-core.patch{,.sig} + 0150-adios-3.2.0.patch{,.sig} + 0200-idle.patch{,.sig} + 0210-pstate.patch{,.sig} + 0211-amd-pstate-fixes.patch{,.sig} + 0212-amd-pstate-epp-cache.patch{,.sig} + 0250-zsmalloc.patch{,.sig} + 0260-mglru-exec-protect.patch{,.sig} + 0270-ksm-rmap-walk.patch{,.sig} + 0280-mm-updates.patch{,.sig} + 0290-zstd-bmi2-fallback-aliases.patch{,.sig} + 0291-zstd-bmi2-cpu-feature-dispatch.patch{,.sig} + 0292-crypto-zstd-defer-cstream-init.patch{,.sig} + 0293-crypto-zstd-defer-dstream-init.patch{,.sig} + 0295-af-alg-restrict.patch{,.sig} + 0296-x86-mm-pmd-modify-keep-dirty-bit.patch{,.sig} + 0300-btrfs.patch{,.sig} + 0301-btrfs-fixes.patch{,.sig} + 0302-btrfs-zstd-decompress-direct-to-page.patch{,.sig} + 0310-fuse-eof-zeroing.patch{,.sig} + 0311-fuse-perf.patch{,.sig} + 0312-fuse-writethrough-uptodate.patch{,.sig} + 0313-fuse-background-wakeup.patch{,.sig} + 0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch{,.sig} + 0360-gpu-mem-cgroup.patch{,.sig} + 0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch{,.sig} + 0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch{,.sig} + 0402-psr2-early-transport-panels.patch{,.sig} + 0411-drm-xe-display-no-stolen-framebuffers.patch{,.sig} + 0420-safe-window.patch{,.sig} + 0430-fbc.patch{,.sig} + 0440-xe3-peak-bandwidth.patch{,.sig} + 0450-amd-hdmi-vrr-allm.patch{,.sig} + 0451-amd-vtem-tmds-links.patch{,.sig} + 0452-amd-hdmi-frl-default.patch{,.sig} + 0460-vesa-displayid-dsc-bpp.patch{,.sig} + 0461-vesa-dsc-passthru-mode-match-fix.patch{,.sig} + 0472-amdgpu-userq-post-reset-error.patch{,.sig} + 0473-i915-ptl-cdclk-sanitize.patch{,.sig} + 0474-amd-display-oled-vesa-backlight.patch{,.sig} + 0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch{,.sig} + 0510-sound-updates.patch{,.sig} + 0511-sound-updates-fixes.patch{,.sig} + 0512-sound-fixes.patch{,.sig} + 0513-xps13-sof-quirk.patch{,.sig} + 0514-rt766-stream-config-type.patch{,.sig} + 0516-hda-realtek-rog-strix-g733zw-speakers.patch{,.sig} + 0517-asoc-amd-yc-acer-aspire-a314-23p.patch{,.sig} + 0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch{,.sig} + 0541-cvs-nova-lake-acpi-id.patch{,.sig} + 0542-media-cvs-wake-irq-without-claiming-gpio.patch{,.sig} + 0560-input.patch{,.sig} + 0565-i2c-asue140d-touchpad-100khz.patch{,.sig} + 0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch{,.sig} + 0600-usb4stream-fixes.patch{,.sig} + 0601-usb4stream-busy-poll.patch{,.sig} + 0610-typec-cable-altmode-check.patch{,.sig} + 0620-usb-string-sanitize.patch{,.sig} + 0650-wireguard-tstamp-type.patch{,.sig} + 0660-btusb-mediatek-mt7922-13d3-3625.patch{,.sig} + 0661-rtw89-command-offload-source.patch{,.sig} + 0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch{,.sig} + 0700-pci-target-speed-quirk.patch{,.sig} + 0750-applesmc-cache-race.patch{,.sig} + 0751-applesmc-key-backlight-workqueue-leak.patch{,.sig} + 0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch{,.sig} + 0800-platform-updates.patch{,.sig} + 0801-amd-pmf-util-unbind-use-after-free.patch{,.sig} + 0850-futex-wait-multiple.patch{,.sig} + 0851-futex-wait-multiple-fixes.patch{,.sig} + 0852-futex-wait-multiple-abi-fixes.patch{,.sig} + 8201-xe-shrinker-return-freed-page-count.patch{,.sig} + 8202-xe-shrinker-runtime-pm-for-non-system-memory.patch{,.sig} + 8203-xe-shrinker-release-through-the-put-helper.patch{,.sig} + 8204-mm-opportunistic-compaction.patch{,.sig} + 8205-mm-hint-uses-allocation-order.patch{,.sig} + 8206-mm-carry-order-and-hint-in-one-word.patch{,.sig} + 8207-mm-classify-huge-page-allocations-as-failable.patch{,.sig} + 8208-mm-thp-deferred-split-uses-hint.patch{,.sig} + 8209-xe-shrinker-use-opportunistic-hint.patch{,.sig} + 8210-xe-shrinker-single-backup-decision.patch{,.sig} + 9999-bpftool-strip-wformat-bootstrap.patch{,.sig} +) +source_x86_64=(config.x86_64) +validpgpkeys=( + ABAF11C65A2970B130ABE3C479BE3E4300411886 # Linus Torvalds + 647F28654894E3BD457199BE38DBBDC86092693E # Greg Kroah-Hartman + 12D27D5D8C8E9BF1AABC7C7C7C64768D3DE334E7 # Krzysztof Wilczynski +) +b2sums=('48551bee71cd02815136fb8abe7da4464c2e17c89ef35cb0c0530c8b969fe12127ca97ab6c656ea8e5b29f2c4a8fe4cc143a626f4cca6b972a8105991e4c6905' + 'SKIP' + '6d92fb81077232b8cd1ae500b3aabc71434792e750ea6126dc095e50d4278d3e42b3cfdc3c8bc693ea14b109f6adfa3a1ed095187fc5501962fbe8f049f867bc' + 'SKIP' + 'a32edb39b4ee9c0378239f4998f477fe5371e6931c188d660874b360ca6d3bdcfff71cf088bed071365627d87458eca0be625e154ca3d1e8150e5e0bdf64175c' + 'SKIP' + 'f6c00ac2400580dffe3605d2693809396e18185f0c59b21b86f1f22a1f8d6529c51e8cc2474bc40f74a96631f415da51521a8b35d5b096e9b9d164c2755fb091' + 'SKIP' + '4651980c9d988ed73dc91fd440467de0a1de21efa3efae0fc006524c88af40173182ce6826c212e8b7faaaae1aa512fc755272ec4663c185533e65c936780db3' + 'SKIP' + '57f88a64a8ec20794c008c9de70b3f01680ce9023925df8e0f2c7be69f2600e10276d54bd264f5b8b258e04d38cf483cbf83bb79df466f17fe4b6b33fec52a76' + 'SKIP' + '050d38240747325e90bb86bb7c47a14573dcb297ee323b3d84f74df9ddf3691cdf32ac9e293bd237c9b09221065b002e297745f4b898a937b9347dacb2de7109' + 'SKIP' + 'd21fa7cb609188720365a8d95a98c3286f2f32a772309e66913c6b16bcf871ed28ea1ff01fc15ecbb47a02def1a557512126e39ac0daa956a1ba89878acf5de5' + 'SKIP' + 'cd744c8f861d2cfdfdc98ea6875419a298dcb5d478444b8e551a52fe7ce5279e2d977f8610783d79144be4927a8bf41b2f80113e6f7aa3ea02f54b9984446017' + 'SKIP' + '3434a5bbcd2bd8b4e2013400bf4d771ad5c8295ec74f67ff7acaf917954a01d25931679a71ddb8995fd7703b0b9904db4e6fe5f384d3705cf4d7b80dbf5dba55' + 'SKIP' + 'c8b8bb1ec676378d862f64b23445f170f2e995f3d89f4ffc727c377128ace87417e50d58c39e62d62795924ab4c62f3defad64139e3d3b472b75f6431484e2dd' + 'SKIP' + 'c9c1be8f72cef610592b1815a81a34706b54f2ef4a188acbd31d98243129e2939fe0c2b679f428a67eac70b069c8a662c3629c598f89430930f5b8bb602aada8' + 'SKIP' + '4f58fc77bc13fe9492fe4662e0a4b879a8a1616e271c2095959dccdffed9b0a866a4eb9dc3ac74fad9eed11c362e43a7bfa56d8ea6f571572a63ff4550c5dc23' + 'SKIP' + 'd1007ee452cdf92967ec7055f1f39a3007a69842040e12f5063a1d2f24fd6d1eeaff2357abf96d0e330541a4781f93fecbcb116a4c73371913582eb72b8f9466' + 'SKIP' + '0d3309140e496ad552d1a15d4fbf04b5ad03877bc73ae689f57554e91b7458578f2c88b6bbd37dd9fdf822a8d48c2cd6f961ec735ae5887f96fa51d908d203ee' + 'SKIP' + '13beae6f8d23a1f075410f50c09bc94f24f505571af1feb3cee58f732c0d649a9ce5939bc11642c4f3c8dfbd967939b2a0a205220abf06f10d559c4c43a946a3' + 'SKIP' + '2f24f88d8f960148046c36222fdaaadfed2ca88c60240fa1d111430898192ca45aeaa93a3afa24ad02b11c4dc8c3904a92f17c0284396f2f43352ad934a35056' + 'SKIP' + '9809e258eb684b80d508fa2f2fa270683b62bd1412ed05a970114aaa7d84c37c0beaea51f9ff8c6ee1bc7318c4aee3725e9a1131fefb51ba700801de6ac414c5' + 'SKIP' + 'f115e8c80d723f7c24e6b1638b801cd664bef20acb1d2f6e6f5612744b916278693011782228cd3f4d0518654b8d53309bc5b06bbe8ed722fab9995f369bf548' + 'SKIP' + '31abdd15ae3f9709d6912c90319a0b62d8380e1ae1169575c0e8e419f8ce264a7cf7c72558a85719cd5a1cae7341007adb3e3936268999ebd5215c2ed288f22c' + 'SKIP' + '4efb9ab5258aee01b07bc322b160d2f6e3571ff82c4f3e317cab152a919c67522629d56fb79d517da034373cd6924bc3a20c98073197c93590feb1a34e902358' + 'SKIP' + '55951c59286df43f069302b0b1036424812f80b45c5ea13219fce1bfc3e831731432c216288d722e44493c736cbf89a3e44801b5a4f1f242c17b5a89b236a5ef' + 'SKIP' + '737db39c2f7f46550de4d6d80659bd6c1fc2bf1ca42bd7bd6fc345fc90af995acd056051e12175ac6c957333629a017051d76d5359c871158c93f0a294fb8de5' + 'SKIP' + '4d0646c8ad139add6abb62d75308d2d7b5b07afbc74a0f0297ffc2148f0db38abaea16b048704187da49fe3ddee8370b10a7a41d6e62b665650cd79e8c392b4e' + 'SKIP' + 'bd7bce1bf0ee24ce201fe0e5f0b8a6c829a84d224800547cf97a893326381e2e6eaed9c70529a6864b0c338276b3a307b14e26fba221e62263b90c02eb5e5f34' + 'SKIP' + 'ad2cfdc3d8de2193110325395e11015577f26ef1081307bb236ed73fbd109822dd44ea026a57c2e17233e1fb7982e78aa986b71c043f42887e6c5d7f51b84ea9' + 'SKIP' + '19c3dd32bbe3ed7caec86e90a6af39b07eb99d5a36515cf061bb6979e5a403ca98a0e00e584a9f746330d663e5fc0868ba7fc98ab2df264bc863bdf22ef3dd5e' + 'SKIP' + 'ed2ee4849b715d80ba103fe090669062fc08c6864c64e8f8291f7f21092f0184ca5528bdd3a9318f077ef00cbe8cf28bacd1e7a9cb9f9ae1047becd930a0c42e' + 'SKIP' + 'f09e80ed25fdda9b2c79bd5ea3701985596d478b0b4e6948a1ea8fb789ef944e71640ae200442afcc23a9f0e07b34d83365e223b620baf776bbd864977e633a0' + 'SKIP' + 'a51e5f2a5e37c350fb4d715ece644c565a508970c3aeb1d28cb6cd03a0c7316a1935e028b64f20a03818926c611b50ae0ffd77d04c676d604d303beed0f96e1f' + 'SKIP' + '25dcd2c4a4287f26c01419b7d6430629d2827b6efff473361c68d6ec44e94e6d2bb8ab8215dbae5da25781a668f571cfc28b60c5693eb4ed629bf80fe2f68ccf' + 'SKIP' + '8d6eeb2330a3ca7c522cef77bf7336fac9444a0884d5d101c933abd3a31a3711529a31f9a181d4795148ecbcc24425750587314ce28e44c9ab2fd2de8365e853' + 'SKIP' + 'fd487cb02212ffdae5d8beb7d895819377686085b95658dcf597a534b017adcb65dfdf06e0276e7322a46dc28118803b05c246c22e7d3c361f1778ed9e12a3c6' + 'SKIP' + 'e18e5086c531a37837f6e7df72ce10a38289b02de4cea376772d45cccb5aabc628fe4f3f9607f646903a7cf59b02692703e6b26800604cf958136987df0b4d5d' + 'SKIP' + '1b340a502dcf5a1680caf5320b3aa72aa4317e4cf1d877c1f6425e281a5668d5f092417e1f282371c0a211344f5c187fb3bdaf3f97c7e122951edf7e4c08ca58' + 'SKIP' + '1124c5c3fd5104c886c841ead1ae9afb42683388b640b1a86523dad9919c6bd59a1f78b07ee5a213e0dca77c636a29676af0592d4ed1735ea35f282f6f23e46f' + 'SKIP' + '505bf01a9d6b0926b557b673bb686d1a4ff73ee70539991e52272fbf58aad3ea25cf5ffc5c2d094a2d5e78b18a1a134f2ab530f595073a2d3eba9ff2c1418e1e' + 'SKIP' + 'b33b46c37f12f7650a67c3731b87ff84add1ac8623cc96b52223a1ef9946bcaa990ed7c7036674e228335f57ec9ed421ea4d6c1dd72d19b516215d9ac2c7c3ee' + 'SKIP' + 'b4bf4c4c0672bad91d7f8d309e741104918dd8ee8688a00d600379eba5828d1b8157953377c73d0b58050b4ff888c43013ede4e9c959be8d9c028a6143bf58ed' + 'SKIP' + 'e403bdc300284c4f0f4302e501b8ccfe5e9af04e73a6070eb6615322f5f40ed6edad53af2ed278741f61a4e3811b92faf1af02f113dbcc2a19bde335a55930d7' + 'SKIP' + '5de29d0eb5264e24ab9228bea2ea4b549b3fe37ea67c045df9f83064f11c2f557b9641e772d28db59f8d258418c77435b66ff471c3695043f0f8275d5e7b1685' + 'SKIP' + '3c2cd960103ad97ce35679f906b3c9be79a9744959c5dfd0ea338d8075b698790934694b2f71fde026b164c5246cba71ffe3cd5dfc1464da338136fbbaea2f1c' + 'SKIP' + '2b33b11e48d8dd8d6743f26170fb99eae38541e3665e2448d91cedf729cca7c0278bfba1078ecbe88201eac4407ec5bd0292a97dbab9cfc67e42646712befc1f' + 'SKIP' + '71934436987ecf6914e5c559445c2b7ee92a31c467552342094c93a59d32563aa9930e710556cc1e271eeb352705a9df9b3702482f87d045d132233cee4ab48b' + 'SKIP' + '9fb1318a3a181644c67b2adcac0cb04d1b88dd0e632751967239d4e8d4698f193795d00194b0f68505964c84572b67168dcbed02ad5bb7abd41e406a62111489' + 'SKIP' + '353b7e89a7044b7426db730d5b900eb347907aa0c20e307d2317925d6021a969a343649ede7a32e698dad60dd4472eb197c9578fb0e36fc354a60266fd680cd7' + 'SKIP' + '803223224f22aca25dfe34d73e2bd49438f0cfa83b71baa545fa6fe93ee1a631b6b62c7324dc1c0d2d2ded94c36185a83af39b4f4a3362f554a1943ec2e2ec3e' + 'SKIP' + 'b0a7ab192066c472f3ebc39bc9afc9fef0528ff7c98bf67c5b9cb679cc6d93eab3d25e91d8d74c9c52f72803949a3ade7940e677b9a3114605b966df94da93bb' + 'SKIP' + '824089f9f45e2ff9e788d874bb9294b9208b4486fd22c56cb59d4d88cb996ff5a81ac22f8ff136ddffa1e40330f02e53ab759ffea7e4535c11d4f8195069966b' + 'SKIP' + '696309582c56fad530a0bf2be12bb744dedac11ed69642b14eade0a26baf1ce52b36074b89b963b40c9c6fbc4857cdc71b2a6c6d64afcbb2df716bbb57579986' + 'SKIP' + '14212fca051d3e9a85f75616ff7f95bbd119ef8e5400dafd4447f70db2c4e9c03d971ec4fdfb4db2319d2613062d74f54dfd5d0a100ab0fa27ac6c05e16bd063' + 'SKIP' + '7228e249d25a99f5106040b8ddb5705979b3a5af21ca60d0d43a9ecaf6eaccef6bde8ea69c896cffbf2b5963ec4f68987e69b184a8885c7f4c98d2b05fd59fbb' + 'SKIP' + 'a54ac1c6d43fe88dee08884ed2e5bad0a0b6d11f34a46dc9801e675860afb7641b57384248a144a621e66a73541d73da11f71671116f1cc7f21534526411439a' + 'SKIP' + '2e486cabad0d45aeb760d031c7946e9887c710b29dbe9c16e5f4bae6a230031dba4d817f1ee25a0bbca849d1c71b2f84052ffb73c4a6c4350a6ea5ee83c5a40e' + 'SKIP' + '23e2a5def526fa886d1f1b05607bfe6b1ecbb2d6009d023145adff9eadc3135f9078faca6638966b586e81e4ab9d48d23b5758504d5a043d5f46e7a701d185fa' + 'SKIP' + '5d82f32f7c04b084530722b9848254aac289c2a754a10e16821083a871ec14065dd9ee83e72b8e9e8ba2915f3fbaf17b0874df724002eb08a5f4f84d1b6aecd8' + 'SKIP' + '4928cc008dc91fddb89b154b89b1ce2bbff14d8186c0ec1f81971fb9d3b622475e93e42015916699c3442e2268680779f545d183112b57dedae96fc48a75844c' + 'SKIP' + 'f93494f5f3196cc35d017ff9719d146bf3e5e7bfe32a7f5e75d96468ce30ef367428ba83b1c99b665064d3edaec84cf3bf18211d8f7b4f118b3bdee495eb8ef9' + 'SKIP' + 'eb79554ef0d014b94bc72d4d559c2cc5419df96486acb3b6a09d5e8e9dc48db626ce0f90338c67fe873b2cc3c45c3f308c8080532b60f74a07c9e39e01d34197' + 'SKIP' + 'f657143cdd9c937d78315f4617392e60f7c46f5cec882e060f740f77a27dd4c045878cb66e568a6d9ae74e9c706ffb3052817db6f327c87b85ff2f46eea678e2' + 'SKIP' + '0acbd87a09a62af860d14f342d61c82c29ec1711038d4abf4ae473df187fbda6aa49386d88928c5dc4c3379e097478b1d40d9ebe9360e64d02e59161164b2a3f' + 'SKIP' + 'bb06066e90132f0172333d46be54ca816eb7d3230a193a6cae765d8edc50df4a6ba9fb7c0647cd6befc8c3bbbe1d96c285ee202812477d7295c1130ea8cdd6f4' + 'SKIP' + 'f1a94986b4b3f310fecf981e3619f9787a0c8cb0b4fb3f39e5fe85ee3400e7e50278d41bf5468f65e5bd890358d2aabe28d3e11ee838ccbda2ac7af16be568d9' + 'SKIP' + '44dcaad2ab326ebee62d3e63207a161dcb1b63bdb21412b5624ca7875bb6e99997528683a633d7dd526dc5ae9408e73bd066e2a1e5b34ba5c829162df2265346' + 'SKIP' + '162a2c2104d5f036657ace6eef2ac2d533c0934376fab2521ff1d3fccd704e3d08fa4f6162e9100a844db2aeb57fa3ce214b8058bd227f38b058ab7606ee7816' + 'SKIP' + 'd50d5a69087a5ba6c416a6391232764c8281a8094645175fdb2cfc1bb011e33335ae4e96d79364681bea61edcb58ba9acb491e08c301d0667481759d0ee1e183' + 'SKIP' + '539b1d0e764f0ace3e572ac49f20a790183155ddc0bcd3a8d040f44c9891dd36b1a0accb8c0288267afbd33e6ceadf6fc3a5e3db40491c695e0c1d3dd64af40c' + 'SKIP' + 'f5cae6eabf2066d5deef737a15967d5d11340de34b181f3b3d841023507fc58af2b68926eb04bb342e1adb857165400f862f2b35ffec4204a107b3b22a1571e7' + 'SKIP' + 'ab28a1f4f1756b8451840566d8100cb3d0b498b13d006593c72829b9783e94900bae78ab47c576d156e1d08c56602274b3f7d5bd885de17336623b23c3e4f255' + 'SKIP' + '8481f23c33a84d8efb798f4b83f78011f74cd5a3dec428b177d7e600922836c1eec61c4238724038221d8ca2e2cdf20462c5911085da3461aa315f7dd19d96d8' + 'SKIP' + '19ebb39f3e53ce77dc3522d15c88270c5ca3fdd76fa8bd417852d08a1d85b30bc6eed3e11c95392b694dfc59bb8b43e41d43df4954f00593c8fbe50bd9e199cc' + 'SKIP' + '4dc42e9bfb5ee0f952bfd7befc9d333044aec11fc3a3852d4636f2e38ab139eb92c497eb3e30921b6ee8ae7d6e5834dc334ad25419bac2c875a9fd7eb7945f9b' + 'SKIP' + 'fea498b5c5ada6d7f57caf652b68f687081aae8552d2d04cd9e96764efd1b896208671f4f501210bb557710cca3ceb0c02ac2b5da6d4c04c210e87ec2d715384' + 'SKIP' + '4a4af88fc8a8c37ae6d20b35d19e60a17672569d5489be120edbcc62ab3ba4bbc43b78cd0799b9fd73c84b089364320bcbad6fcb7128c2cb06d15ed984d6fc8c' + 'SKIP' + '0959ca78ff31299adf1091f4aa1b32cd2e9066aba5e44f0658c91bcb93fe8714f7b671ffa2cd498ecbb6c6e643ac6571bfb193034af206599122fd7c2a3c23a8' + 'SKIP' + '03ca740048e48c5b6948f972f7a826240cf51c9b5c8645d1b1c50366e78cbf45f5b45b7b47f87f231b50aab46ef2f8fd78bc53899ba1c319a19f9f61c7cecb90' + 'SKIP' + '1b63569b589e994d1869e2f8eaf5d4ee225fe68862ac5c848e46544b827ca5fddb4bf060f5f39001608fd9bb284eec005f5f9b095307d3a008c9ea8957c2fad1' + 'SKIP' + '073514dabc88f0206911eb27eb36157e425849221056438340b22ee7bfa6997b58ce7dc4470b09d7ee96f6374f117d91cc19d9032401b2355d1c0aba6fc75771' + 'SKIP' + 'd7dffb68fd22db7ef41d1ac193dd297fb6150f034f257a44e6cb0a4848dc4a94e328c2e26d5296a72993e2db75d09e3ac46456d337a5bae767fb266082d3cc66' + 'SKIP' + 'aca0527f5630671a319e7ecff958a3a64f29df73b427c67c1d29763cc1801fa82e96761c3a61c22d936913943a49261da64d490a0752ba9ffc8e0c1d92f99835' + 'SKIP' + '803736a8e836560b3e50b7c0c8cf1f9ebb16b8642cc7bc6e5167d19037bd757431565fe4581776e50eb2693c49f778a24fecff41a553612e7e498f755b8f2f67' + 'SKIP' + '4bc132d9c34deaae9b13d08dcff8ea83aa8f9ce4398531e0e0a80df30da3808815f3a587fe5627f6553b8e9c2aea6cc1749b752250a91c94f1cf8ba4f3eb5e8d' + 'SKIP' + '2b007e58c0541c6ae71ac07cc8c0762343a50e1f3eb52db5631ce725c4f074f41c639fbf6d8695daa78ee1d6a8cb9a0c31d0a9d9ea2a3a10c05f7f6421dda184' + 'SKIP' + 'd3b2c0e4fd9f26a0eded375522478f8e9403232799d233eed4668920f8a9942ec6bafbc928f14459e4736f63a9a66d3ae2e9f70dc810145acb5fc522b13ecbd9' + 'SKIP' + 'c7b0be7a05a304e9993fe8ebccefa44944bf9d27dad9f7b3cc7111221bf50b2db71ca51cafc89dc03b237aef3aba7114543c8475cc393d13e61726e68898d3af' + 'SKIP' + '4008c7443bc221a142a939deac86918bef8f09ff1ca99a899586b13120edb8007450daf0b9d143770a9f04d616fe96f527b6aac160be45e5cfa66c0da898039c' + 'SKIP' + '029d9e899dd9e7bdbcacee34ee9b468e2e2a8f726e38d014891c8353f6afcc52a58c7d9289fb0487cce4d90c8d51f578c27b1dbcdab42de7308f50b7baf27877' + 'SKIP' + '9ede433f4e01a5a313a133a8029d64250596582b93a33e434b8e2ee5bc53ba7f91947e1be2aaf57776028cc89e7ca62cc90067ee64650b3e503acad858777a8a' + 'SKIP' + '9ac15b3542629c5333cc7d7f53b4f2995d807a31331b9e04d3da051be0ddde7e8c3cbc79837ffb49a4d364db1680cdc6d763465fa56bd4d26b16b765985babe1' + 'SKIP' + '25fc5f03732a584c864ee2ad20a74e16777998b0b34f880bbe9d646cf1944bac1f276bbd62515475337eaa81c0f398d79888aca6dc18bcd06ebb7cee9e4a1d8e' + 'SKIP' + '70e35154f9385156a11280294cc254cb314fd89653daedb46546a6bd64c550e3fc7e7a8df3e653c516687f0317bcc710fce82b4d649472333c501ed85fcd6cf6' + 'SKIP' + '576fd826f28d945782840865f3e5b7c87d7f6f91f08e473ff0d06a37caa2f89cdd9cb4a7aafef5551363aab9c60e887ee794d5b6292874c12baf88c23ea5668e' + 'SKIP' + 'b69cf36cc5633e507866e67f59557646dda7d6ea436b145f3f3356c4b12e1300c94977a7d1214835f2476c94a30dd672076241b920d96d744fbc199539df506f' + 'SKIP') +b2sums_x86_64=('66da8f81bc90bd7d60cd1a5fc3cb3e721dd0d6d9ec8b82a6f0948cc9652ddbf9bef4d029d21694b346fe18e87e1b14067ea265c354a3adb020861a15131e1232') + +# https://www.kernel.org/pub/linux/kernel/v7.x/sha256sums.asc +sha256sums=('55ddf0df8325d9dad96fcff7bd93977d22e3f50af06527572af59b77c7632b78' + 'SKIP' + '95f3e9209629044028373af987dc0e270a5a15acb8c8e49c5f05057220c75fe2' + 'SKIP' + 'daf0aaebff3cf4679d0bb8137ace6c9a2de62f75aa4655a0a02ddf759f3c7f26' + 'SKIP' + 'a9171e731d08a454a50174889af8936fab962b33c37c67169b0cdf21b0b80821' + 'SKIP' + '413e9994d35bc722cfe45752ab60a1bd8454443170d80987e6a5be6b7b745940' + 'SKIP' + '01b9087c0cebf17d0ffbbbdd6025c9937fcdfa00c8672d310786ca3c3ce6daf9' + 'SKIP' + '5c776365d391a15c23afb3969183449852f795f42355629ac563abf7ae5e4735' + 'SKIP' + '3a6aaf3b29eb5b4018c5703626311cd1c7a61bd7100789633efc6a79a9b3247b' + 'SKIP' + 'b28d9b548407f0da4280f4f9a6c0e92db6f3a32a35c7668471b92096eb7b093c' + 'SKIP' + '9492d7db11fd327ff06a52cad945bf8ff6dfcabec72c435bf02b4432ee67475e' + 'SKIP' + '66af19c43c068144699183a17b67bcf4df03578937c4cffc5d8b24e2e2828f7b' + 'SKIP' + '267506a02ac723ea37adcc7c851051edbd1ed731c6aaea4c9e590aadc207855c' + 'SKIP' + '07baad05112a47cb0aea81191502c87e496e13b4f23a6ec36ea65bb4219c35a8' + 'SKIP' + '720720067a21e8c57d2619f4c0b46846ac060ec6db97088634d62ab8628c70a9' + 'SKIP' + '3d1299d19aa9fbc96d25f67154009813e05e92340340953ac8355649cff837a4' + 'SKIP' + 'dac26fb42f5df71b30c46b3c5894c6016af5a283c57b45173386767ca53b1bf8' + 'SKIP' + '66eb2c49dbf708cc781d411d0e44ed613480f0ef66d522272929d9cb7bb21e93' + 'SKIP' + 'b7ba949eea77e169aceb2c401d4eb83a7413aab6f15ee2a7ab1a96352f0aa12d' + 'SKIP' + '341424b925d506869793686e22ea8f095a34048595ebebec409c0a7bbd346ca2' + 'SKIP' + '7c25d0a53a115bc1f072ff0cfdd9aa88858e754ae8b15026a2582656e8bd9c43' + 'SKIP' + '79799f12cdf42de1cf6c5d483896b439d77bc2e582e10c7215f5375a76612a72' + 'SKIP' + '5e3b455024fab2855b590091c9d14b0d12d5363f3d4bf025b31b9aa88b65eba5' + 'SKIP' + 'abd8c9326cecc9bb85db4b3dc98ddcb66306b34a6d84fb05d0805aed537e2dc4' + 'SKIP' + '60a9df54821cd98581e7476f8a7bb6cb0819762bdbed4d356332e41d164a8182' + 'SKIP' + '929108ae0a8eb4782cafdd89e3d4426d02d86f2c2102ed99e760e31f0bc3794a' + 'SKIP' + 'e1226c836a9c2fb6daff109e56ba21c13fd198e109cbb181de920af04fe153e6' + 'SKIP' + '8d60f7dd26ef904419aa966427a9af2f2c9a079bb092ea0b58274ab905f6047a' + 'SKIP' + '258597006eb96e1fb185ec9303ceb48148eedce95608b57900383f6df59d2989' + 'SKIP' + 'f935409f9aeba3314adfad7db1b3d895557a07029a80d10969804e49c0e270ca' + 'SKIP' + 'd8e64b9da3beab7c33832c019da50d0977b39dc32ec1f649a7c3bc93a2fdb9a5' + 'SKIP' + '1780f66b157de6d249301331e31b7505f6e6fa26686fa4c618623567e4b758ae' + 'SKIP' + '2501c98aaea6a53eeaa18d696f60b677e710d0f2d89f7525e47ba806de1878b3' + 'SKIP' + 'e6b770d37e80509ed7ec5d8b59b4d2e5d6dba54ba659d7392de3b567f1eb6e54' + 'SKIP' + 'b8596b5b5b546fdc309e10326b0e2ccea5a0538d4919630ed2845cded78580aa' + 'SKIP' + '5cc215215fed4247c2d6b9732deff586331a07d54c3b00538a81921093a3a1a1' + 'SKIP' + 'e303da14a3c8a15fd1cb45394138f03dd16e66d2ac0d1ebcd933977df937d771' + 'SKIP' + '1f0c958b64ba48b8dc8b5548e1ad1934b8d4903fc16fec15014759e3681ea275' + 'SKIP' + '47994d576008a377de612ed77e6b2e7bc6b24ad3a30a6d157646d380ae323142' + 'SKIP' + 'd9eb3717ba98e270b0a998f8dd90cbda072f63b6a6ea8f66763bde3ea64e591d' + 'SKIP' + '1e2fddb2e0c5d184a2e8c774886cf9be2f2f292e4f05832b50e5b49c48a7bacc' + 'SKIP' + 'b9b8771b6a8bd7128f4344ad189e32c155abf72abd3ff646dcf5c78b08eb94c4' + 'SKIP' + 'f62837e8ee51070f8139f718e0e80a4ce0c0dea9ce2328c10c63403aca5b3d2e' + 'SKIP' + 'cae59282e3d1afd69f4f5dc00a180c712ef464c98b8fd7ccc9b03de944dc79b9' + 'SKIP' + '5a74c8b2ba11e5876eeeb5ba530ae97e384b48943fa7a31e13396ecb82c5a8ce' + 'SKIP' + '8b954ea37a2190022064c82688bc8cb1cf79a90598f078035be7d4041003cda9' + 'SKIP' + '40ac31789399a5964c4fd820d4cdc7214add91109bbcf4a614619e5d8b367441' + 'SKIP' + '707460d08c15f451d0a0e567c6e35522f3e1409bbe3bfbcf7160f33af06391b7' + 'SKIP' + 'eebbe50deab379d09b5149ef30769ad6dfa11109696934ee66b108ec406da942' + 'SKIP' + 'dab92dbc6eac02771c72801d94e6a53aabcf629a9cd79e85e0ce2ba44a65b06b' + 'SKIP' + '30e2fc90c950869ab2fe1535b609f35dc1d46d6b6816de4afcbf687dbc224f0a' + 'SKIP' + '1fdf8c4d8ee07c02883b827a11a84c1e5f70570545b8a4a232f6b59dd02049fa' + 'SKIP' + '0057dff07a3ce9c7b7085edd1f912958e01ce8f7400151d55dfb74c0c6a084e2' + 'SKIP' + '2762f72620781a672d9a21bcc591b862b04a756b271cd1009a7390eb1b77635e' + 'SKIP' + '541325476efee83232ab8a6c62c14cf3c3c957c49cf053ed2869f7780f86d032' + 'SKIP' + '8a8eb0934424ab6312f26e03a06be5d0401360ee60a20a87b11f7c3c6590103c' + 'SKIP' + 'f7ff13ea46359217d696c6ccd49bc3d74c441ac5893d5151d95138017b0d10aa' + 'SKIP' + 'd17493b3579c730d27dc723447175b70d991f50253c9fd419df5e923bdf7a244' + 'SKIP' + '92971bb4dbde209cf170838cfcd75d8ac9978239821d3493c28cc12623c2bb60' + 'SKIP' + '7245c6e2cb5c0b0ddf800894f087770cbf8ccc6f8b3f53b9a21c704e9dfa42c2' + 'SKIP' + '135140bce40644079be7743b51f8618fc829a8868ac53137050f76c45eca4061' + 'SKIP' + 'e6ec816ae309445c6c11e3433c04d0c9100e16c0e23268226d13c19e7be49edb' + 'SKIP' + '29c61ec984bd342113b3c4310a3d5fddfd6d7db83847cfb26b84cc730385320a' + 'SKIP' + '53425998005a4115bdf6001b9ffbe62c66bb2db205994b2e13005a3417ea6cbb' + 'SKIP' + 'c28fca9ba015e96599627acf42e52737a6a846bdcbfd3d4af4147eda3109e12d' + 'SKIP' + '5ce97dadb9eb1a8b0da41a8446ee74e01a5a7e7ab439bbea7a711318f04ae24f' + 'SKIP' + '44f1c7bd18ea7f8772ce41e24dddd5f314e416ec8dfac6afe1ce8680c1bb4903' + 'SKIP' + '0bb8ae5b6f23e13c60953eb5bbc8939e8f6c8faf2b216a20203af3b6f0217134' + 'SKIP' + '907c92ef681154d10959a5f0cbe2a636688036990ba42e059af00cc2311b4be4' + 'SKIP' + '534e68a989d36823f14a10299803f294eef39391619b4c329d78c20539d9fcd5' + 'SKIP' + '516e6ec51ca0330989128a4eb2a8f6785e5d45d930899cbbbf37b8147ddfd854' + 'SKIP' + 'bda3d6842f15998ce3f3e33d0e6e420df9c9c2abcba13716af478720c5933753' + 'SKIP' + '3e7e7dba1ca88930c2c3f0d5411ddcee8c2e12fca59d859e2383dccaf9a08022' + 'SKIP' + '3a271bc6b0219152047fb2b78d9e695eb791b796ea6d8233096912c77d62a5c0' + 'SKIP' + '4e80f2d05591175d4ed0a1a144509e97260e5593472aae586c07956215d99162' + 'SKIP' + '8ebce8f38e38bd66879d040028c2448e5bc7dcde9df0ccee2b4221015591b162' + 'SKIP' + '0b0e05b615ae6eb825bf7d7fa568f551b0dff6c2cee62c9285c1b0d63224ea1c' + 'SKIP' + '2e1e2a0eb039ce61c1612bf6c7ab5227b99df053bcd5592da7758715dd333fda' + 'SKIP' + '17de1f76da3db42dc3c7829a22953cd2c6de916999d809e3fb0bbb741753ec8f' + 'SKIP' + 'd90cc6570a47c8754e4c5d45834e0434c0a1fcabda9b8933b1fba84e3dabc083' + 'SKIP' + '028bf538c870465e1752514725372fa9eef6b7178d9b8d1e2ecc15edc5a63cc8' + 'SKIP' + 'a92a84405ee9845738c6b52d8f0f0eca16eb40afe0ebe0f4e9e1168597675311' + 'SKIP' + '44a0b10a6dc83465ea86f5fba936b2bfbeffb1b3f0b2e04013bfed99ba4f6c5a' + 'SKIP' + '229b28cee6f2b8afb5888eed453ce7f82de033900383bd91957339007c9e6cbf' + 'SKIP' + 'c5d61db05b19fde06102b0b3dbd76e1989c8f07f3b7d175176bd2d73f1a71e5c' + 'SKIP' + '5988d7a37b4b71a64ed09fea872f08515a3c7029d33fd053a6a1822bb811ddb5' + 'SKIP' + '1ec4fb93700b0696ba2c2a371f218f13b32dcc745523c7ee26a5c3cd7af253fc' + 'SKIP' + 'c67fee35a42866dd3c9056ff099fc504cdad9530c107d959331b5572b21af818' + 'SKIP' + '36cb306751c57de3e61c7a4cd18a7f131eef0dc1de15a4e28751d1e5b08bace8' + 'SKIP' + '07683e522d6dea3d3ce658ee80bf4317f6c3caf248259da960bc4610f4d1e807' + 'SKIP' + '66a0933d2d4f2edc8999b6fdf0fe10b5b607646fd6f1a1d534bf26270543f8f1' + 'SKIP' + 'c5279468d93e562f509a6b599a2b7ca7ee0032d3fe35314c5769fc6c9a9234ab' + 'SKIP' + 'c0111614ec44014cbf621eedf5a4ef302a855423bc1633ee692392950e971d0e' + 'SKIP' + '1cbe1ddd3b37cd0c7e4bad4af08e681eda2f7a61698004322bb87f320aa95dbb' + 'SKIP') + +export KBUILD_BUILD_HOST=omarchy +export KBUILD_BUILD_USER=$pkgbase +export KBUILD_BUILD_TIMESTAMP="$(date -Ru${SOURCE_DATE_EPOCH:+d @$SOURCE_DATE_EPOCH})" + +prepare() { + cd $_srcname + + echo "Setting version..." + echo "-$pkgrel" > localversion.10-pkgrel + echo "${pkgbase#linux}" > localversion.20-pkgname + + if [[ -n ${_rcpatch:-} ]]; then + echo "Applying patch $_rcpatch..." + patch -Np1 --fuzz=0 < "../$_rcpatch" + fi + + local src + for src in "${source[@]}"; do + src="${src%%::*}" + src="${src##*/}" + src="${src%.zst}" + [[ $src = *.patch ]] || continue + echo "Applying patch $src..." + patch -Np1 --fuzz=0 < "../$src" + done + + echo "Setting config..." + cp ../config.$CARCH .config + make olddefconfig + diff -u ../config.$CARCH .config || : + + make -s kernelrelease > version + echo "Prepared $pkgbase version $( + ++#ifdef CONFIG_USER_NS ++static int unprivileged_userns_clone = 1; ++#else ++#define unprivileged_userns_clone 1 ++#endif ++ + /* + * Minimum number of threads to boot the kernel + */ +@@ -2093,6 +2099,11 @@ __latent_entropy struct task_struct *copy_process( + return ERR_PTR(-EPERM); + } + ++ if ((clone_flags & CLONE_NEWUSER) && !unprivileged_userns_clone) { ++ if (!capable(CAP_SYS_ADMIN)) ++ return ERR_PTR(-EPERM); ++ } ++ + /* + * Force any signals received before this point to be delivered + * before the fork happens. Collect up signals sent to multiple +@@ -3163,6 +3174,10 @@ static int check_unshare_flags(unsigned long unshare_flags) + if (!current_is_single_threaded()) + return -EINVAL; + } ++ if ((unshare_flags & CLONE_NEWUSER) && !unprivileged_userns_clone) { ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ } + + return 0; + } +@@ -3398,6 +3413,15 @@ static const struct ctl_table fork_sysctl_table[] = { + .mode = 0644, + .proc_handler = sysctl_max_threads, + }, ++#ifdef CONFIG_USER_NS ++ { ++ .procname = "unprivileged_userns_clone", ++ .data = &unprivileged_userns_clone, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec, ++ }, ++#endif + }; + + static int __init init_fork_sysctl(void) diff --git a/pkgbuilds/linux-omarchy/0120-tlbpull.patch b/pkgbuilds/linux-omarchy/0120-tlbpull.patch new file mode 100644 index 0000000..712b9b7 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0120-tlbpull.patch @@ -0,0 +1,763 @@ +diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h +--- a/arch/x86/include/asm/pgtable.h ++++ b/arch/x86/include/asm/pgtable.h +@@ -50,7 +50,7 @@ void ptdump_walk_user_pgd_level_checkwx(void); + extern spinlock_t pgd_lock; + extern struct list_head pgd_list; + +-extern struct mm_struct *pgd_page_get_mm(struct page *page); ++struct mm_struct *pgd_page_get_mm(struct ptdesc *pt); + + extern pmdval_t early_pmd_flags; + +diff --git a/arch/x86/include/asm/pgtable_types.h b/arch/x86/include/asm/pgtable_types.h +--- a/arch/x86/include/asm/pgtable_types.h ++++ b/arch/x86/include/asm/pgtable_types.h +@@ -512,7 +512,7 @@ static inline pgprot_t pgprot_large_2_4k(pgprot_t pgprot) + return __pgprot(protval_large_2_4k(pgprot_val(pgprot))); + } + +- ++struct ptdesc; + typedef struct page *pgtable_t; + + extern pteval_t __supported_pte_mask; +diff --git a/arch/x86/include/asm/pkeys.h b/arch/x86/include/asm/pkeys.h +--- a/arch/x86/include/asm/pkeys.h ++++ b/arch/x86/include/asm/pkeys.h +@@ -88,6 +88,9 @@ int mm_pkey_alloc(struct mm_struct *mm) + u16 all_pkeys_mask = ((1U << arch_max_pkey()) - 1); + int ret; + ++ if (!arch_pkeys_enabled()) ++ return -1; ++ + /* + * Are we out of pkeys? We must handle this specially + * because ffz() behavior is undefined if there are no +diff --git a/arch/x86/include/asm/tlbflush.h b/arch/x86/include/asm/tlbflush.h +--- a/arch/x86/include/asm/tlbflush.h ++++ b/arch/x86/include/asm/tlbflush.h +@@ -4,6 +4,7 @@ + + #include + #include ++#include + #include + + #include +@@ -211,6 +212,12 @@ extern u16 invlpgb_count_max; + + extern void initialize_tlbstate_and_flush(void); + ++/* ++ * Keep stack-allocated flush_tlb_info cacheline aligned, but cap the ++ * alignment to avoid excessive stack usage on large-cacheline systems. ++ */ ++#define FLUSH_TLB_INFO_ALIGN MIN(SMP_CACHE_BYTES, 64) ++ + /* + * TLB flushing: + * +@@ -249,7 +256,7 @@ struct flush_tlb_info { + u8 stride_shift; + u8 freed_tables; + u8 trim_cpumask; +-}; ++} __aligned(FLUSH_TLB_INFO_ALIGN); + + void flush_tlb_local(void); + void flush_tlb_one_user(unsigned long addr); +diff --git a/arch/x86/kernel/kvm.c b/arch/x86/kernel/kvm.c +--- a/arch/x86/kernel/kvm.c ++++ b/arch/x86/kernel/kvm.c +@@ -663,8 +663,10 @@ static void kvm_flush_tlb_multi(const struct cpumask *cpumask, + u8 state; + int cpu; + struct kvm_steal_time *src; +- struct cpumask *flushmask = this_cpu_cpumask_var_ptr(__pv_cpu_mask); ++ struct cpumask *flushmask; + ++ guard(preempt)(); ++ flushmask = this_cpu_cpumask_var_ptr(__pv_cpu_mask); + cpumask_copy(flushmask, cpumask); + /* + * We have to call flush only on online vCPUs. And +diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c +--- a/arch/x86/mm/fault.c ++++ b/arch/x86/mm/fault.c +@@ -275,17 +275,17 @@ void arch_sync_kernel_mappings(unsigned long start, unsigned long end) + for (addr = start & PMD_MASK; + addr >= TASK_SIZE_MAX && addr < VMALLOC_END; + addr += PMD_SIZE) { +- struct page *page; ++ struct ptdesc *ptdesc; + + spin_lock(&pgd_lock); +- list_for_each_entry(page, &pgd_list, lru) { ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + spinlock_t *pgt_lock; + + /* the pgt_lock only for Xen */ +- pgt_lock = &pgd_page_get_mm(page)->page_table_lock; ++ pgt_lock = &pgd_page_get_mm(ptdesc)->page_table_lock; + + spin_lock(pgt_lock); +- vmalloc_sync_one(page_address(page), addr); ++ vmalloc_sync_one(ptdesc_address(ptdesc), addr); + spin_unlock(pgt_lock); + } + spin_unlock(&pgd_lock); +diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c +--- a/arch/x86/mm/init_64.c ++++ b/arch/x86/mm/init_64.c +@@ -136,7 +136,7 @@ static void sync_global_pgds_l5(unsigned long start, unsigned long end) + + for (addr = start; addr <= end; addr = ALIGN(addr + 1, PGDIR_SIZE)) { + const pgd_t *pgd_ref = pgd_offset_k(addr); +- struct page *page; ++ struct ptdesc *ptdesc; + + /* Check for overflow */ + if (addr < start) +@@ -146,13 +146,13 @@ static void sync_global_pgds_l5(unsigned long start, unsigned long end) + continue; + + spin_lock(&pgd_lock); +- list_for_each_entry(page, &pgd_list, lru) { ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + pgd_t *pgd; + spinlock_t *pgt_lock; + +- pgd = (pgd_t *)page_address(page) + pgd_index(addr); ++ pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(addr); + /* the pgt_lock only for Xen */ +- pgt_lock = &pgd_page_get_mm(page)->page_table_lock; ++ pgt_lock = &pgd_page_get_mm(ptdesc)->page_table_lock; + spin_lock(pgt_lock); + + if (!pgd_none(*pgd_ref) && !pgd_none(*pgd)) +@@ -174,7 +174,7 @@ static void sync_global_pgds_l4(unsigned long start, unsigned long end) + for (addr = start; addr <= end; addr = ALIGN(addr + 1, PGDIR_SIZE)) { + pgd_t *pgd_ref = pgd_offset_k(addr); + const p4d_t *p4d_ref; +- struct page *page; ++ struct ptdesc *ptdesc; + + /* + * With folded p4d, pgd_none() is always false, we need to +@@ -187,15 +187,15 @@ static void sync_global_pgds_l4(unsigned long start, unsigned long end) + continue; + + spin_lock(&pgd_lock); +- list_for_each_entry(page, &pgd_list, lru) { ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + pgd_t *pgd; + p4d_t *p4d; + spinlock_t *pgt_lock; + +- pgd = (pgd_t *)page_address(page) + pgd_index(addr); ++ pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(addr); + p4d = p4d_offset(pgd, addr); + /* the pgt_lock only for Xen */ +- pgt_lock = &pgd_page_get_mm(page)->page_table_lock; ++ pgt_lock = &pgd_page_get_mm(ptdesc)->page_table_lock; + spin_lock(pgt_lock); + + if (!p4d_none(*p4d_ref) && !p4d_none(*p4d)) +diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c +--- a/arch/x86/mm/pat/set_memory.c ++++ b/arch/x86/mm/pat/set_memory.c +@@ -62,18 +62,18 @@ enum cpa_warn { + static const int cpa_warn_level = CPA_PROTECT; + + /* +- * Serialize cpa() (for !DEBUG_PAGEALLOC which uses large identity mappings) +- * using cpa_lock. So that we don't allow any other cpu, with stale large tlb +- * entries change the page attribute in parallel to some other cpu +- * splitting a large page entry along with changing the attribute. ++ * Serialize cpa() using cpa_lock so that we don't allow any other cpu, with ++ * stale large tlb entries, to change the page attribute in parallel to some ++ * other cpu splitting a large page entry along with changing the attribute. + */ + static DEFINE_SPINLOCK(cpa_lock); + +-#define CPA_FLUSHTLB 1 +-#define CPA_ARRAY 2 +-#define CPA_PAGES_ARRAY 4 +-#define CPA_NO_CHECK_ALIAS 8 /* Do not search for aliases */ +-#define CPA_COLLAPSE 16 /* try to collapse large pages */ ++#define CPA_FLUSHTLB 0x01 ++#define CPA_ARRAY 0x02 ++#define CPA_PAGES_ARRAY 0x04 ++#define CPA_NO_CHECK_ALIAS 0x08 /* Do not search for aliases */ ++#define CPA_COLLAPSE 0x10 /* try to collapse large pages */ ++#define CPA_DEBUG_PAGEALLOC 0x20 + + static inline pgprot_t cachemode2pgprot(enum page_cache_mode pcm) + { +@@ -86,9 +86,8 @@ static unsigned long direct_pages_count[PG_LEVEL_NUM]; + void update_page_count(int level, unsigned long pages) + { + /* Protect against CPA */ +- spin_lock(&pgd_lock); ++ guard(spinlock)(&pgd_lock); + direct_pages_count[level] += pages; +- spin_unlock(&pgd_lock); + } + + static void split_page_count(int level) +@@ -418,6 +417,8 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa) + int collapsed = 0; + int i; + ++ guard(spinlock)(&cpa_lock); ++ + if (cpa->flags & (CPA_PAGES_ARRAY | CPA_ARRAY)) { + for (i = 0; i < cpa->numpages; i++) + collapsed += collapse_large_pages(__cpa_addr(cpa, i), +@@ -888,24 +889,23 @@ static void __set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte) + { + /* change init_mm */ + set_pte_atomic(kpte, pte); +-#ifdef CONFIG_X86_32 +- { +- struct page *page; + +- list_for_each_entry(page, &pgd_list, lru) { ++ if (IS_ENABLED(CONFIG_X86_32)) { ++ struct ptdesc *ptdesc; ++ ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + pgd_t *pgd; + p4d_t *p4d; + pud_t *pud; + pmd_t *pmd; + +- pgd = (pgd_t *)page_address(page) + pgd_index(address); ++ pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(address); + p4d = p4d_offset(pgd, address); + pud = pud_offset(p4d, address); + pmd = pmd_offset(pud, address); + set_pte_atomic((pte_t *)pmd, pte); + } + } +-#endif + } + + static pgprot_t pgprot_clear_protnone_bits(pgprot_t prot) +@@ -1075,16 +1075,11 @@ static int __should_split_large_page(pte_t *kpte, unsigned long address, + static int should_split_large_page(pte_t *kpte, unsigned long address, + struct cpa_data *cpa) + { +- int do_split; +- + if (cpa->force_split) + return 1; + +- spin_lock(&pgd_lock); +- do_split = __should_split_large_page(kpte, address, cpa); +- spin_unlock(&pgd_lock); +- +- return do_split; ++ guard(spinlock)(&pgd_lock); ++ return __should_split_large_page(kpte, address, cpa); + } + + static void split_set_pte(struct cpa_data *cpa, pte_t *pte, unsigned long pfn, +@@ -1135,16 +1130,14 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, + bool nx, rw; + pte_t *tmp; + +- spin_lock(&pgd_lock); ++ guard(spinlock)(&pgd_lock); + /* + * Check for races, another CPU might have split this page + * up for us already: + */ + tmp = _lookup_address_cpa(cpa, address, &level, &nx, &rw); +- if (tmp != kpte) { +- spin_unlock(&pgd_lock); ++ if (tmp != kpte) + return 1; +- } + + paravirt_alloc_pte(&init_mm, page_to_pfn(base)); + +@@ -1177,7 +1170,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, + break; + + default: +- spin_unlock(&pgd_lock); + return 1; + } + +@@ -1225,7 +1217,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, + * just split large page entry. + */ + flush_tlb_all(); +- spin_unlock(&pgd_lock); + + return 0; + } +@@ -1235,11 +1226,9 @@ static int split_large_page(struct cpa_data *cpa, pte_t *kpte, + { + struct ptdesc *ptdesc; + +- if (!debug_pagealloc_enabled()) +- spin_unlock(&cpa_lock); ++ spin_unlock(&cpa_lock); + ptdesc = pagetable_alloc(GFP_KERNEL, 0); +- if (!debug_pagealloc_enabled()) +- spin_lock(&cpa_lock); ++ spin_lock(&cpa_lock); + if (!ptdesc) + return -ENOMEM; + +@@ -1298,11 +1287,11 @@ static int collapse_pmd_page(pmd_t *pmd, unsigned long addr, + list_add(&page_ptdesc(pmd_page(old_pmd))->pt_list, pgtables); + + if (IS_ENABLED(CONFIG_X86_32)) { +- struct page *page; ++ struct ptdesc *ptdesc; + + /* Update all PGD tables to use the same large page */ +- list_for_each_entry(page, &pgd_list, lru) { +- pgd_t *pgd = (pgd_t *)page_address(page) + pgd_index(addr); ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { ++ pgd_t *pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(addr); + p4d_t *p4d = p4d_offset(pgd, addr); + pud_t *pud = pud_offset(p4d, addr); + pmd_t *pmd = pmd_offset(pud, addr); +@@ -1376,7 +1365,7 @@ static int collapse_pud_page(pud_t *pud, unsigned long addr, + */ + static int collapse_large_pages(unsigned long addr, struct list_head *pgtables) + { +- int collapsed = 0; ++ int collapsed; + pgd_t *pgd; + p4d_t *p4d; + pud_t *pud; +@@ -1384,26 +1373,24 @@ static int collapse_large_pages(unsigned long addr, struct list_head *pgtables) + + addr &= PMD_MASK; + +- spin_lock(&pgd_lock); ++ guard(spinlock)(&pgd_lock); + pgd = pgd_offset_k(addr); + if (pgd_none(*pgd)) +- goto out; ++ return 0; + p4d = p4d_offset(pgd, addr); + if (p4d_none(*p4d)) +- goto out; ++ return 0; + pud = pud_offset(p4d, addr); + if (!pud_present(*pud) || pud_leaf(*pud)) +- goto out; ++ return 0; + pmd = pmd_offset(pud, addr); + if (!pmd_present(*pmd) || pmd_leaf(*pmd)) +- goto out; ++ return 0; + + collapsed = collapse_pmd_page(pmd, addr, pgtables); + if (collapsed) + collapsed += collapse_pud_page(pud, addr, pgtables); + +-out: +- spin_unlock(&pgd_lock); + return collapsed; + } + +@@ -2004,6 +1991,7 @@ static int __change_page_attr_set_clr(struct cpa_data *cpa, int primary) + { + unsigned long numpages = cpa->numpages; + unsigned long rempages = numpages; ++ bool lock = true; + int ret = 0; + + /* +@@ -2013,6 +2001,29 @@ static int __change_page_attr_set_clr(struct cpa_data *cpa, int primary) + !cpa->force_split) + return ret; + ++ /* ++ * DEBUG_PAGEALLOC is special; it is called from any context the ++ * page-allocator is, which violates the normal cpa_lock locking ++ * rules. ++ * ++ * However, since it is part of the page-allocator, things are still ++ * properly serialized by the page-allocator locking and the fact that ++ * when a page is owned by the page-allocator, it isn't owned by ++ * anybody else. That is, you *SHOULD NOT* be calling cpa() on memory ++ * that isn't allocated. ++ * ++ * Additionally, DEBUG_PAGEALLOC ensures (per probe_page_size_mask()) ++ * that the kernel mapping is 4k pages, therefore there are no large ++ * pages to split/collapse. ++ * ++ * Furthermore, the page-allocator strictly manages pages that ++ * *exist*, avoiding pgd_lock. ++ * ++ * Therefore, it is safe to not take cpa_lock. ++ */ ++ if (debug_pagealloc_enabled() && (cpa->flags & CPA_DEBUG_PAGEALLOC)) ++ lock = false; ++ + while (rempages) { + /* + * Store the remaining nr of pages for the large page +@@ -2023,11 +2034,12 @@ static int __change_page_attr_set_clr(struct cpa_data *cpa, int primary) + if (cpa->flags & (CPA_ARRAY | CPA_PAGES_ARRAY)) + cpa->numpages = 1; + +- if (!debug_pagealloc_enabled()) +- spin_lock(&cpa_lock); +- ret = __change_page_attr(cpa, primary); +- if (!debug_pagealloc_enabled()) +- spin_unlock(&cpa_lock); ++ if (lock) { ++ guard(spinlock)(&cpa_lock); ++ ret = __change_page_attr(cpa, primary); ++ } else { ++ ret = __change_page_attr(cpa, primary); ++ } + if (ret) + goto out; + +@@ -2606,7 +2618,7 @@ int set_pages_rw(struct page *page, int numpages) + return set_memory_rw(addr, numpages); + } + +-static int __set_pages_p(struct page *page, int numpages) ++static int __set_pages_p(struct page *page, int numpages, unsigned int cpa_flags) + { + unsigned long tempaddr = (unsigned long) page_address(page); + struct cpa_data cpa = { .vaddr = &tempaddr, +@@ -2614,7 +2626,7 @@ static int __set_pages_p(struct page *page, int numpages) + .numpages = numpages, + .mask_set = __pgprot(_PAGE_PRESENT | _PAGE_RW), + .mask_clr = __pgprot(0), +- .flags = CPA_NO_CHECK_ALIAS }; ++ .flags = CPA_NO_CHECK_ALIAS | cpa_flags }; + + /* + * No alias checking needed for setting present flag. otherwise, +@@ -2625,7 +2637,7 @@ static int __set_pages_p(struct page *page, int numpages) + return __change_page_attr_set_clr(&cpa, 1); + } + +-static int __set_pages_np(struct page *page, int numpages) ++static int __set_pages_np(struct page *page, int numpages, unsigned int cpa_flags) + { + unsigned long tempaddr = (unsigned long) page_address(page); + struct cpa_data cpa = { .vaddr = &tempaddr, +@@ -2633,7 +2645,7 @@ static int __set_pages_np(struct page *page, int numpages) + .numpages = numpages, + .mask_set = __pgprot(0), + .mask_clr = __pgprot(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY), +- .flags = CPA_NO_CHECK_ALIAS }; ++ .flags = CPA_NO_CHECK_ALIAS | cpa_flags }; + + /* + * No alias checking needed for setting not present flag. otherwise, +@@ -2646,20 +2658,20 @@ static int __set_pages_np(struct page *page, int numpages) + + int set_direct_map_invalid_noflush(struct page *page) + { +- return __set_pages_np(page, 1); ++ return __set_pages_np(page, 1, 0); + } + + int set_direct_map_default_noflush(struct page *page) + { +- return __set_pages_p(page, 1); ++ return __set_pages_p(page, 1, 0); + } + + int set_direct_map_valid_noflush(struct page *page, unsigned nr, bool valid) + { + if (valid) +- return __set_pages_p(page, nr); ++ return __set_pages_p(page, nr, 0); + +- return __set_pages_np(page, nr); ++ return __set_pages_np(page, nr, 0); + } + + #ifdef CONFIG_DEBUG_PAGEALLOC +@@ -2678,15 +2690,23 @@ void __kernel_map_pages(struct page *page, int numpages, int enable) + * and hence no memory allocations during large page split. + */ + if (enable) +- __set_pages_p(page, numpages); ++ __set_pages_p(page, numpages, CPA_DEBUG_PAGEALLOC); + else +- __set_pages_np(page, numpages); ++ __set_pages_np(page, numpages, CPA_DEBUG_PAGEALLOC); + + /* +- * We should perform an IPI and flush all tlbs, +- * but that can deadlock->flush only current cpu. +- * Preemption needs to be disabled around __flush_tlb_all() due to +- * CR3 reload in __native_flush_tlb(). ++ * We should perform an IPI and flush all tlbs, but that can ++ * deadlock, settle for a local flush. ++ * ++ * Not doing a global TLB flush means that remote CPUs will retain ++ * stale TLB entries. In case of P->NP (on free) this means the remote ++ * CPUs will not take the faults, making the debug scheme less ++ * reliable. On the NP->P (on alloc) this means the remote CPUs can ++ * take a spurious fault. However spurious_kernel_fault() will observe ++ * *_present() and fix it up. ++ * ++ * Preemption needs to be disabled around __flush_tlb_all() due to CR3 ++ * reload in __native_flush_tlb(). + */ + preempt_disable(); + __flush_tlb_all(); +diff --git a/arch/x86/mm/pgtable.c b/arch/x86/mm/pgtable.c +--- a/arch/x86/mm/pgtable.c ++++ b/arch/x86/mm/pgtable.c +@@ -74,9 +74,9 @@ static void pgd_set_mm(pgd_t *pgd, struct mm_struct *mm) + virt_to_ptdesc(pgd)->pt_mm = mm; + } + +-struct mm_struct *pgd_page_get_mm(struct page *page) ++struct mm_struct *pgd_page_get_mm(struct ptdesc *pt) + { +- return page_ptdesc(page)->pt_mm; ++ return pt->pt_mm; + } + + static void pgd_ctor(struct mm_struct *mm, pgd_t *pgd) +diff --git a/arch/x86/mm/tlb.c b/arch/x86/mm/tlb.c +--- a/arch/x86/mm/tlb.c ++++ b/arch/x86/mm/tlb.c +@@ -1373,35 +1373,19 @@ void flush_tlb_multi(const struct cpumask *cpumask, + */ + unsigned long tlb_single_page_flush_ceiling __read_mostly = 33; + +-static DEFINE_PER_CPU_SHARED_ALIGNED(struct flush_tlb_info, flush_tlb_info); +- +-#ifdef CONFIG_DEBUG_VM +-static DEFINE_PER_CPU(unsigned int, flush_tlb_info_idx); +-#endif +- +-static struct flush_tlb_info *get_flush_tlb_info(struct mm_struct *mm, +- unsigned long start, unsigned long end, +- unsigned int stride_shift, bool freed_tables, +- u64 new_tlb_gen) ++static void init_flush_tlb_info(struct flush_tlb_info *info, ++ struct mm_struct *mm, ++ unsigned long start, unsigned long end, ++ unsigned int stride_shift, bool freed_tables, ++ u64 new_tlb_gen) + { +- struct flush_tlb_info *info = this_cpu_ptr(&flush_tlb_info); +- +-#ifdef CONFIG_DEBUG_VM +- /* +- * Ensure that the following code is non-reentrant and flush_tlb_info +- * is not overwritten. This means no TLB flushing is initiated by +- * interrupt handlers and machine-check exception handlers. +- */ +- BUG_ON(this_cpu_inc_return(flush_tlb_info_idx) != 1); +-#endif +- + /* + * If the number of flushes is so large that a full flush + * would be faster, do a full flush. + */ + if ((end - start) >> stride_shift > tlb_single_page_flush_ceiling) { +- start = 0; +- end = TLB_FLUSH_ALL; ++ start = 0; ++ end = TLB_FLUSH_ALL; + } + + info->start = start; +@@ -1412,32 +1396,21 @@ static struct flush_tlb_info *get_flush_tlb_info(struct mm_struct *mm, + info->new_tlb_gen = new_tlb_gen; + info->initiating_cpu = smp_processor_id(); + info->trim_cpumask = 0; +- +- return info; +-} +- +-static void put_flush_tlb_info(void) +-{ +-#ifdef CONFIG_DEBUG_VM +- /* Complete reentrancy prevention checks */ +- barrier(); +- this_cpu_dec(flush_tlb_info_idx); +-#endif + } + + void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, + unsigned long end, unsigned int stride_shift, + bool freed_tables) + { +- struct flush_tlb_info *info; ++ struct flush_tlb_info info; ++ bool remote_flush = false; + int cpu = get_cpu(); + u64 new_tlb_gen; + + /* This is also a barrier that synchronizes with switch_mm(). */ + new_tlb_gen = inc_mm_tlb_gen(mm); + +- info = get_flush_tlb_info(mm, start, end, stride_shift, freed_tables, +- new_tlb_gen); ++ init_flush_tlb_info(&info, mm, start, end, stride_shift, freed_tables, new_tlb_gen); + + /* + * flush_tlb_multi() is not optimized for the common case in which only +@@ -1445,20 +1418,24 @@ void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, + * flush_tlb_func_local() directly in this case. + */ + if (mm_global_asid(mm)) { +- broadcast_tlb_flush(info); ++ broadcast_tlb_flush(&info); + } else if (cpumask_any_but(mm_cpumask(mm), cpu) < nr_cpu_ids) { +- info->trim_cpumask = should_trim_cpumask(mm); +- flush_tlb_multi(mm_cpumask(mm), info); +- consider_global_asid(mm); ++ remote_flush = true; + } else if (mm == this_cpu_read(cpu_tlbstate.loaded_mm)) { + lockdep_assert_irqs_enabled(); + local_irq_disable(); +- flush_tlb_func(info); ++ flush_tlb_func(&info); + local_irq_enable(); + } + +- put_flush_tlb_info(); + put_cpu(); ++ ++ if (remote_flush) { ++ info.trim_cpumask = should_trim_cpumask(mm); ++ flush_tlb_multi(mm_cpumask(mm), &info); ++ consider_global_asid(mm); ++ } ++ + mmu_notifier_arch_invalidate_secondary_tlbs(mm, start, end); + } + +@@ -1527,19 +1504,16 @@ static void kernel_tlb_flush_range(struct flush_tlb_info *info) + + void flush_tlb_kernel_range(unsigned long start, unsigned long end) + { +- struct flush_tlb_info *info; ++ struct flush_tlb_info info; + + guard(preempt)(); ++ init_flush_tlb_info(&info, NULL, start, end, PAGE_SHIFT, false, ++ TLB_GENERATION_INVALID); + +- info = get_flush_tlb_info(NULL, start, end, PAGE_SHIFT, false, +- TLB_GENERATION_INVALID); +- +- if (info->end == TLB_FLUSH_ALL) +- kernel_tlb_flush_all(info); ++ if (info.end == TLB_FLUSH_ALL) ++ kernel_tlb_flush_all(&info); + else +- kernel_tlb_flush_range(info); +- +- put_flush_tlb_info(); ++ kernel_tlb_flush_range(&info); + } + + /* +@@ -1707,12 +1681,12 @@ EXPORT_SYMBOL_FOR_KVM(__flush_tlb_all); + + void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch) + { +- struct flush_tlb_info *info; +- ++ struct flush_tlb_info info; ++ bool remote_flush = false; + int cpu = get_cpu(); + +- info = get_flush_tlb_info(NULL, 0, TLB_FLUSH_ALL, 0, false, +- TLB_GENERATION_INVALID); ++ init_flush_tlb_info(&info, NULL, 0, TLB_FLUSH_ALL, 0, false, ++ TLB_GENERATION_INVALID); + /* + * flush_tlb_multi() is not optimized for the common case in which only + * a local TLB flush is needed. Optimize this use-case by calling +@@ -1722,18 +1696,20 @@ void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch) + invlpgb_flush_all_nonglobals(); + batch->unmapped_pages = false; + } else if (cpumask_any_but(&batch->cpumask, cpu) < nr_cpu_ids) { +- flush_tlb_multi(&batch->cpumask, info); ++ remote_flush = true; + } else if (cpumask_test_cpu(cpu, &batch->cpumask)) { + lockdep_assert_irqs_enabled(); + local_irq_disable(); +- flush_tlb_func(info); ++ flush_tlb_func(&info); + local_irq_enable(); + } + +- cpumask_clear(&batch->cpumask); +- +- put_flush_tlb_info(); + put_cpu(); ++ ++ if (remote_flush) ++ flush_tlb_multi(&batch->cpumask, &info); ++ ++ cpumask_clear(&batch->cpumask); + } + + /* +diff --git a/arch/x86/xen/mmu_pv.c b/arch/x86/xen/mmu_pv.c +--- a/arch/x86/xen/mmu_pv.c ++++ b/arch/x86/xen/mmu_pv.c +@@ -836,15 +836,15 @@ static void xen_pgd_pin(struct mm_struct *mm) + */ + void xen_mm_pin_all(void) + { +- struct page *page; ++ struct ptdesc *ptdesc; + + spin_lock(&init_mm.page_table_lock); + spin_lock(&pgd_lock); + +- list_for_each_entry(page, &pgd_list, lru) { +- if (!PagePinned(page)) { +- __xen_pgd_pin(&init_mm, (pgd_t *)page_address(page)); +- SetPageSavePinned(page); ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { ++ if (!PagePinned(ptdesc_page(ptdesc))) { ++ __xen_pgd_pin(&init_mm, (pgd_t *)ptdesc_address(ptdesc)); ++ SetPageSavePinned(ptdesc_page(ptdesc)); + } + } + +@@ -947,16 +947,16 @@ static void xen_pgd_unpin(struct mm_struct *mm) + */ + void xen_mm_unpin_all(void) + { +- struct page *page; ++ struct ptdesc *ptdesc; + + spin_lock(&init_mm.page_table_lock); + spin_lock(&pgd_lock); + +- list_for_each_entry(page, &pgd_list, lru) { +- if (PageSavePinned(page)) { +- BUG_ON(!PagePinned(page)); +- __xen_pgd_unpin(&init_mm, (pgd_t *)page_address(page)); +- ClearPageSavePinned(page); ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { ++ if (PageSavePinned(ptdesc_page(ptdesc))) { ++ BUG_ON(!PagePinned(ptdesc_page(ptdesc))); ++ __xen_pgd_unpin(&init_mm, (pgd_t *)ptdesc_address(ptdesc)); ++ ClearPageSavePinned(ptdesc_page(ptdesc)); + } + } + diff --git a/pkgbuilds/linux-omarchy/0121-smp-preempt.patch b/pkgbuilds/linux-omarchy/0121-smp-preempt.patch new file mode 100644 index 0000000..59d8c69 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0121-smp-preempt.patch @@ -0,0 +1,514 @@ +diff --git a/include/linux/sched.h b/include/linux/sched.h +--- a/include/linux/sched.h ++++ b/include/linux/sched.h +@@ -823,6 +823,17 @@ struct kmap_ctrl { + #endif + }; + ++#if defined(CONFIG_SMP) && defined(CONFIG_PREEMPTION) ++struct task_ipi_mask { ++ union { ++ cpumask_t *ipi_mask_ptr; ++ unsigned long ipi_mask_val; ++ }; ++}; ++#else ++struct task_ipi_mask { }; ++#endif ++ + struct task_struct { + #ifdef CONFIG_THREAD_INFO_IN_TASK + /* +@@ -1359,6 +1370,7 @@ struct task_struct { + struct list_head perf_event_list; + struct perf_ctx_data __rcu *perf_ctx_data; + #endif ++ struct task_ipi_mask __private ipi_mask; + #ifdef CONFIG_DEBUG_PREEMPT + unsigned long preempt_disable_ip; + #endif +diff --git a/include/linux/smp.h b/include/linux/smp.h +--- a/include/linux/smp.h ++++ b/include/linux/smp.h +@@ -47,8 +47,7 @@ extern void __smp_call_single_queue(int cpu, struct llist_node *node); + /* total number of cpus in this system (may exceed NR_CPUS) */ + extern unsigned int total_cpus; + +-int smp_call_function_single(int cpuid, smp_call_func_t func, void *info, +- int wait); ++int smp_call_function_single(int cpuid, smp_call_func_t func, void *info, bool wait); + + void on_each_cpu_cond_mask(smp_cond_func_t cond_func, smp_call_func_t func, + void *info, bool wait, const struct cpumask *mask); +@@ -239,6 +238,18 @@ static inline int get_boot_cpu_id(void) + + #endif /* !SMP */ + ++#if defined(CONFIG_PREEMPTION) && defined(CONFIG_SMP) ++int smp_task_ipi_mask_alloc(struct task_struct *task); ++void smp_task_ipi_mask_free(struct task_struct *task); ++#else ++static inline int smp_task_ipi_mask_alloc(struct task_struct *task) ++{ ++ return 0; ++} ++ ++static inline void smp_task_ipi_mask_free(struct task_struct *task) { } ++#endif ++ + /* + * raw_smp_processor_id() - get the current (unstable) CPU id + * +diff --git a/kernel/fork.c b/kernel/fork.c +--- a/kernel/fork.c ++++ b/kernel/fork.c +@@ -543,6 +543,7 @@ void free_task(struct task_struct *tsk) + #endif + release_user_cpus_ptr(tsk); + scs_release(tsk); ++ smp_task_ipi_mask_free(tsk); + + #ifndef CONFIG_THREAD_INFO_IN_TASK + /* +@@ -941,10 +942,14 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node) + #endif + account_kernel_stack(tsk, 1); + +- err = scs_prepare(tsk, node); ++ err = smp_task_ipi_mask_alloc(tsk); + if (err) + goto free_stack; + ++ err = scs_prepare(tsk, node); ++ if (err) ++ goto free_ipi_mask; ++ + #ifdef CONFIG_SECCOMP + /* + * We must handle setting up seccomp filters once we're under +@@ -1022,6 +1027,8 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node) + #endif + return tsk; + ++free_ipi_mask: ++ smp_task_ipi_mask_free(tsk); + free_stack: + exit_task_stack_account(tsk); + free_thread_stack(tsk); +diff --git a/kernel/scftorture.c b/kernel/scftorture.c +--- a/kernel/scftorture.c ++++ b/kernel/scftorture.c +@@ -348,6 +348,8 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + int ret = 0; + struct scf_check *scfcp = NULL; + struct scf_selector *scfsp = scf_sel_rand(trsp); ++ bool is_single = (scfsp->scfs_prim == SCF_PRIM_SINGLE || ++ scfsp->scfs_prim == SCF_PRIM_SINGLE_RPC); + + if (scfsp->scfs_prim == SCF_PRIM_SINGLE || scfsp->scfs_wait) { + scfcp = kmalloc_obj(*scfcp, GFP_ATOMIC); +@@ -364,8 +366,6 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + } + if (use_cpus_read_lock) + cpus_read_lock(); +- else +- preempt_disable(); + switch (scfsp->scfs_prim) { + case SCF_PRIM_RESCHED: + if (IS_BUILTIN(CONFIG_SCF_TORTURE_TEST)) { +@@ -411,13 +411,10 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + if (!ret) { + if (use_cpus_read_lock) + cpus_read_unlock(); +- else +- preempt_enable(); ++ + wait_for_completion(&scfcp->scfc_completion); + if (use_cpus_read_lock) + cpus_read_lock(); +- else +- preempt_disable(); + } else { + scfp->n_single_rpc_ofl++; + scf_add_to_free_list(scfcp); +@@ -452,7 +449,7 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + scfcp->scfc_out = true; + } + if (scfcp && scfsp->scfs_wait) { +- if (WARN_ON_ONCE((num_online_cpus() > 1 || scfsp->scfs_prim == SCF_PRIM_SINGLE) && ++ if (WARN_ON_ONCE(((use_cpus_read_lock && num_online_cpus() > 1) || is_single) && + !scfcp->scfc_out)) { + pr_warn("%s: Memory-ordering failure, scfs_prim: %d.\n", __func__, scfsp->scfs_prim); + atomic_inc(&n_mb_out_errs); // Leak rather than trash! +@@ -463,8 +460,6 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + } + if (use_cpus_read_lock) + cpus_read_unlock(); +- else +- preempt_enable(); + if (allocfail) + schedule_timeout_idle((1 + longwait) * HZ); // Let no-wait handlers complete. + else if (!(torture_random(trsp) & 0xfff)) +diff --git a/kernel/smp.c b/kernel/smp.c +--- a/kernel/smp.c ++++ b/kernel/smp.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -63,7 +64,14 @@ int smpcfd_prepare_cpu(unsigned int cpu) + free_cpumask_var(cfd->cpumask); + return -ENOMEM; + } +- cfd->csd = alloc_percpu(call_single_data_t); ++ ++ /* ++ * Allocate the per-CPU CSD the first time a CPU comes up. It is ++ * not freed when the CPU is offlined, so csd_lock_wait() can access ++ * it even when the CPU was offlined after preemption was re-enabled. ++ */ ++ if (!cfd->csd) ++ cfd->csd = alloc_percpu(call_single_data_t); + if (!cfd->csd) { + free_cpumask_var(cfd->cpumask); + free_cpumask_var(cfd->cpumask_ipi); +@@ -79,7 +87,6 @@ int smpcfd_dead_cpu(unsigned int cpu) + + free_cpumask_var(cfd->cpumask); + free_cpumask_var(cfd->cpumask_ipi); +- free_percpu(cfd->csd); + return 0; + } + +@@ -323,6 +330,8 @@ static void __csd_lock_wait(call_single_data_t *csd) + int bug_id = 0; + u64 ts0, ts1; + ++ guard(preempt)(); ++ + ts1 = ts0 = ktime_get_mono_fast_ns(); + for (;;) { + if (csd_lock_wait_toolong(csd, ts0, &ts1, &bug_id, &nmessages)) +@@ -659,17 +668,9 @@ void flush_smp_call_function_queue(void) + local_irq_restore(flags); + } + +-/** +- * smp_call_function_single - Run a function on a specific CPU +- * @cpu: Specific target CPU for this function. +- * @func: The function to run. This must be fast and non-blocking. +- * @info: An arbitrary pointer to pass to the function. +- * @wait: If true, wait until function has completed on other CPUs. +- * +- * Returns: %0 on success, else a negative status code. +- */ +-int smp_call_function_single(int cpu, smp_call_func_t func, void *info, +- int wait) ++static int __smp_call_function_single(int cpu, smp_call_func_t func, ++ void *info, const struct cpumask *mask, ++ bool wait) + { + call_single_data_t *csd; + call_single_data_t csd_stack = { +@@ -686,6 +687,14 @@ int smp_call_function_single(int cpu, smp_call_func_t func, void *info, + */ + this_cpu = get_cpu(); + ++ if (mask) { ++ /* Try for same CPU (cheapest) */ ++ if (!cpumask_test_cpu(this_cpu, mask)) ++ cpu = sched_numa_find_nth_cpu(mask, 0, cpu_to_node(this_cpu)); ++ else ++ cpu = this_cpu; ++ } ++ + /* + * Can deadlock when called with interrupts disabled. + * We allow cpu's that are not yet online though, as no one else can +@@ -718,13 +727,32 @@ int smp_call_function_single(int cpu, smp_call_func_t func, void *info, + + err = generic_exec_single(cpu, csd); + ++ /* ++ * @csd is stack-allocated when @wait is true. No concurrent access ++ * except from the IPI completion path, so we can re-enable preemption ++ * early to reduce latency. ++ */ ++ put_cpu(); ++ + if (wait) + csd_lock_wait(csd); + +- put_cpu(); +- + return err; + } ++ ++/** ++ * smp_call_function_single - Run a function on a specific CPU ++ * @cpu: Specific target CPU for this function. ++ * @func: The function to run. This must be fast and non-blocking. ++ * @info: An arbitrary pointer to pass to the function. ++ * @wait: If true, wait until function has completed on other CPUs. ++ * ++ * Returns: %0 on success, else a negative status code. ++ */ ++int smp_call_function_single(int cpu, smp_call_func_t func, void *info, bool wait) ++{ ++ return __smp_call_function_single(cpu, func, info, NULL, wait); ++} + EXPORT_SYMBOL(smp_call_function_single); + + /** +@@ -775,10 +803,10 @@ EXPORT_SYMBOL_GPL(smp_call_function_single_async); + + /** + * smp_call_function_any - Run a function on any of the given cpus +- * @mask: The mask of cpus it can run on. +- * @func: The function to run. This must be fast and non-blocking. +- * @info: An arbitrary pointer to pass to the function. +- * @wait: If true, wait until function has completed. ++ * @mask: The mask of cpus it can run on. ++ * @func: The function to run. This must be fast and non-blocking. ++ * @info: An arbitrary pointer to pass to the function. ++ * @wait: If true, wait until function has completed. + * + * Selection preference: + * 1) current cpu if in @mask +@@ -789,20 +817,54 @@ EXPORT_SYMBOL_GPL(smp_call_function_single_async); + int smp_call_function_any(const struct cpumask *mask, + smp_call_func_t func, void *info, int wait) + { +- unsigned int cpu; +- int ret; +- +- /* Try for same CPU (cheapest) */ +- cpu = get_cpu(); +- if (!cpumask_test_cpu(cpu, mask)) +- cpu = sched_numa_find_nth_cpu(mask, 0, cpu_to_node(cpu)); +- +- ret = smp_call_function_single(cpu, func, info, wait); +- put_cpu(); +- return ret; ++ return __smp_call_function_single(-1, func, info, mask, wait); + } + EXPORT_SYMBOL_GPL(smp_call_function_any); + ++static DEFINE_STATIC_KEY_FALSE(ipi_mask_inlined); ++ ++#ifdef CONFIG_PREEMPTION ++ ++int smp_task_ipi_mask_alloc(struct task_struct *task) ++{ ++ if (static_branch_unlikely(&ipi_mask_inlined)) ++ return 0; ++ ++ ACCESS_PRIVATE(task, ipi_mask).ipi_mask_ptr = ++ kmalloc(cpumask_size(), GFP_KERNEL); ++ if (!ACCESS_PRIVATE(task, ipi_mask).ipi_mask_ptr) ++ return -ENOMEM; ++ ++ return 0; ++} ++ ++void smp_task_ipi_mask_free(struct task_struct *task) ++{ ++ if (static_branch_unlikely(&ipi_mask_inlined)) ++ return; ++ ++ kfree(ACCESS_PRIVATE(task, ipi_mask).ipi_mask_ptr); ++} ++ ++static cpumask_t *smp_task_ipi_mask(struct task_struct *cur) ++{ ++ /* ++ * If cpumask_size() is smaller than or equal to the pointer ++ * size, it stashes the cpumask in the pointer itself to ++ * avoid extra memory allocations. ++ */ ++ if (static_branch_unlikely(&ipi_mask_inlined)) ++ return (cpumask_t *)&ACCESS_PRIVATE(cur, ipi_mask).ipi_mask_val; ++ ++ return ACCESS_PRIVATE(cur, ipi_mask).ipi_mask_ptr; ++} ++#else ++static cpumask_t *smp_task_ipi_mask(struct task_struct *cur) ++{ ++ return NULL; ++} ++#endif ++ + /* + * Flags to be used as scf_flags argument of smp_call_function_many_cond(). + * +@@ -817,13 +879,20 @@ static void smp_call_function_many_cond(const struct cpumask *mask, + unsigned int scf_flags, + smp_cond_func_t cond_func) + { +- int cpu, last_cpu, this_cpu = smp_processor_id(); +- struct call_function_data *cfd; ++ struct cpumask *cpumask, *task_mask; + bool wait = scf_flags & SCF_WAIT; +- int nr_cpus = 0; ++ struct call_function_data *cfd; ++ int cpu, last_cpu, this_cpu; + bool run_remote = false; ++ int nr_cpus = 0; + +- lockdep_assert_preemption_disabled(); ++ this_cpu = get_cpu(); ++ cfd = this_cpu_ptr(&cfd_data); ++ task_mask = smp_task_ipi_mask(current); ++ if (task_mask) ++ cpumask = task_mask; ++ else ++ cpumask = cfd->cpumask; + + /* + * Can deadlock when called with interrupts disabled. +@@ -845,16 +914,15 @@ static void smp_call_function_many_cond(const struct cpumask *mask, + + /* Check if we need remote execution, i.e., any CPU excluding this one. */ + if (cpumask_any_and_but(mask, cpu_online_mask, this_cpu) < nr_cpu_ids) { +- cfd = this_cpu_ptr(&cfd_data); +- cpumask_and(cfd->cpumask, mask, cpu_online_mask); +- __cpumask_clear_cpu(this_cpu, cfd->cpumask); ++ cpumask_and(cpumask, mask, cpu_online_mask); ++ __cpumask_clear_cpu(this_cpu, cpumask); + + cpumask_clear(cfd->cpumask_ipi); +- for_each_cpu(cpu, cfd->cpumask) { ++ for_each_cpu(cpu, cpumask) { + call_single_data_t *csd = per_cpu_ptr(cfd->csd, cpu); + + if (cond_func && !cond_func(cpu, info)) { +- __cpumask_clear_cpu(cpu, cfd->cpumask); ++ __cpumask_clear_cpu(cpu, cpumask); + continue; + } + +@@ -904,8 +972,18 @@ static void smp_call_function_many_cond(const struct cpumask *mask, + local_irq_restore(flags); + } + ++ /* ++ * The IPI work has been queued and dispatched. On PREEMPT kernels, ++ * tasks created through dup_task_struct() have task-local wait masks. ++ * The boot init_task can fall back to cfd->cpumask when the mask is ++ * not inlined, but other tasks still use task-local masks and cannot ++ * overwrite it. On !PREEMPT kernels, preempt_enable() cannot schedule ++ * another task, so the per-CPU mask remains protected. ++ */ ++ put_cpu(); ++ + if (run_remote && wait) { +- for_each_cpu(cpu, cfd->cpumask) { ++ for_each_cpu(cpu, cpumask) { + call_single_data_t *csd; + + csd = per_cpu_ptr(cfd->csd, cpu); +@@ -916,15 +994,14 @@ static void smp_call_function_many_cond(const struct cpumask *mask, + + /** + * smp_call_function_many() - Run a function on a set of CPUs. +- * @mask: The set of cpus to run on (only runs on online subset). +- * @func: The function to run. This must be fast and non-blocking. +- * @info: An arbitrary pointer to pass to the function. +- * @wait: If true, wait (atomically) until function has completed +- * on other CPUs. ++ * @mask: The set of cpus to run on (only runs on online subset). ++ * @func: The function to run. This must be fast and non-blocking. ++ * @info: An arbitrary pointer to pass to the function. ++ * @wait: If true, wait (atomically) until function has completed ++ * on other CPUs. + * + * You must not call this function with disabled interrupts or from a +- * hardware interrupt handler or from a bottom half handler. Preemption +- * must be disabled when calling this function. ++ * hardware interrupt handler or from a bottom half handler. + * + * @func is not called on the local CPU even if @mask contains it. Consider + * using on_each_cpu_cond_mask() instead if this is not desirable. +@@ -938,10 +1015,10 @@ EXPORT_SYMBOL(smp_call_function_many); + + /** + * smp_call_function() - Run a function on all other CPUs. +- * @func: The function to run. This must be fast and non-blocking. +- * @info: An arbitrary pointer to pass to the function. +- * @wait: If true, wait (atomically) until function has completed +- * on other CPUs. ++ * @func: The function to run. This must be fast and non-blocking. ++ * @info: An arbitrary pointer to pass to the function. ++ * @wait: If true, wait (atomically) until function has completed ++ * on other CPUs. + * + * If @wait is true, then returns once @func has returned; otherwise + * it returns just before the target cpu calls @func. +@@ -951,9 +1028,8 @@ EXPORT_SYMBOL(smp_call_function_many); + */ + void smp_call_function(smp_call_func_t func, void *info, int wait) + { +- preempt_disable(); +- smp_call_function_many(cpu_online_mask, func, info, wait); +- preempt_enable(); ++ smp_call_function_many_cond(cpu_online_mask, func, info, ++ wait ? SCF_WAIT : 0, NULL); + } + EXPORT_SYMBOL(smp_call_function); + +@@ -1019,6 +1095,9 @@ EXPORT_SYMBOL(nr_cpu_ids); + void __init setup_nr_cpu_ids(void) + { + set_nr_cpu_ids(find_last_bit(cpumask_bits(cpu_possible_mask), NR_CPUS) + 1); ++ ++ if (IS_ENABLED(CONFIG_PREEMPTION) && cpumask_size() <= sizeof(unsigned long)) ++ static_branch_enable(&ipi_mask_inlined); + } + + /* Called by boot processor to activate the rest. */ +@@ -1055,12 +1134,14 @@ void __init smp_init(void) + * @func: The function to run on all applicable CPUs. + * This must be fast and non-blocking. + * @info: An arbitrary pointer to pass to both functions. +- * @wait: If true, wait (atomically) until function has +- * completed on other CPUs. ++ * @wait: If true, wait until function has completed on other CPUs. + * @mask: The set of cpus to run on (only runs on online subset). + * +- * Preemption is disabled to protect against CPUs going offline but not online. +- * CPUs going online during the call will not be seen or sent an IPI. ++ * Target CPU selection and work queueing are done with preemption ++ * disabled. This protects against CPUs going offline, but not against ++ * CPUs coming online concurrently; newly online CPUs are not guaranteed ++ * to be seen or sent an IPI. If @wait is true, the final wait for remote ++ * completion happens after that preemption-disabled section. + * + * You must not call this function with disabled interrupts or + * from a hardware interrupt handler or from a bottom half handler. +@@ -1073,9 +1154,7 @@ void on_each_cpu_cond_mask(smp_cond_func_t cond_func, smp_call_func_t func, + if (wait) + scf_flags |= SCF_WAIT; + +- preempt_disable(); + smp_call_function_many_cond(mask, func, info, scf_flags, cond_func); +- preempt_enable(); + } + EXPORT_SYMBOL(on_each_cpu_cond_mask); + +diff --git a/kernel/up.c b/kernel/up.c +--- a/kernel/up.c ++++ b/kernel/up.c +@@ -9,8 +9,7 @@ + #include + #include + +-int smp_call_function_single(int cpu, void (*func) (void *info), void *info, +- int wait) ++int smp_call_function_single(int cpu, void (*func)(void *info), void *info, bool wait) + { + unsigned long flags; + diff --git a/pkgbuilds/linux-omarchy/0130-sched-detach-tasks.patch b/pkgbuilds/linux-omarchy/0130-sched-detach-tasks.patch new file mode 100644 index 0000000..296b430 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0130-sched-detach-tasks.patch @@ -0,0 +1,21 @@ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -13338,12 +13338,15 @@ static int sched_balance_rq(int this_cpu, struct rq *this_rq, + * still unbalanced. ld_moved simply stays zero, so it is + * correctly treated as an imbalance. + */ +- env.loop_max = min(sysctl_sched_nr_migrate, busiest->nr_running); +- + more_balance: + rq_lock_irqsave(busiest, &rf); + update_rq_clock(busiest); + ++ if (!env.loop_max) ++ env.loop_max = min(sysctl_sched_nr_migrate, busiest->cfs.h_nr_queued); ++ else ++ env.loop_max = min(env.loop_max, busiest->cfs.h_nr_queued); ++ + /* + * cur_ld_moved - load moved in current iteration + * ld_moved - cumulative load moved across iterations diff --git a/pkgbuilds/linux-omarchy/0131-sched-avg-idle.patch b/pkgbuilds/linux-omarchy/0131-sched-avg-idle.patch new file mode 100644 index 0000000..5f8bb20 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0131-sched-avg-idle.patch @@ -0,0 +1,23 @@ +diff --git a/kernel/sched/core.c b/kernel/sched/core.c +--- a/kernel/sched/core.c ++++ b/kernel/sched/core.c +@@ -3743,11 +3743,17 @@ static inline void ttwu_do_wakeup(struct task_struct *p) + + void update_rq_avg_idle(struct rq *rq) + { +- u64 delta = rq_clock(rq) - rq->idle_stamp; +- u64 max = 2*rq->max_idle_balance_cost; ++ u64 idle_stamp = rq->idle_stamp; ++ u64 delta, max; ++ ++ if (!idle_stamp) ++ return; ++ ++ delta = rq_clock(rq) - idle_stamp; + + update_avg(&rq->avg_idle, delta); + ++ max = 2 * rq->max_idle_balance_cost; + if (rq->avg_idle > max) + rq->avg_idle = max; + rq->idle_stamp = 0; diff --git a/pkgbuilds/linux-omarchy/0140-sched-always-inline.patch b/pkgbuilds/linux-omarchy/0140-sched-always-inline.patch new file mode 100644 index 0000000..ee0da9b --- /dev/null +++ b/pkgbuilds/linux-omarchy/0140-sched-always-inline.patch @@ -0,0 +1,367 @@ +diff --git a/arch/arm/include/asm/mmu_context.h b/arch/arm/include/asm/mmu_context.h +--- a/arch/arm/include/asm/mmu_context.h ++++ b/arch/arm/include/asm/mmu_context.h +@@ -80,7 +80,7 @@ static inline void check_and_switch_context(struct mm_struct *mm, + #ifndef MODULE + #define finish_arch_post_lock_switch \ + finish_arch_post_lock_switch +-static inline void finish_arch_post_lock_switch(void) ++static __always_inline void finish_arch_post_lock_switch(void) + { + struct mm_struct *mm = current->mm; + +diff --git a/arch/riscv/include/asm/sync_core.h b/arch/riscv/include/asm/sync_core.h +--- a/arch/riscv/include/asm/sync_core.h ++++ b/arch/riscv/include/asm/sync_core.h +@@ -6,7 +6,7 @@ + * RISC-V implements return to user-space through an xRET instruction, + * which is not core serializing. + */ +-static inline void sync_core_before_usermode(void) ++static __always_inline void sync_core_before_usermode(void) + { + asm volatile ("fence.i" ::: "memory"); + } +diff --git a/arch/s390/include/asm/mmu_context.h b/arch/s390/include/asm/mmu_context.h +--- a/arch/s390/include/asm/mmu_context.h ++++ b/arch/s390/include/asm/mmu_context.h +@@ -93,7 +93,7 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, + } + + #define finish_arch_post_lock_switch finish_arch_post_lock_switch +-static inline void finish_arch_post_lock_switch(void) ++static __always_inline void finish_arch_post_lock_switch(void) + { + struct task_struct *tsk = current; + struct mm_struct *mm = tsk->mm; +diff --git a/arch/sparc/include/asm/mmu_context_64.h b/arch/sparc/include/asm/mmu_context_64.h +--- a/arch/sparc/include/asm/mmu_context_64.h ++++ b/arch/sparc/include/asm/mmu_context_64.h +@@ -160,7 +160,7 @@ static inline void arch_start_context_switch(struct task_struct *prev) + } + + #define finish_arch_post_lock_switch finish_arch_post_lock_switch +-static inline void finish_arch_post_lock_switch(void) ++static __always_inline void finish_arch_post_lock_switch(void) + { + /* Restore the state of MCDPER register for the new process + * just switched to. +diff --git a/arch/x86/include/asm/sync_core.h b/arch/x86/include/asm/sync_core.h +--- a/arch/x86/include/asm/sync_core.h ++++ b/arch/x86/include/asm/sync_core.h +@@ -93,7 +93,7 @@ static __always_inline void sync_core(void) + * to user-mode. x86 implements return to user-space through sysexit, + * sysrel, and sysretq, which are not core serializing. + */ +-static inline void sync_core_before_usermode(void) ++static __always_inline void sync_core_before_usermode(void) + { + /* With PTI, we unconditionally serialize before running user code. */ + if (static_cpu_has(X86_FEATURE_PTI)) +diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h +--- a/include/linux/perf_event.h ++++ b/include/linux/perf_event.h +@@ -1632,7 +1632,7 @@ static inline void perf_event_task_migrate(struct task_struct *task) + task->sched_migrated = 1; + } + +-static inline void perf_event_task_sched_in(struct task_struct *prev, ++static __always_inline void perf_event_task_sched_in(struct task_struct *prev, + struct task_struct *task) + { + if (static_branch_unlikely(&perf_sched_events)) +diff --git a/include/linux/sched/mm.h b/include/linux/sched/mm.h +--- a/include/linux/sched/mm.h ++++ b/include/linux/sched/mm.h +@@ -32,7 +32,7 @@ extern struct mm_struct *mm_alloc(void); + * See also for an in-depth explanation + * of &mm_struct.mm_count vs &mm_struct.mm_users. + */ +-static inline void mmgrab(struct mm_struct *mm) ++static __always_inline void mmgrab(struct mm_struct *mm) + { + atomic_inc(&mm->mm_count); + } +@@ -44,7 +44,7 @@ static inline void smp_mb__after_mmgrab(void) + + extern void __mmdrop(struct mm_struct *mm); + +-static inline void mmdrop(struct mm_struct *mm) ++static __always_inline void mmdrop(struct mm_struct *mm) + { + /* + * The implicit full barrier implied by atomic_dec_and_test() is +@@ -71,27 +71,27 @@ static inline void __mmdrop_delayed(struct rcu_head *rhp) + * Invoked from finish_task_switch(). Delegates the heavy lifting on RT + * kernels via RCU. + */ +-static inline void mmdrop_sched(struct mm_struct *mm) ++static __always_inline void mmdrop_sched(struct mm_struct *mm) + { + /* Provides a full memory barrier. See mmdrop() */ + if (atomic_dec_and_test(&mm->mm_count)) + call_rcu(&mm->delayed_drop, __mmdrop_delayed); + } + #else +-static inline void mmdrop_sched(struct mm_struct *mm) ++static __always_inline void mmdrop_sched(struct mm_struct *mm) + { + mmdrop(mm); + } + #endif + + /* Helpers for lazy TLB mm refcounting */ +-static inline void mmgrab_lazy_tlb(struct mm_struct *mm) ++static __always_inline void mmgrab_lazy_tlb(struct mm_struct *mm) + { + if (IS_ENABLED(CONFIG_MMU_LAZY_TLB_REFCOUNT)) + mmgrab(mm); + } + +-static inline void mmdrop_lazy_tlb(struct mm_struct *mm) ++static __always_inline void mmdrop_lazy_tlb(struct mm_struct *mm) + { + if (IS_ENABLED(CONFIG_MMU_LAZY_TLB_REFCOUNT)) { + mmdrop(mm); +@@ -104,7 +104,7 @@ static inline void mmdrop_lazy_tlb(struct mm_struct *mm) + } + } + +-static inline void mmdrop_lazy_tlb_sched(struct mm_struct *mm) ++static __always_inline void mmdrop_lazy_tlb_sched(struct mm_struct *mm) + { + if (IS_ENABLED(CONFIG_MMU_LAZY_TLB_REFCOUNT)) + mmdrop_sched(mm); +@@ -128,12 +128,12 @@ static inline void mmdrop_lazy_tlb_sched(struct mm_struct *mm) + * See also for an in-depth explanation + * of &mm_struct.mm_count vs &mm_struct.mm_users. + */ +-static inline void mmget(struct mm_struct *mm) ++static __always_inline void mmget(struct mm_struct *mm) + { + atomic_inc(&mm->mm_users); + } + +-static inline bool mmget_not_zero(struct mm_struct *mm) ++static __always_inline bool mmget_not_zero(struct mm_struct *mm) + { + return atomic_inc_not_zero(&mm->mm_users); + } +@@ -532,7 +532,7 @@ enum { + #include + #endif + +-static inline void membarrier_mm_sync_core_before_usermode(struct mm_struct *mm) ++static __always_inline void membarrier_mm_sync_core_before_usermode(struct mm_struct *mm) + { + /* + * The atomic_read() below prevents CSE. The following should +diff --git a/include/linux/tick.h b/include/linux/tick.h +--- a/include/linux/tick.h ++++ b/include/linux/tick.h +@@ -175,7 +175,7 @@ extern cpumask_var_t tick_nohz_full_mask; + #ifdef CONFIG_NO_HZ_FULL + extern bool tick_nohz_full_running; + +-static inline bool tick_nohz_full_enabled(void) ++static __always_inline bool tick_nohz_full_enabled(void) + { + if (!context_tracking_enabled()) + return false; +@@ -299,7 +299,7 @@ static inline void __tick_nohz_task_switch(void) { } + static inline void tick_nohz_full_setup(cpumask_var_t cpumask) { } + #endif + +-static inline void tick_nohz_task_switch(void) ++static __always_inline void tick_nohz_task_switch(void) + { + if (tick_nohz_full_enabled()) + __tick_nohz_task_switch(); +diff --git a/include/linux/vtime.h b/include/linux/vtime.h +--- a/include/linux/vtime.h ++++ b/include/linux/vtime.h +@@ -89,24 +89,24 @@ static __always_inline void vtime_account_guest_exit(void) + * For now vtime state is tied to context tracking. We might want to decouple + * those later if necessary. + */ +-static inline bool vtime_accounting_enabled(void) ++static __always_inline bool vtime_accounting_enabled(void) + { + return context_tracking_enabled(); + } + +-static inline bool vtime_accounting_enabled_cpu(int cpu) ++static __always_inline bool vtime_accounting_enabled_cpu(int cpu) + { + return vtime_generic_enabled_cpu(cpu); + } + +-static inline bool vtime_accounting_enabled_this_cpu(void) ++static __always_inline bool vtime_accounting_enabled_this_cpu(void) + { + return vtime_generic_enabled_this_cpu(); + } + + extern void vtime_task_switch_generic(struct task_struct *prev); + +-static inline void vtime_task_switch(struct task_struct *prev) ++static __always_inline void vtime_task_switch(struct task_struct *prev) + { + if (vtime_accounting_enabled_this_cpu()) + vtime_task_switch_generic(prev); +diff --git a/kernel/sched/core.c b/kernel/sched/core.c +--- a/kernel/sched/core.c ++++ b/kernel/sched/core.c +@@ -5091,7 +5091,7 @@ static inline void prepare_task(struct task_struct *next) + WRITE_ONCE(next->on_cpu, 1); + } + +-static inline void finish_task(struct task_struct *prev) ++static __always_inline void finish_task(struct task_struct *prev) + { + /* + * This must be the very last reference to @prev from this CPU. After +@@ -5135,7 +5135,7 @@ static void zap_balance_callbacks(struct rq *rq) + rq->balance_callback = found ? &balance_push_callback : NULL; + } + +-static void do_balance_callbacks(struct rq *rq, struct balance_callback *head) ++static __always_inline void do_balance_callbacks(struct rq *rq, struct balance_callback *head) + { + void (*func)(struct rq *rq); + struct balance_callback *next; +@@ -5170,7 +5170,7 @@ struct balance_callback balance_push_callback = { + .func = balance_push, + }; + +-static inline struct balance_callback * ++static __always_inline struct balance_callback * + __splice_balance_callbacks(struct rq *rq, bool split) + { + struct balance_callback *head = rq->balance_callback; +@@ -5244,7 +5244,7 @@ prepare_lock_switch(struct rq *rq, struct task_struct *next, struct rq_flags *rf + __acquire(__rq_lockp(this_rq())); + } + +-static inline void finish_lock_switch(struct rq *rq) ++static __always_inline void finish_lock_switch(struct rq *rq) + __releases(__rq_lockp(rq)) + { + /* +@@ -5278,7 +5278,7 @@ static inline void kmap_local_sched_out(void) + #endif + } + +-static inline void kmap_local_sched_in(void) ++static __always_inline void kmap_local_sched_in(void) + { + #ifdef CONFIG_KMAP_LOCAL + if (unlikely(current->kmap_ctrl.idx)) +@@ -5332,7 +5332,7 @@ prepare_task_switch(struct rq *rq, struct task_struct *prev, + * past. 'prev == current' is still correct but we need to recalculate this_rq + * because prev may have moved to another CPU. + */ +-static struct rq *finish_task_switch(struct task_struct *prev) ++static __always_inline struct rq *finish_task_switch(struct task_struct *prev) + __releases(__rq_lockp(this_rq())) + { + struct rq *rq = this_rq(); +diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h +--- a/kernel/sched/sched.h ++++ b/kernel/sched/sched.h +@@ -1460,12 +1460,12 @@ static inline struct cpumask *sched_group_span(struct sched_group *sg); + + DECLARE_STATIC_KEY_FALSE(__sched_core_enabled); + +-static inline bool sched_core_enabled(struct rq *rq) ++static __always_inline bool sched_core_enabled(struct rq *rq) + { + return static_branch_unlikely(&__sched_core_enabled) && rq->core_enabled; + } + +-static inline bool sched_core_disabled(void) ++static __always_inline bool sched_core_disabled(void) + { + return !static_branch_unlikely(&__sched_core_enabled); + } +@@ -1474,7 +1474,7 @@ static inline bool sched_core_disabled(void) + * Be careful with this function; not for general use. The return value isn't + * stable unless you actually hold a relevant rq->__lock. + */ +-static inline raw_spinlock_t *rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *rq_lockp(struct rq *rq) + { + if (sched_core_enabled(rq)) + return &rq->core->__lock; +@@ -1482,7 +1482,7 @@ static inline raw_spinlock_t *rq_lockp(struct rq *rq) + return &rq->__lock; + } + +-static inline raw_spinlock_t *__rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *__rq_lockp(struct rq *rq) + __returns_ctx_lock(rq_lockp(rq)) /* alias them */ + { + if (rq->core_enabled) +@@ -1585,12 +1585,12 @@ static inline bool sched_core_disabled(void) + return true; + } + +-static inline raw_spinlock_t *rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *rq_lockp(struct rq *rq) + { + return &rq->__lock; + } + +-static inline raw_spinlock_t *__rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *__rq_lockp(struct rq *rq) + __returns_ctx_lock(rq_lockp(rq)) /* alias them */ + { + return &rq->__lock; +@@ -1650,33 +1650,33 @@ extern void raw_spin_rq_lock_nested(struct rq *rq, int subclass) + extern bool raw_spin_rq_trylock(struct rq *rq) + __cond_acquires(true, __rq_lockp(rq)); + +-static inline void raw_spin_rq_lock(struct rq *rq) ++static __always_inline void raw_spin_rq_lock(struct rq *rq) + __acquires(__rq_lockp(rq)) + { + raw_spin_rq_lock_nested(rq, 0); + } + +-static inline void raw_spin_rq_unlock(struct rq *rq) ++static __always_inline void raw_spin_rq_unlock(struct rq *rq) + __releases(__rq_lockp(rq)) + { + raw_spin_unlock(rq_lockp(rq)); + } + +-static inline void raw_spin_rq_lock_irq(struct rq *rq) ++static __always_inline void raw_spin_rq_lock_irq(struct rq *rq) + __acquires(__rq_lockp(rq)) + { + local_irq_disable(); + raw_spin_rq_lock(rq); + } + +-static inline void raw_spin_rq_unlock_irq(struct rq *rq) ++static __always_inline void raw_spin_rq_unlock_irq(struct rq *rq) + __releases(__rq_lockp(rq)) + { + raw_spin_rq_unlock(rq); + local_irq_enable(); + } + +-static inline unsigned long _raw_spin_rq_lock_irqsave(struct rq *rq) ++static __always_inline unsigned long _raw_spin_rq_lock_irqsave(struct rq *rq) + __acquires(__rq_lockp(rq)) + { + unsigned long flags; +@@ -1687,7 +1687,7 @@ static inline unsigned long _raw_spin_rq_lock_irqsave(struct rq *rq) + return flags; + } + +-static inline void raw_spin_rq_unlock_irqrestore(struct rq *rq, unsigned long flags) ++static __always_inline void raw_spin_rq_unlock_irqrestore(struct rq *rq, unsigned long flags) + __releases(__rq_lockp(rq)) + { + raw_spin_rq_unlock(rq); diff --git a/pkgbuilds/linux-omarchy/0141-sched-urgent-fixes.patch b/pkgbuilds/linux-omarchy/0141-sched-urgent-fixes.patch new file mode 100644 index 0000000..9bc941f --- /dev/null +++ b/pkgbuilds/linux-omarchy/0141-sched-urgent-fixes.patch @@ -0,0 +1,125 @@ +diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c +--- a/kernel/sched/deadline.c ++++ b/kernel/sched/deadline.c +@@ -3026,8 +3026,8 @@ static struct task_struct *pick_next_pushable_dl_task(struct rq *rq) + next_node = rb_first_cached(&rq->dl.pushable_dl_tasks_root); + while (next_node) { + i = __node_2_pdl(next_node); +- /* make sure task isn't on_cpu (possible with proxy-exec) */ +- if (!task_on_cpu(rq, i)) { ++ /* skip tasks that cannot be migrated */ ++ if (!task_on_cpu(rq, i) && !is_migration_disabled(i)) { + p = i; + break; + } +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -10565,17 +10565,40 @@ static enum llc_mig can_migrate_llc(int src_cpu, int dst_cpu, + return mig_llc; + } + ++static inline bool task_misfits_asym_cpu(struct lb_env *env, struct task_struct *p) ++{ ++ /* ++ * On asymmetric CPU capacity domains, do not let cache-aware ++ * balancing pull the task onto a destination CPU that cannot ++ * accommodate it. Doing so would turn the task into a misfit on ++ * the destination, trading a cache-locality gain for a capacity ++ * loss. If the task already does not fit its source CPU, the move ++ * cannot make things worse, so let the LLC preference decide. ++ */ ++ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && p && ++ !task_fits_cpu(p, env->dst_cpu) && ++ task_fits_cpu(p, env->src_cpu)) ++ return true; ++ ++ return false; ++} ++ + /* + * Check if task p can migrate from source LLC to + * destination LLC in terms of cache aware load balance. + */ +-static enum llc_mig can_migrate_llc_task(int src_cpu, int dst_cpu, ++static enum llc_mig can_migrate_llc_task(struct lb_env *env, + struct task_struct *p) + { + struct mm_struct *mm; + bool to_pref; +- int cpu; ++ int cpu, src_cpu, dst_cpu; + ++ if (task_misfits_asym_cpu(env, p)) ++ return mig_forbid; ++ ++ src_cpu = env->src_cpu; ++ dst_cpu = env->dst_cpu; + mm = p->mm; + if (!mm) + return mig_unrestricted; +@@ -10632,6 +10655,14 @@ alb_break_llc(struct lb_env *env) + unsigned long util = 0; + struct task_struct *cur; + ++ /* ++ * Migrating misfit tasks from current CPU ++ * to CPU with a better fit. ++ * Prioritize that over LLC preference. ++ */ ++ if (env->migration_type == migrate_misfit) ++ return false; ++ + if (env->src_rq->nr_running <= 1) + return true; + +@@ -10639,7 +10670,8 @@ alb_break_llc(struct lb_env *env) + if (cur && cur->sched_class == &fair_sched_class) + util = task_util(cur); + +- if (can_migrate_llc(env->src_cpu, env->dst_cpu, ++ if (task_misfits_asym_cpu(env, cur) || ++ can_migrate_llc(env->src_cpu, env->dst_cpu, + util, false) == mig_forbid) + return true; + } +@@ -10679,8 +10711,7 @@ static bool migrate_degrades_llc(struct task_struct *p, struct lb_env *env) + READ_ONCE(p->preferred_llc) != llc_id(env->dst_cpu)) + return true; + +- if (can_migrate_llc_task(env->src_cpu, +- env->dst_cpu, p) != mig_forbid) ++ if (can_migrate_llc_task(env, p) != mig_forbid) + return false; + + return true; +@@ -11743,6 +11774,15 @@ static inline bool llc_balance(struct lb_env *env, struct sg_lb_stats *sgs, + if (env->sd->flags & SD_SHARE_LLC) + return false; + ++ /* ++ * On asymmetric domains, group_misfit_task_load ++ * should be prioritized to move tasks to CPU that fit them ++ * over aggregating tasks to their preferred LLC. ++ */ ++ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && ++ sgs->group_misfit_task_load) ++ return false; ++ + /* + * Skip cache aware tagging if nr_balanced_failed is sufficiently high. + * Threshold of cache_nice_tries is set to 1 higher than nr_balance_failed +diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c +--- a/kernel/sched/rt.c ++++ b/kernel/sched/rt.c +@@ -1871,8 +1871,8 @@ static struct task_struct *pick_next_pushable_task(struct rq *rq) + return NULL; + + plist_for_each_entry(i, head, pushable_tasks) { +- /* make sure task isn't on_cpu (possible with proxy-exec) */ +- if (!task_on_cpu(rq, i)) { ++ /* skip tasks that cannot be migrated */ ++ if (!task_on_cpu(rq, i) && !is_migration_disabled(i)) { + p = i; + break; + } diff --git a/pkgbuilds/linux-omarchy/0142-sched-itmt-no-debugfs-dependency.patch b/pkgbuilds/linux-omarchy/0142-sched-itmt-no-debugfs-dependency.patch new file mode 100644 index 0000000..a0f39ea --- /dev/null +++ b/pkgbuilds/linux-omarchy/0142-sched-itmt-no-debugfs-dependency.patch @@ -0,0 +1,24 @@ +diff --git a/arch/x86/kernel/itmt.c b/arch/x86/kernel/itmt.c +--- a/arch/x86/kernel/itmt.c ++++ b/arch/x86/kernel/itmt.c +@@ -110,18 +110,14 @@ int sched_set_itmt_support(void) + arch_debugfs_dir, + &sysctl_sched_itmt_enabled, + &dfs_sched_itmt_fops); +- if (IS_ERR_OR_NULL(dfs_sched_itmt)) { ++ if (IS_ERR(dfs_sched_itmt)) + dfs_sched_itmt = NULL; +- return -ENOMEM; +- } + + dfs_sched_core_prio = debugfs_create_file("sched_core_priority", 0644, + arch_debugfs_dir, NULL, + &sched_core_priority_fops); +- if (IS_ERR_OR_NULL(dfs_sched_core_prio)) { ++ if (IS_ERR(dfs_sched_core_prio)) + dfs_sched_core_prio = NULL; +- return -ENOMEM; +- } + + sched_itmt_capable = true; + diff --git a/pkgbuilds/linux-omarchy/0143-sched-hybrid-cluster-balancing.patch b/pkgbuilds/linux-omarchy/0143-sched-hybrid-cluster-balancing.patch new file mode 100644 index 0000000..9c10fcd --- /dev/null +++ b/pkgbuilds/linux-omarchy/0143-sched-hybrid-cluster-balancing.patch @@ -0,0 +1,124 @@ +diff --git a/include/linux/sched/sd_flags.h b/include/linux/sched/sd_flags.h +--- a/include/linux/sched/sd_flags.h ++++ b/include/linux/sched/sd_flags.h +@@ -146,8 +146,7 @@ SD_FLAG(SD_ASYM_PACKING, SDF_NEEDS_GROUPS) + /* + * Prefer to place tasks in a sibling domain + * +- * Set up until domains start spanning NUMA nodes. Close to being a SHARED_CHILD +- * flag, but cleared below domains with SD_ASYM_CPUCAPACITY. ++ * Set up until domains start spanning NUMA nodes. + * + * NEEDS_GROUPS: Load balancing flag. + */ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -11902,10 +11902,25 @@ static inline void update_sg_lb_stats(struct lb_env *env, + continue; + + if (sd_flags & SD_ASYM_CPUCAPACITY) { +- /* Check for a misfit task on the cpu */ +- if (sgs->group_misfit_task_load < rq->misfit_task_load) { +- sgs->group_misfit_task_load = rq->misfit_task_load; +- *sg_overloaded = 1; ++ if (rq->misfit_task_load) { ++ /* ++ * Always mark the root domain overloaded so big ++ * CPUs can pick up misfit tasks via newly idle ++ * balance. ++ */ ++ if (balancing_at_rd) ++ *sg_overloaded = 1; ++ ++ /* ++ * Only account misfit load if @dst_cpu can ++ * help; otherwise, the group may be classified ++ * as misfit_task and update_sd_pick_busiest() ++ * will skip it. ++ */ ++ if (capacity_greater(capacity_of(env->dst_cpu), ++ group->sgc->max_capacity) && ++ (sgs->group_misfit_task_load < rq->misfit_task_load)) ++ sgs->group_misfit_task_load = rq->misfit_task_load; + } + } else if (env->idle && sched_reduced_capacity(rq, env->sd)) { + /* Check for a task running on a CPU with reduced capacity */ +@@ -11984,6 +11999,17 @@ static bool update_sd_pick_busiest(struct lb_env *env, + sds->local_stat.group_type != group_has_spare)) + return false; + ++ /* ++ * Candidate sg has no more than one task per CPU and has higher ++ * per-CPU capacity. Migrating tasks to less capable CPUs may harm ++ * throughput. Maximize throughput, power/energy consequences are not ++ * considered. ++ */ ++ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && ++ (sgs->group_type <= group_fully_busy) && ++ (capacity_greater(sg->sgc->min_capacity, capacity_of(env->dst_cpu)))) ++ return false; ++ + if (sgs->group_type > busiest->group_type) + return true; + +@@ -12090,17 +12116,6 @@ static bool update_sd_pick_busiest(struct lb_env *env, + break; + } + +- /* +- * Candidate sg has no more than one task per CPU and has higher +- * per-CPU capacity. Migrating tasks to less capable CPUs may harm +- * throughput. Maximize throughput, power/energy consequences are not +- * considered. +- */ +- if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && +- (sgs->group_type <= group_fully_busy) && +- (capacity_greater(sg->sgc->min_capacity, capacity_of(env->dst_cpu)))) +- return false; +- + return true; + } + +@@ -13018,9 +13033,24 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env, + * average load. + */ + if (env->sd->flags & SD_ASYM_CPUCAPACITY && +- !capacity_greater(capacity_of(env->dst_cpu), capacity) && +- nr_running == 1) +- continue; ++ nr_running == 1) { ++ bool cluster_equal_cap = static_branch_unlikely(&sched_cluster_active) && ++ (get_actual_cpu_capacity(env->dst_cpu) == ++ get_actual_cpu_capacity(i)); ++ bool smt_degraded_cap = sched_smt_active() && !is_core_idle(i); ++ ++ /* ++ * Busy SMT siblings reduce the capacity of CPU @i. Do ++ * not skip it in this case. ++ * ++ * CONFIG_SCHED_CLUSTER requires balancing load across ++ * clusters of identical capacity, accounting for ++ * hardware and cpufreq pressure. ++ */ ++ if (!smt_degraded_cap && !cluster_equal_cap && ++ !capacity_greater(capacity_of(env->dst_cpu), capacity)) ++ continue; ++ } + + /* + * Make sure we only pull tasks from a CPU of lower priority +diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c +--- a/kernel/sched/topology.c ++++ b/kernel/sched/topology.c +@@ -1995,10 +1995,6 @@ sd_init(struct sched_domain_topology_level *tl, + /* + * Convert topological properties into behaviour. + */ +- /* Don't attempt to spread across CPUs of different capacities. */ +- if ((sd->flags & SD_ASYM_CPUCAPACITY) && sd->child) +- sd->child->flags &= ~SD_PREFER_SIBLING; +- + if (sd->flags & SD_SHARE_CPUCAPACITY) { + sd->imbalance_pct = 110; + diff --git a/pkgbuilds/linux-omarchy/0144-sched-nohz-idle-core.patch b/pkgbuilds/linux-omarchy/0144-sched-nohz-idle-core.patch new file mode 100644 index 0000000..1fe001c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0144-sched-nohz-idle-core.patch @@ -0,0 +1,77 @@ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -13924,29 +13924,62 @@ static inline int on_null_domain(struct rq *rq) + */ + static inline int find_new_ilb(void) + { +- int this_cpu = smp_processor_id(); +- const struct cpumask *hk_mask; +- int ilb_cpu; ++ struct cpumask *ilb_cpus; ++ int ilb_cpu, fallback = -1; + +- hk_mask = housekeeping_cpumask(HK_TYPE_KERNEL_NOISE); ++ lockdep_assert_irqs_disabled(); + +- for_each_cpu_and(ilb_cpu, nohz.idle_cpus_mask, hk_mask) { +- if (ilb_cpu == this_cpu) ++ /* ++ * Reuse the per-CPU select_rq_mask, which is protected from concurrent ++ * use on this CPU by having interrupts disabled. ++ */ ++ ilb_cpus = this_cpu_cpumask_var_ptr(select_rq_mask); ++ cpumask_and(ilb_cpus, nohz.idle_cpus_mask, ++ housekeeping_cpumask(HK_TYPE_KERNEL_NOISE)); ++ ++ for_each_cpu(ilb_cpu, ilb_cpus) { ++ if (!idle_cpu(ilb_cpu)) { ++ /* ++ * Once an idle fallback exists, a busy CPU proves that ++ * this core cannot be fully idle. Skip its siblings. ++ */ ++ if (sched_smt_active() && fallback >= 0) ++ cpumask_andnot(ilb_cpus, ilb_cpus, cpu_smt_mask(ilb_cpu)); + continue; ++ } + +- if (idle_cpu(ilb_cpu)) +- return ilb_cpu; ++ /* ++ * Running the idle load balancer on an idle sibling of a busy ++ * SMT core can reduce the capacity available to its sibling. Prefer ++ * a CPU whose entire core is idle, but retain the first idle CPU as ++ * a fallback so idle balancing can still make progress when no fully ++ * idle core exists. ++ */ ++ if (sched_smt_active() && !is_core_idle(ilb_cpu)) { ++ if (fallback < 0) ++ fallback = ilb_cpu; ++ ++ /* ++ * The core is not idle, so there is no need to check ++ * any of its other SMT siblings. ++ */ ++ cpumask_andnot(ilb_cpus, ilb_cpus, ++ cpu_smt_mask(ilb_cpu)); ++ continue; ++ } ++ ++ return ilb_cpu; + } + +- return -1; ++ return fallback; + } + + /* + * Kick a CPU to do the NOHZ balancing, if it is time for it, via a cross-CPU + * SMP function call (IPI). + * +- * We pick the first idle CPU in the HK_TYPE_KERNEL_NOISE housekeeping set +- * (if there is one). ++ * Prefer a CPU on a fully idle core in the HK_TYPE_KERNEL_NOISE housekeeping ++ * set. Fall back to the first idle CPU when no fully idle core exists. + */ + static void kick_ilb(unsigned int flags) + { diff --git a/pkgbuilds/linux-omarchy/0145-sched-eevdf-tunables.patch b/pkgbuilds/linux-omarchy/0145-sched-eevdf-tunables.patch new file mode 100644 index 0000000..f949578 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0145-sched-eevdf-tunables.patch @@ -0,0 +1,45 @@ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -67,19 +67,19 @@ + * SCHED_TUNABLESCALING_LOG - scaled logarithmically, *1+ilog(ncpus) + * SCHED_TUNABLESCALING_LINEAR - scaled linear, *ncpus + * +- * (default SCHED_TUNABLESCALING_LOG = *(1+ilog(ncpus)) ++ * (default SCHED_TUNABLESCALING_NONE = *1 constant) + */ +-unsigned int sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_LOG; ++unsigned int sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_NONE; + + /* + * Minimal preemption granularity for CPU-bound tasks: + * +- * (default: 0.70 msec * (1 + ilog(ncpus)), units: nanoseconds) ++ * (default: 1.6 msec constant, units: nanoseconds) + */ +-unsigned int sysctl_sched_base_slice = 700000ULL; +-static unsigned int normalized_sysctl_sched_base_slice = 700000ULL; ++unsigned int sysctl_sched_base_slice = 1600000ULL; ++static unsigned int normalized_sysctl_sched_base_slice = 1600000ULL; + +-__read_mostly unsigned int sysctl_sched_migration_cost = 500000UL; ++__read_mostly unsigned int sysctl_sched_migration_cost = 400000UL; + + static int __init setup_sched_thermal_decay_shift(char *str) + { +diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h +--- a/kernel/sched/sched.h ++++ b/kernel/sched/sched.h +@@ -3103,11 +3103,7 @@ + attach_task(rq, p); + } + +-#ifdef CONFIG_PREEMPT_RT +-# define SCHED_NR_MIGRATE_BREAK 8 +-#else +-# define SCHED_NR_MIGRATE_BREAK 32 +-#endif ++#define SCHED_NR_MIGRATE_BREAK 8 + + extern __read_mostly unsigned int sysctl_sched_nr_migrate; + extern __read_mostly unsigned int sysctl_sched_migration_cost; diff --git a/pkgbuilds/linux-omarchy/0200-idle.patch b/pkgbuilds/linux-omarchy/0200-idle.patch new file mode 100644 index 0000000..dc499e5 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0200-idle.patch @@ -0,0 +1,61 @@ +diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c +--- a/drivers/idle/intel_idle.c ++++ b/drivers/idle/intel_idle.c +@@ -53,6 +53,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2697,6 +2698,9 @@ static void __init cmdline_table_adjust(struct cpuidle_driver *drv) + pr_info("Failed to adjust C-states with data from 'intel_idle.table'\n"); + } + ++#define INTEL_IDLE_INIT_QOS 20 ++static struct pm_qos_request qos_req __initdata; ++ + static int __init intel_idle_init(void) + { + const struct x86_cpu_id *id; +@@ -2766,6 +2770,13 @@ static int __init intel_idle_init(void) + if (retval) + pr_warn("failed to initialized sysfs"); + ++ /* ++ * Some platforms, in particular the Intel S1200BTL motherboard, have a ++ * problem with using package idle states too early, so prevent that ++ * from taking place until the device_initcall() phase is over. ++ */ ++ cpu_latency_qos_add_request(&qos_req, INTEL_IDLE_INIT_QOS); ++ + retval = cpuidle_register_driver(&intel_idle_driver); + if (retval) { + struct cpuidle_driver *drv = cpuidle_get_driver(); +@@ -2790,6 +2801,9 @@ static int __init intel_idle_init(void) + intel_idle_cpuidle_devices_uninit(); + cpuidle_unregister_driver(&intel_idle_driver); + init_driver_fail: ++ if (cpu_latency_qos_request_active((&qos_req))) ++ cpu_latency_qos_remove_request(&qos_req); ++ + intel_idle_sysfs_uninit(); + free_percpu(intel_idle_cpuidle_devices); + return retval; +@@ -2797,6 +2811,15 @@ static int __init intel_idle_init(void) + } + subsys_initcall_sync(intel_idle_init); + ++static int __init intel_idle_init_complete(void) ++{ ++ if (cpu_latency_qos_request_active((&qos_req))) ++ cpu_latency_qos_remove_request(&qos_req); ++ ++ return 0; ++} ++device_initcall_sync(intel_idle_init_complete); ++ + /* + * We are not really modular, but we used to support that. Meaning we also + * support "intel_idle.max_cstate=..." at boot and also a read-only export of diff --git a/pkgbuilds/linux-omarchy/0210-pstate.patch b/pkgbuilds/linux-omarchy/0210-pstate.patch new file mode 100644 index 0000000..3d4eefc --- /dev/null +++ b/pkgbuilds/linux-omarchy/0210-pstate.patch @@ -0,0 +1,480 @@ +diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c +--- a/drivers/cpufreq/amd-pstate.c ++++ b/drivers/cpufreq/amd-pstate.c +@@ -782,6 +782,7 @@ static unsigned int amd_pstate_fast_switch(struct cpufreq_policy *policy, + static void amd_pstate_adjust_perf(struct cpufreq_policy *policy, + unsigned long _min_perf, + unsigned long target_perf, ++ unsigned long _max_perf, + unsigned long capacity) + { + u8 max_perf, min_perf, des_perf, cap_perf; +diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c +--- a/drivers/cpufreq/cpufreq.c ++++ b/drivers/cpufreq/cpufreq.c +@@ -2225,14 +2225,17 @@ EXPORT_SYMBOL_GPL(cpufreq_driver_fast_switch); + * @policy: cpufreq policy object of the target CPU. + * @min_perf: Minimum (required) performance level (units of @capacity). + * @target_perf: Target (desired) performance level (units of @capacity). ++ * @max_perf: Maximum (allowed) performance level (units of @capacity). + * @capacity: Capacity of the target CPU. + * +- * Carry out a fast performance level switch of @cpu without sleeping. ++ * Carry out a fast performance level adjustment for the CPU represented by ++ * @policy without sleeping. + * + * The driver's ->adjust_perf() callback invoked by this function must be +- * suitable for being called from within RCU-sched read-side critical sections +- * and it is expected to select a suitable performance level equal to or above +- * @min_perf and preferably equal to or below @target_perf. ++ * suitable for calling from within RCU-sched read-side critical sections and ++ * it is expected to program the processor to select suitable performance ++ * levels between @min_perf and @max_perf inclusive and preferably close to ++ * @target_perf going forward for the CPU represented by @policy. + * + * This function must not be called if policy->fast_switch_enabled is unset. + * +@@ -2244,9 +2247,10 @@ EXPORT_SYMBOL_GPL(cpufreq_driver_fast_switch); + void cpufreq_driver_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity) + { +- cpufreq_driver->adjust_perf(policy, min_perf, target_perf, capacity); ++ cpufreq_driver->adjust_perf(policy, min_perf, target_perf, max_perf, capacity); + } + + /** +diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c +--- a/drivers/cpufreq/intel_pstate.c ++++ b/drivers/cpufreq/intel_pstate.c +@@ -299,7 +299,6 @@ struct pstate_funcs { + static struct pstate_funcs pstate_funcs __read_mostly; + + static bool hwp_active __ro_after_init; +-static int hwp_mode_bdw __ro_after_init; + static bool per_cpu_limits __ro_after_init; + static bool hwp_forced __ro_after_init; + static bool hwp_boost __read_mostly; +@@ -587,21 +586,14 @@ static void intel_pstate_hybrid_hwp_adjust(struct cpudata *cpu) + + hwp_is_hybrid = true; + +- cpu->pstate.turbo_freq = rounddown(cpu->pstate.turbo_pstate * scaling, +- perf_ctl_scaling); +- cpu->pstate.max_freq = rounddown(cpu->pstate.max_pstate * scaling, +- perf_ctl_scaling); +- + freq = perf_ctl_max_phys * perf_ctl_scaling; + cpu->pstate.max_pstate_physical = intel_pstate_freq_to_hwp(cpu, freq); + +- freq = cpu->pstate.min_pstate * perf_ctl_scaling; +- cpu->pstate.min_freq = freq; + /* + * Cast the min P-state value retrieved via pstate_funcs.get_min() to + * the effective range of HWP performance levels. + */ +- cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, freq); ++ cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, cpu->pstate.min_freq); + } + + static bool turbo_is_disabled(void) +@@ -979,12 +971,10 @@ static int hybrid_get_cost(struct device *dev, unsigned long freq, + * capacity. Similarly, P-cores start to be populated when E-cores are + * utilized above 60% of the capacity. + */ +- if (hybrid_get_cpu_type(dev->id) == INTEL_CPU_TYPE_ATOM) { +- if (hybrid_has_l3(dev->id)) /* E-core */ +- *cost += 1; +- } else { /* P-core */ ++ if (hybrid_get_cpu_type(dev->id) == INTEL_CPU_TYPE_CORE) /* P-core */ + *cost += 2; +- } ++ else if (hybrid_has_l3(dev->id)) /* E-core */ ++ *cost += 1; + + return 0; + } +@@ -1185,6 +1175,22 @@ static bool hybrid_clear_max_perf_cpu(void) + return ret; + } + ++static void intel_pstate_update_freq_limits(struct cpudata *cpu) ++{ ++ int scaling = cpu->pstate.scaling; ++ unsigned int turbo_freq = cpu->pstate.turbo_pstate * scaling; ++ unsigned int max_freq = cpu->pstate.max_pstate * scaling; ++ int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; ++ ++ if (scaling != perf_ctl_scaling) { ++ turbo_freq = rounddown(turbo_freq, perf_ctl_scaling); ++ max_freq = rounddown(max_freq, perf_ctl_scaling); ++ } ++ ++ cpu->pstate.turbo_freq = turbo_freq; ++ cpu->pstate.max_freq = max_freq; ++} ++ + static void __intel_pstate_get_hwp_cap(struct cpudata *cpu) + { + u64 cap; +@@ -1197,20 +1203,8 @@ static void __intel_pstate_get_hwp_cap(struct cpudata *cpu) + + static void intel_pstate_get_hwp_cap(struct cpudata *cpu) + { +- int scaling = cpu->pstate.scaling; +- + __intel_pstate_get_hwp_cap(cpu); +- +- cpu->pstate.max_freq = cpu->pstate.max_pstate * scaling; +- cpu->pstate.turbo_freq = cpu->pstate.turbo_pstate * scaling; +- if (scaling != cpu->pstate.perf_ctl_scaling) { +- int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; +- +- cpu->pstate.max_freq = rounddown(cpu->pstate.max_freq, +- perf_ctl_scaling); +- cpu->pstate.turbo_freq = rounddown(cpu->pstate.turbo_freq, +- perf_ctl_scaling); +- } ++ intel_pstate_update_freq_limits(cpu); + } + + static void hybrid_update_capacity(struct cpudata *cpu) +@@ -2299,33 +2293,16 @@ static int hwp_get_cpu_scaling(int cpu) + return intel_pstate_cppc_get_scaling(cpu); + } + +-static void intel_pstate_set_pstate(struct cpudata *cpu, int pstate) +-{ +- trace_cpu_frequency(pstate * cpu->pstate.scaling, cpu->cpu); +- cpu->pstate.current_pstate = pstate; +- /* +- * Generally, there is no guarantee that this code will always run on +- * the CPU being updated, so force the register update to run on the +- * right CPU. +- */ +- wrmsrq_on_cpu(cpu->cpu, MSR_IA32_PERF_CTL, +- pstate_funcs.get_val(cpu, pstate)); +-} +- +-static void intel_pstate_set_min_pstate(struct cpudata *cpu) +-{ +- intel_pstate_set_pstate(cpu, cpu->pstate.min_pstate); +-} +- + static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) + { + int perf_ctl_scaling = pstate_funcs.get_scaling(); + + cpu->pstate.max_pstate_physical = pstate_funcs.get_max_physical(cpu->cpu); + cpu->pstate.min_pstate = pstate_funcs.get_min(cpu->cpu); ++ cpu->pstate.min_freq = cpu->pstate.min_pstate * perf_ctl_scaling; + cpu->pstate.perf_ctl_scaling = perf_ctl_scaling; + +- if (hwp_active && !hwp_mode_bdw) { ++ if (hwp_active) { + __intel_pstate_get_hwp_cap(cpu); + + if (pstate_funcs.get_cpu_scaling) { +@@ -2345,19 +2322,13 @@ static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) + cpu->pstate.turbo_pstate = pstate_funcs.get_turbo(cpu->cpu); + } + +- if (cpu->pstate.scaling == perf_ctl_scaling) { +- cpu->pstate.min_freq = cpu->pstate.min_pstate * perf_ctl_scaling; +- cpu->pstate.max_freq = cpu->pstate.max_pstate * perf_ctl_scaling; +- cpu->pstate.turbo_freq = cpu->pstate.turbo_pstate * perf_ctl_scaling; +- } ++ intel_pstate_update_freq_limits(cpu); + + if (pstate_funcs.get_aperf_mperf_shift) + cpu->aperf_mperf_shift = pstate_funcs.get_aperf_mperf_shift(); + + if (pstate_funcs.get_vid) + pstate_funcs.get_vid(cpu); +- +- intel_pstate_set_min_pstate(cpu); + } + + /* +@@ -2884,8 +2855,22 @@ static void intel_pstate_update_perf_limits(struct cpudata *cpu, + cpu->min_perf_ratio); + } + ++static void intel_pstate_set_pstate(struct cpudata *cpu, int pstate) ++{ ++ trace_cpu_frequency(pstate * cpu->pstate.scaling, cpu->cpu); ++ cpu->pstate.current_pstate = pstate; ++ /* ++ * Generally, there is no guarantee that this code will always run on ++ * the CPU being updated, so force the register update to run on the ++ * right CPU. ++ */ ++ wrmsrq_on_cpu(cpu->cpu, MSR_IA32_PERF_CTL, ++ pstate_funcs.get_val(cpu, pstate)); ++} ++ + static int intel_pstate_set_policy(struct cpufreq_policy *policy) + { ++ unsigned int freq = policy->min; + struct cpudata *cpu; + + if (!policy->cpuinfo.max_freq) +@@ -2901,7 +2886,23 @@ static int intel_pstate_set_policy(struct cpufreq_policy *policy) + + intel_pstate_update_perf_limits(cpu, policy->min, policy->max); + +- if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { ++ if (hwp_active) { ++ /* ++ * The active mode only requires an update util hook if HWP ++ * boost is used and the policy is not "performance". ++ */ ++ if (hwp_boost && cpu->policy != CPUFREQ_POLICY_PERFORMANCE) { ++ intel_pstate_set_update_util_hook(policy->cpu); ++ } else { ++ intel_pstate_clear_update_util_hook(policy->cpu); ++ if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { ++ freq = cpu->max_perf_ratio * cpu->pstate.scaling; ++ if (cpu->pstate.scaling != cpu->pstate.perf_ctl_scaling) ++ freq = rounddown(freq, cpu->pstate.perf_ctl_scaling); ++ } ++ } ++ intel_pstate_hwp_set(policy->cpu); ++ } else if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { + int pstate = max(cpu->pstate.min_pstate, cpu->max_perf_ratio); + + /* +@@ -2910,25 +2911,17 @@ static int intel_pstate_set_policy(struct cpufreq_policy *policy) + */ + intel_pstate_clear_update_util_hook(policy->cpu); + intel_pstate_set_pstate(cpu, pstate); ++ freq = pstate * cpu->pstate.scaling; + } else { + intel_pstate_set_update_util_hook(policy->cpu); + } +- +- if (hwp_active) { +- /* +- * When hwp_boost was active before and dynamically it +- * was turned off, in that case we need to clear the +- * update util hook. +- */ +- if (!hwp_boost) +- intel_pstate_clear_update_util_hook(policy->cpu); +- intel_pstate_hwp_set(policy->cpu); +- } + /* +- * policy->cur is never updated with the intel_pstate driver, but it +- * is used as a stale frequency value. So, keep it within limits. ++ * policy->cur is never updated in the intel_pstate driver, but it is ++ * used as a stale frequency value, so set it to reflect the actual ++ * requested P-state in the "performance" policy case and to the min ++ * otherwise. + */ +- policy->cur = policy->min; ++ policy->cur = freq; + + mutex_unlock(&intel_pstate_limits_lock); + +@@ -2971,6 +2964,11 @@ static int intel_pstate_verify_policy(struct cpufreq_policy_data *policy) + return 0; + } + ++static void intel_pstate_set_min_pstate(struct cpudata *cpu) ++{ ++ intel_pstate_set_pstate(cpu, cpu->pstate.min_pstate); ++} ++ + static int intel_cpufreq_cpu_offline(struct cpufreq_policy *policy) + { + struct cpudata *cpu = all_cpu_data[policy->cpu]; +@@ -3063,6 +3061,7 @@ static int __intel_pstate_cpu_init(struct cpufreq_policy *policy) + static int intel_pstate_cpu_init(struct cpufreq_policy *policy) + { + int ret = __intel_pstate_cpu_init(policy); ++ struct cpudata *cpu; + + if (ret) + return ret; +@@ -3073,11 +3072,11 @@ static int intel_pstate_cpu_init(struct cpufreq_policy *policy) + */ + policy->policy = CPUFREQ_POLICY_POWERSAVE; + +- if (hwp_active) { +- struct cpudata *cpu = all_cpu_data[policy->cpu]; +- ++ cpu = all_cpu_data[policy->cpu]; ++ if (hwp_active) + cpu->epp_cached = intel_pstate_get_epp(cpu, 0); +- } ++ else ++ intel_pstate_set_min_pstate(cpu); + + return 0; + } +@@ -3243,6 +3242,7 @@ static unsigned int intel_cpufreq_fast_switch(struct cpufreq_policy *policy, + static void intel_cpufreq_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity) + { + struct cpudata *cpu = all_cpu_data[policy->cpu]; +@@ -3273,7 +3273,13 @@ static void intel_cpufreq_adjust_perf(struct cpufreq_policy *policy, + if (min_pstate > cpu->max_perf_ratio) + min_pstate = cpu->max_perf_ratio; + +- max_pstate = min(cap_pstate, cpu->max_perf_ratio); ++ max_pstate = cap_pstate; ++ if (max_perf < capacity) ++ max_pstate = DIV_ROUND_UP(cap_pstate * max_perf, capacity); ++ ++ if (max_pstate > cpu->max_perf_ratio) ++ max_pstate = cpu->max_perf_ratio; ++ + if (max_pstate < min_pstate) + max_pstate = min_pstate; + +@@ -3301,8 +3307,6 @@ static int intel_cpufreq_cpu_init(struct cpufreq_policy *policy) + return ret; + + policy->cpuinfo.transition_latency = INTEL_CPUFREQ_TRANSITION_LATENCY; +- /* This reflects the intel_pstate_get_cpu_pstates() setting. */ +- policy->cur = policy->cpuinfo.min_freq; + + req = kzalloc_objs(*req, 2); + if (!req) { +@@ -3323,9 +3327,15 @@ static int intel_cpufreq_cpu_init(struct cpufreq_policy *policy) + WRITE_ONCE(cpu->hwp_req_cached, value); + + cpu->epp_cached = intel_pstate_get_epp(cpu, value); ++ ++ intel_cpufreq_hwp_update(cpu, cpu->pstate.min_pstate, ++ cpu->pstate.max_pstate, ++ cpu->pstate.min_pstate, false); + } else { + policy->transition_delay_us = INTEL_CPUFREQ_TRANSITION_DELAY; ++ intel_pstate_set_min_pstate(cpu); + } ++ policy->cur = policy->cpuinfo.min_freq; + + freq = DIV_ROUND_UP(cpu->pstate.turbo_freq * global.min_perf_pct, 100); + +@@ -3676,14 +3686,14 @@ static inline bool intel_pstate_has_acpi_ppc(void) { return false; } + static inline void intel_pstate_request_control_from_smm(void) {} + #endif /* CONFIG_ACPI */ + +-#define INTEL_PSTATE_HWP_BROADWELL 0x01 ++#define INTEL_PSTATE_HWP_NOT_HYBRID 0x01 + + #define X86_MATCH_HWP(vfm, hwp_mode) \ + X86_MATCH_VFM_FEATURE(vfm, X86_FEATURE_HWP, hwp_mode) + + static const struct x86_cpu_id hwp_support_ids[] __initconst = { +- X86_MATCH_HWP(INTEL_BROADWELL_X, INTEL_PSTATE_HWP_BROADWELL), +- X86_MATCH_HWP(INTEL_BROADWELL_D, INTEL_PSTATE_HWP_BROADWELL), ++ X86_MATCH_HWP(INTEL_BROADWELL_X, INTEL_PSTATE_HWP_NOT_HYBRID), ++ X86_MATCH_HWP(INTEL_BROADWELL_D, INTEL_PSTATE_HWP_NOT_HYBRID), + X86_MATCH_HWP(INTEL_ANY, 0), + {} + }; +@@ -3808,7 +3818,6 @@ static int __init intel_pstate_init(void) + + if (!no_hwp) { + hwp_active = true; +- hwp_mode_bdw = id->driver_data; + intel_pstate.attr = hwp_cpufreq_attrs; + intel_cpufreq.attr = hwp_cpufreq_attrs; + intel_cpufreq.flags |= CPUFREQ_NEED_UPDATE_LIMITS; +@@ -3816,7 +3825,8 @@ static int __init intel_pstate_init(void) + if (!default_driver) + default_driver = &intel_pstate; + +- pstate_funcs.get_cpu_scaling = hwp_get_cpu_scaling; ++ if (!id->driver_data) ++ pstate_funcs.get_cpu_scaling = hwp_get_cpu_scaling; + + goto hwp_cpu_matched; + } +diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h +--- a/include/linux/cpufreq.h ++++ b/include/linux/cpufreq.h +@@ -379,6 +379,7 @@ struct cpufreq_driver { + void (*adjust_perf)(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity); + + /* +@@ -624,6 +625,7 @@ unsigned int cpufreq_driver_fast_switch(struct cpufreq_policy *policy, + void cpufreq_driver_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity); + bool cpufreq_driver_has_adjust_perf(void); + int cpufreq_driver_target(struct cpufreq_policy *policy, +diff --git a/kernel/sched/cpufreq_schedutil.c b/kernel/sched/cpufreq_schedutil.c +--- a/kernel/sched/cpufreq_schedutil.c ++++ b/kernel/sched/cpufreq_schedutil.c +@@ -50,6 +50,7 @@ struct sugov_cpu { + + unsigned long util; + unsigned long bw_min; ++ unsigned long bw_max; + + /* The field below is for single-CPU policies only: */ + #ifdef CONFIG_NO_HZ_COMMON +@@ -243,6 +244,7 @@ static void sugov_get_util(struct sugov_cpu *sg_cpu, unsigned long boost) + util = effective_cpu_util(sg_cpu->cpu, util, &min, &max); + util = max(util, boost); + sg_cpu->bw_min = min; ++ sg_cpu->bw_max = max; + sg_cpu->util = sugov_effective_cpu_perf(sg_cpu->cpu, util, min, max); + } + +@@ -495,7 +497,7 @@ static void sugov_update_single_perf(struct update_util_data *hook, u64 time, + sg_cpu->util = prev_util; + + cpufreq_driver_adjust_perf(sg_policy->policy, sg_cpu->bw_min, +- sg_cpu->util, max_cap); ++ sg_cpu->util, sg_cpu->bw_max, max_cap); + + sg_policy->need_freq_update = false; + sg_policy->last_freq_update_time = time; +diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs +--- a/rust/kernel/cpufreq.rs ++++ b/rust/kernel/cpufreq.rs +@@ -792,7 +792,13 @@ fn fast_switch(_policy: &mut Policy, _target_freq: u32) -> u32 { + } + + /// Driver's `adjust_perf` callback. +- fn adjust_perf(_policy: &mut Policy, _min_perf: usize, _target_perf: usize, _capacity: usize) { ++ fn adjust_perf( ++ _policy: &mut Policy, ++ _min_perf: usize, ++ _target_perf: usize, ++ _max_perf: usize, ++ _capacity: usize, ++ ) { + build_error!(VTABLE_DEFAULT_ERROR) + } + +@@ -1263,12 +1269,13 @@ impl Registration { + ptr: *mut bindings::cpufreq_policy, + min_perf: c_ulong, + target_perf: c_ulong, ++ max_perf: c_ulong, + capacity: c_ulong, + ) { + // SAFETY: The `ptr` is guaranteed to be valid by the contract with the C code for the + // lifetime of `policy`. + let policy = unsafe { Policy::from_raw_mut(ptr) }; +- T::adjust_perf(policy, min_perf, target_perf, capacity); ++ T::adjust_perf(policy, min_perf, target_perf, max_perf, capacity); + } + + /// Driver's `get_intermediate` callback. diff --git a/pkgbuilds/linux-omarchy/0211-amd-pstate-fixes.patch b/pkgbuilds/linux-omarchy/0211-amd-pstate-fixes.patch new file mode 100644 index 0000000..67a6cf4 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0211-amd-pstate-fixes.patch @@ -0,0 +1,127 @@ +diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c +--- a/drivers/cpufreq/amd-pstate-ut.c ++++ b/drivers/cpufreq/amd-pstate-ut.c +@@ -560,6 +560,11 @@ static int amd_pstate_ut_check_freq_attrs(u32 index) + static int __init amd_pstate_ut_init(void) + { + u32 i = 0, arr_size = ARRAY_SIZE(amd_pstate_ut_cases); ++ enum amd_pstate_mode mode = amd_pstate_get_status(); ++ ++ /* don't test if no running amd-pstate driver */ ++ if (mode == AMD_PSTATE_UNDEFINED || mode == AMD_PSTATE_DISABLE) ++ return -EOPNOTSUPP; + + for (i = 0; i < arr_size; i++) { + int ret; +diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c +--- a/drivers/cpufreq/amd-pstate.c ++++ b/drivers/cpufreq/amd-pstate.c +@@ -199,7 +199,7 @@ static inline int get_mode_idx_from_str(const char *str, size_t size) + + static DEFINE_MUTEX(amd_pstate_driver_lock); + +-static u8 msr_get_epp(struct amd_cpudata *cpudata) ++static int msr_get_epp(struct amd_cpudata *cpudata) + { + u64 value; + int ret; +@@ -215,12 +215,12 @@ static u8 msr_get_epp(struct amd_cpudata *cpudata) + + DEFINE_STATIC_CALL(amd_pstate_get_epp, msr_get_epp); + +-static inline s16 amd_pstate_get_epp(struct amd_cpudata *cpudata) ++static inline int amd_pstate_get_epp(struct amd_cpudata *cpudata) + { + return static_call(amd_pstate_get_epp)(cpudata); + } + +-static u8 shmem_get_epp(struct amd_cpudata *cpudata) ++static int shmem_get_epp(struct amd_cpudata *cpudata) + { + u64 epp; + int ret; +@@ -526,9 +526,6 @@ static int shmem_init_perf(struct amd_cpudata *cpudata) + WRITE_ONCE(cpudata->perf, perf); + WRITE_ONCE(cpudata->prefcore_ranking, cppc_perf.highest_perf); + +- if (cppc_state == AMD_PSTATE_ACTIVE) +- return 0; +- + ret = cppc_get_auto_sel(cpudata->cpu, &auto_sel); + if (ret) { + pr_warn("failed to get auto_sel, ret: %d\n", ret); +@@ -1174,6 +1171,9 @@ static int amd_pstate_power_supply_notifier(struct notifier_block *nb, + if (cpudata->current_profile != PLATFORM_PROFILE_BALANCED) + return 0; + ++ if (!policy) ++ return NOTIFY_OK; ++ + epp = amd_pstate_get_balanced_epp(policy); + + ret = amd_pstate_set_epp(policy, epp); +@@ -1209,6 +1209,9 @@ static int amd_pstate_profile_set(struct device *dev, + struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpudata->cpu); + int ret; + ++ if (!policy) ++ return -ENODEV; ++ + switch (profile) { + case PLATFORM_PROFILE_LOW_POWER: + ret = amd_pstate_set_epp(policy, AMD_CPPC_EPP_POWERSAVE); +@@ -1877,6 +1880,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + struct amd_cpudata *cpudata; + union perf_cached perf; + struct device *dev; ++ int default_epp; + int ret; + + /* +@@ -1925,6 +1929,13 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + + policy->boost_supported = READ_ONCE(cpudata->boost_supported); + ++ /* Fetch the firmware programmed default EPP value */ ++ default_epp = amd_pstate_get_epp(cpudata); ++ if (default_epp < 0) { ++ ret = default_epp; ++ goto free_cpudata1; ++ } ++ + /* + * Set the policy to provide a valid fallback value in case + * the default cpufreq governor is neither powersave nor performance. +@@ -1932,7 +1943,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + if (amd_pstate_acpi_pm_profile_server() || + amd_pstate_acpi_pm_profile_undefined()) { + policy->policy = CPUFREQ_POLICY_PERFORMANCE; +- cpudata->epp_default_ac = cpudata->epp_default_dc = amd_pstate_get_epp(cpudata); ++ cpudata->epp_default_ac = cpudata->epp_default_dc = default_epp; + cpudata->current_profile = PLATFORM_PROFILE_PERFORMANCE; + } else { + policy->policy = CPUFREQ_POLICY_POWERSAVE; +diff --git a/drivers/cpufreq/amd-pstate.h b/drivers/cpufreq/amd-pstate.h +--- a/drivers/cpufreq/amd-pstate.h ++++ b/drivers/cpufreq/amd-pstate.h +@@ -32,6 +32,7 @@ + * @min_limit_perf: Cached value of the performance corresponding to policy->min + * @max_limit_perf: Cached value of the performance corresponding to policy->max + * @bios_min_perf: Cached perf value corresponding to the "Requested CPU Min Frequency" BIOS option ++ * @val: Raw 64-bit value for atomic access via READ_ONCE()/WRITE_ONCE() + */ + union perf_cached { + struct { +@@ -89,7 +90,12 @@ struct amd_aperf_mperf { + * @epp_default_ac: Default EPP value for AC power source + * @epp_default_dc: Default EPP value for DC power source + * @dynamic_epp: Whether dynamic EPP is enabled ++ * @raw_epp: Whether the last EPP write was a raw numeric value rather than a ++ * named preference + * @power_nb: Notifier block for power events ++ * @current_profile: Currently selected platform profile option ++ * @ppdev: Device registered with the platform profile handler ++ * @profile_name: Name under which @ppdev is registered + * + * The amd_cpudata is key private data for each CPU thread in AMD P-State, and + * represents all the attributes and goals that AMD P-State requests at runtime. diff --git a/pkgbuilds/linux-omarchy/0212-amd-pstate-epp-cache.patch b/pkgbuilds/linux-omarchy/0212-amd-pstate-epp-cache.patch new file mode 100644 index 0000000..61ea14c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0212-amd-pstate-epp-cache.patch @@ -0,0 +1,18 @@ +diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c +--- a/drivers/cpufreq/amd-pstate.c ++++ b/drivers/cpufreq/amd-pstate.c +@@ -1929,12 +1929,13 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + + policy->boost_supported = READ_ONCE(cpudata->boost_supported); + +- /* Fetch the firmware programmed default EPP value */ ++ /* Cache the firmware programmed EPP */ + default_epp = amd_pstate_get_epp(cpudata); + if (default_epp < 0) { + ret = default_epp; + goto free_cpudata1; + } ++ FIELD_MODIFY(AMD_CPPC_EPP_PERF_MASK, &cpudata->cppc_req_cached, default_epp); + + /* + * Set the policy to provide a valid fallback value in case diff --git a/pkgbuilds/linux-omarchy/0250-zsmalloc.patch b/pkgbuilds/linux-omarchy/0250-zsmalloc.patch new file mode 100644 index 0000000..43793e5 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0250-zsmalloc.patch @@ -0,0 +1,396 @@ +diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c +--- a/mm/zsmalloc.c ++++ b/mm/zsmalloc.c +@@ -21,6 +21,10 @@ + * pool->lock + * class->lock + * zspage->lock ++ * ++ * When ZS_OBJ_CLASS_BITS > 0, zs_free() skips pool->lock; it picks ++ * the size_class from obj's encoded class_idx and serializes against ++ * page migration via class->lock. + */ + + #include +@@ -67,8 +71,8 @@ + #define MAX_POSSIBLE_PHYSMEM_BITS MAX_PHYSMEM_BITS + #else + /* +- * If this definition of MAX_PHYSMEM_BITS is used, OBJ_INDEX_BITS will just +- * be PAGE_SHIFT ++ * If this definition of MAX_PHYSMEM_BITS is used, ZS_OBJ_PFN_SHIFT will ++ * just be PAGE_SHIFT + */ + #define MAX_POSSIBLE_PHYSMEM_BITS BITS_PER_LONG + #endif +@@ -88,8 +92,23 @@ + #define OBJ_TAG_BITS 1 + #define OBJ_TAG_MASK OBJ_ALLOCATED_TAG + +-#define OBJ_INDEX_BITS (BITS_PER_LONG - _PFN_BITS) +-#define OBJ_INDEX_MASK ((_AC(1, UL) << OBJ_INDEX_BITS) - 1) ++/* ++ * obj is encoded as [PFN | class_idx | obj_idx] within an unsigned long: ++ * ++ * |<-- _PFN_BITS -->|<-- ZS_OBJ_CLASS_BITS -->|<-- ZS_OBJ_IDX_BITS -->| ++ * +-----------------+-------------------------+-----------------------+ ++ * | PFN | class_idx | obj_idx | ++ * +-----------------+-------------------------+-----------------------+ ++ * MSB ^ LSB ++ * | ++ * +-- ZS_OBJ_PFN_SHIFT ++ * ++ * Encoding class_idx into obj lets zs_free() locate the size_class ++ * without holding pool->lock; class_idx is invariant across page ++ * migration (only PFN changes), so a lockless read of the obj value ++ * always yields a valid class_idx. ++ */ ++#define ZS_OBJ_PFN_SHIFT (BITS_PER_LONG - _PFN_BITS) + + #define HUGE_BITS 1 + #define FULLNESS_BITS 4 +@@ -98,9 +117,61 @@ + + #define ZS_MAX_PAGES_PER_ZSPAGE (_AC(CONFIG_ZSMALLOC_CHAIN_SIZE, UL)) + ++/* ++ * Bits to index a page within a zspage = ceil(log2(ZS_MAX_PAGES_PER_ZSPAGE)). ++ * Computed at preprocessor time, for use in #if below. Kconfig ++ * restricts ZSMALLOC_CHAIN_SIZE to [4, 16]. ++ */ ++#if ZS_MAX_PAGES_PER_ZSPAGE <= 4 ++#define ZS_PAGES_PER_ZSPAGE_BITS 2 ++#elif ZS_MAX_PAGES_PER_ZSPAGE <= 8 ++#define ZS_PAGES_PER_ZSPAGE_BITS 3 ++#elif ZS_MAX_PAGES_PER_ZSPAGE <= 16 ++#define ZS_PAGES_PER_ZSPAGE_BITS 4 ++#else ++#error "ZSMALLOC_CHAIN_SIZE out of expected range [4,16]" ++#endif ++ ++/* ++ * Bits to index an object within a single PAGE_SIZE at the smallest ++ * possible object size: log2(PAGE_SIZE / 32) = PAGE_SHIFT - 5. ++ * 32 is the hard floor of ZS_MIN_ALLOC_SIZE. ++ */ ++#define ZS_OBJS_PER_PAGE_BITS (PAGE_SHIFT - 5) ++ ++/* ++ * Bits to index any object in the densest possible zspage. Below this, ++ * ZS_MIN_ALLOC_SIZE is auto-raised by the MAX(32, ...) formula -- still ++ * correct, but objects are coarser. ++ */ ++#define ZS_OBJS_PER_ZSPAGE_BITS \ ++ (ZS_PAGES_PER_ZSPAGE_BITS + ZS_OBJS_PER_PAGE_BITS) ++ ++/* ++ * Encode class_idx only when obj has spare bits; otherwise ++ * ZS_OBJ_CLASS_BITS folds to 0 (32-bit, or 64-bit UML/fallback). ++ */ ++#if BITS_PER_LONG >= 64 && \ ++ ZS_OBJ_PFN_SHIFT >= (CLASS_BITS + 1) + ZS_OBJS_PER_ZSPAGE_BITS ++#define ZS_OBJ_CLASS_BITS (CLASS_BITS + 1) ++#else ++#define ZS_OBJ_CLASS_BITS 0 ++#endif ++#define ZS_OBJ_CLASS_MASK ((_AC(1, UL) << ZS_OBJ_CLASS_BITS) - 1) ++ ++#define ZS_OBJ_IDX_BITS (ZS_OBJ_PFN_SHIFT - ZS_OBJ_CLASS_BITS) ++#define ZS_OBJ_IDX_MASK ((_AC(1, UL) << ZS_OBJ_IDX_BITS) - 1) ++ ++/* ++ * Belt-and-suspenders: the #if above already guarantees this when ++ * class_idx is enabled. Catches future tweaks that bypass it. ++ */ ++static_assert(ZS_OBJ_IDX_BITS >= ZS_PAGES_PER_ZSPAGE_BITS, ++ "zsmalloc: ZS_MIN_ALLOC_SIZE would exceed ZS_MAX_ALLOC_SIZE"); ++ + /* ZS_MIN_ALLOC_SIZE must be multiple of ZS_ALIGN */ + #define ZS_MIN_ALLOC_SIZE \ +- MAX(32, (ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT >> OBJ_INDEX_BITS)) ++ MAX(32, (ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT >> ZS_OBJ_IDX_BITS)) + /* each chunk includes extra space to keep handle */ + #define ZS_MAX_ALLOC_SIZE PAGE_SIZE + +@@ -396,10 +467,13 @@ static void cache_free_zspage(struct zspage *zspage) + kmem_cache_free(zspage_cachep, zspage); + } + +-/* class->lock(which owns the handle) synchronizes races */ ++/* ++ * Pairs with READ_ONCE() in handle_to_obj(): zs_free() may read the ++ * handle locklessly, so prevent store tearing here. ++ */ + static void record_obj(unsigned long handle, unsigned long obj) + { +- *(unsigned long *)handle = obj; ++ WRITE_ONCE(*(unsigned long *)handle, obj); + } + + static inline bool __maybe_unused is_first_zpdesc(struct zpdesc *zpdesc) +@@ -725,33 +799,36 @@ static struct zpdesc *get_next_zpdesc(struct zpdesc *zpdesc) + static void obj_to_location(unsigned long obj, struct zpdesc **zpdesc, + unsigned int *obj_idx) + { +- *zpdesc = pfn_zpdesc(obj >> OBJ_INDEX_BITS); +- *obj_idx = (obj & OBJ_INDEX_MASK); ++ *zpdesc = pfn_zpdesc(obj >> ZS_OBJ_PFN_SHIFT); ++ *obj_idx = (obj & ZS_OBJ_IDX_MASK); + } + + static void obj_to_zpdesc(unsigned long obj, struct zpdesc **zpdesc) + { +- *zpdesc = pfn_zpdesc(obj >> OBJ_INDEX_BITS); ++ *zpdesc = pfn_zpdesc(obj >> ZS_OBJ_PFN_SHIFT); + } + + /** +- * location_to_obj - get obj value encoded from (, ) ++ * location_to_obj - encode (, , ) into obj value + * @zpdesc: zpdesc object resides in zspage + * @obj_idx: object index ++ * @class_idx: size class index; ignored when ZS_OBJ_CLASS_BITS == 0 + */ +-static unsigned long location_to_obj(struct zpdesc *zpdesc, unsigned int obj_idx) ++static unsigned long location_to_obj(struct zpdesc *zpdesc, unsigned int obj_idx, ++ unsigned int class_idx) + { + unsigned long obj; + +- obj = zpdesc_pfn(zpdesc) << OBJ_INDEX_BITS; +- obj |= obj_idx & OBJ_INDEX_MASK; ++ obj = zpdesc_pfn(zpdesc) << ZS_OBJ_PFN_SHIFT; ++ obj |= (unsigned long)(class_idx & ZS_OBJ_CLASS_MASK) << ZS_OBJ_IDX_BITS; ++ obj |= obj_idx & ZS_OBJ_IDX_MASK; + + return obj; + } + + static unsigned long handle_to_obj(unsigned long handle) + { +- return *(unsigned long *)handle; ++ return READ_ONCE(*(unsigned long *)handle); + } + + static inline bool obj_allocated(struct zpdesc *zpdesc, void *obj, +@@ -805,13 +882,26 @@ static int trylock_zspage(struct zspage *zspage) + return 0; + } + +-static void __free_zspage(struct zs_pool *pool, struct size_class *class, +- struct zspage *zspage) ++/* ++ * Three free helpers, kept apart here: ++ * ++ * __free_zspage_lockless(): bare core; walks zpdescs and returns pages ++ * to the buddy allocator. Caller owns all zpdesc locks and has ++ * removed the zspage from its class list. Used by zs_free() outside ++ * class->lock so the buddy-side work does not stall the class. ++ * ++ * __free_zspage(): __free_zspage_lockless() + per-class accounting, ++ * under class->lock. Used by async_free_zspage(), the worker for ++ * zspages whose trylock_zspage() failed. ++ * ++ * free_zspage(): full wrapper - trylock zpdescs, remove from class ++ * list, call __free_zspage(); kicks deferred free on contention. ++ * Used by compaction. ++ */ ++static inline void __free_zspage_lockless(struct zspage *zspage) + { + struct zpdesc *zpdesc, *next; + +- assert_spin_locked(&class->lock); +- + VM_BUG_ON(get_zspage_inuse(zspage)); + VM_BUG_ON(zspage->fullness != ZS_INUSE_RATIO_0); + +@@ -827,7 +917,13 @@ static void __free_zspage(struct zs_pool *pool, struct size_class *class, + } while (zpdesc != NULL); + + cache_free_zspage(zspage); ++} + ++static void __free_zspage(struct zs_pool *pool, struct size_class *class, ++ struct zspage *zspage) ++{ ++ assert_spin_locked(&class->lock); ++ __free_zspage_lockless(zspage); + class_stat_sub(class, ZS_OBJS_ALLOCATED, class->objs_per_zspage); + atomic_long_sub(class->pages_per_zspage, &pool->pages_allocated); + } +@@ -1280,7 +1376,7 @@ static unsigned long obj_malloc(struct zs_pool *pool, + kunmap_local(vaddr); + mod_zspage_inuse(zspage, 1); + +- obj = location_to_obj(m_zpdesc, obj); ++ obj = location_to_obj(m_zpdesc, obj, zspage->class); + record_obj(handle, obj); + + return obj; +@@ -1383,37 +1479,97 @@ static void obj_free(int class_size, unsigned long obj) + mod_zspage_inuse(zspage, -1); + } + ++#if (ZS_OBJ_CLASS_BITS > 0) || defined(CONFIG_COMPACTION) ++/* Folds to 0 when ZS_OBJ_CLASS_BITS == 0; no ifdef needed at callers. */ ++static unsigned int obj_to_class_idx(unsigned long obj) ++{ ++ return (obj >> ZS_OBJ_IDX_BITS) & ZS_OBJ_CLASS_MASK; ++} ++#endif ++ ++/* ++ * Resolve @handle to its zspage / size_class and acquire class->lock. ++ * ++ * When class_idx is encoded in obj (ZS_OBJ_CLASS_BITS > 0), it is ++ * invariant under page migration, so the handle can be read locklessly ++ * to pick the size_class. Once class->lock is held migration is ++ * blocked and the handle is re-read to obtain a stable PFN. ++ * ++ * Otherwise (32-bit, or 64-bit fallback paths like UML where the ++ * encoding is disabled), fall back to pool->lock for the lookup. ++ */ ++#if ZS_OBJ_CLASS_BITS > 0 ++static inline void obj_class_get_and_lock(struct zs_pool *pool, unsigned long handle, ++ unsigned long *objp, struct zspage **zspagep, ++ struct size_class **classp) ++ __acquires(&(*classp)->lock) ++{ ++ struct zpdesc *f_zpdesc; ++ unsigned long obj; ++ ++ obj = handle_to_obj(handle); ++ *classp = pool->size_class[obj_to_class_idx(obj)]; ++ spin_lock(&(*classp)->lock); ++ /* Re-read under class->lock: PFN is now stable vs migration. */ ++ obj = handle_to_obj(handle); ++ obj_to_zpdesc(obj, &f_zpdesc); ++ *zspagep = get_zspage(f_zpdesc); ++ *objp = obj; ++} ++#else ++static inline void obj_class_get_and_lock(struct zs_pool *pool, unsigned long handle, ++ unsigned long *objp, struct zspage **zspagep, ++ struct size_class **classp) ++ __acquires(&(*classp)->lock) ++{ ++ struct zpdesc *f_zpdesc; ++ unsigned long obj; ++ ++ read_lock(&pool->lock); ++ obj = handle_to_obj(handle); ++ obj_to_zpdesc(obj, &f_zpdesc); ++ *zspagep = get_zspage(f_zpdesc); ++ *classp = zspage_class(pool, *zspagep); ++ spin_lock(&(*classp)->lock); ++ read_unlock(&pool->lock); ++ *objp = obj; ++} ++#endif ++ + void zs_free(struct zs_pool *pool, unsigned long handle) + { + struct zspage *zspage; +- struct zpdesc *f_zpdesc; + unsigned long obj; + struct size_class *class; + int fullness; ++ struct zspage *zspage_to_free = NULL; + + if (IS_ERR_OR_NULL((void *)handle)) + return; + +- /* +- * The pool->lock protects the race with zpage's migration +- * so it's safe to get the page from handle. +- */ +- read_lock(&pool->lock); +- obj = handle_to_obj(handle); +- obj_to_zpdesc(obj, &f_zpdesc); +- zspage = get_zspage(f_zpdesc); +- class = zspage_class(pool, zspage); +- spin_lock(&class->lock); +- read_unlock(&pool->lock); ++ obj_class_get_and_lock(pool, handle, &obj, &zspage, &class); + + class_stat_sub(class, ZS_OBJS_INUSE, 1); + obj_free(class->size, obj); + + fullness = fix_fullness_group(class, zspage); +- if (fullness == ZS_INUSE_RATIO_0) +- free_zspage(pool, class, zspage); ++ if (fullness == ZS_INUSE_RATIO_0) { ++ if (trylock_zspage(zspage)) { ++ remove_zspage(class, zspage); ++ class_stat_sub(class, ZS_OBJS_ALLOCATED, ++ class->objs_per_zspage); ++ zspage_to_free = zspage; ++ } else { ++ kick_deferred_free(pool); ++ } ++ } + + spin_unlock(&class->lock); ++ ++ if (zspage_to_free) { ++ __free_zspage_lockless(zspage_to_free); ++ atomic_long_sub(class->pages_per_zspage, &pool->pages_allocated); ++ } + cache_free_handle(handle); + } + EXPORT_SYMBOL_GPL(zs_free); +@@ -1646,9 +1802,6 @@ static void lock_zspage(struct zspage *zspage) + } + zspage_read_unlock(zspage); + } +-#endif /* CONFIG_COMPACTION */ +- +-#ifdef CONFIG_COMPACTION + + static void replace_sub_page(struct size_class *class, struct zspage *zspage, + struct zpdesc *newzpdesc, struct zpdesc *oldzpdesc) +@@ -1715,8 +1868,8 @@ static int zs_page_migrate(struct page *newpage, struct page *page, + pool = zspage->pool; + + /* +- * The pool migrate_lock protects the race between zpage migration +- * and zs_free. ++ * The pool migrate_lock protects against races between zpage migration ++ * and zs_free(), but only when ZS_OBJ_CLASS_BITS does not apply. + */ + write_lock(&pool->lock); + class = zspage_class(pool, zspage); +@@ -1764,7 +1917,8 @@ static int zs_page_migrate(struct page *newpage, struct page *page, + + old_obj = handle_to_obj(handle); + obj_to_location(old_obj, &dummy, &obj_idx); +- new_obj = (unsigned long)location_to_obj(newzpdesc, obj_idx); ++ new_obj = location_to_obj(newzpdesc, obj_idx, ++ obj_to_class_idx(old_obj)); + record_obj(handle, new_obj); + } + } +@@ -1775,9 +1929,9 @@ static int zs_page_migrate(struct page *newpage, struct page *page, + * Since we complete the data copy and set up new zspage structure, + * it's okay to release migration_lock. + */ +- write_unlock(&pool->lock); +- spin_unlock(&class->lock); + zspage_write_unlock(zspage); ++ spin_unlock(&class->lock); ++ write_unlock(&pool->lock); + + zpdesc_get(newzpdesc); + if (zpdesc_zone(newzpdesc) != zpdesc_zone(zpdesc)) { +@@ -1894,8 +2048,9 @@ static unsigned long __zs_compact(struct zs_pool *pool, + unsigned long pages_freed = 0; + + /* +- * protect the race between zpage migration and zs_free +- * as well as zpage allocation/free ++ * Protect against races between zpage migration and zs_free() ++ * (only when ZS_OBJ_CLASS_BITS does not apply), as well as ++ * zpage allocation and free. + */ + write_lock(&pool->lock); + spin_lock(&class->lock); diff --git a/pkgbuilds/linux-omarchy/0260-mglru-exec-protect.patch b/pkgbuilds/linux-omarchy/0260-mglru-exec-protect.patch new file mode 100644 index 0000000..f3b0e56 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0260-mglru-exec-protect.patch @@ -0,0 +1,313 @@ +diff --git a/include/linux/rmap.h b/include/linux/rmap.h +--- a/include/linux/rmap.h ++++ b/include/linux/rmap.h +@@ -843,7 +843,7 @@ static inline int folio_try_share_anon_rmap_pmd(struct folio *folio, + * Called from mm/vmscan.c to handle paging out + */ + int folio_referenced(struct folio *, int is_locked, +- struct mem_cgroup *memcg, vm_flags_t *vm_flags); ++ struct mem_cgroup *memcg, vma_flags_t *vma_flags); + + void try_to_migrate(struct folio *folio, enum ttu_flags flags); + void try_to_unmap(struct folio *, enum ttu_flags flags); +@@ -975,10 +975,9 @@ struct anon_vma *folio_lock_anon_vma_read(const struct folio *folio, + #define anon_vma_prepare(vma) (0) + + static inline int folio_referenced(struct folio *folio, int is_locked, +- struct mem_cgroup *memcg, +- vm_flags_t *vm_flags) ++ struct mem_cgroup *memcg, vma_flags_t *vma_flags) + { +- *vm_flags = 0; ++ vma_flags_clear_all(vma_flags); + return 0; + } + +diff --git a/mm/rmap.c b/mm/rmap.c +--- a/mm/rmap.c ++++ b/mm/rmap.c +@@ -907,7 +907,7 @@ pmd_t *mm_find_pmd(struct mm_struct *mm, unsigned long address) + struct folio_referenced_arg { + int mapcount; + int referenced; +- vm_flags_t vm_flags; ++ vma_flags_t vma_flags; + struct mem_cgroup *memcg; + }; + +@@ -926,7 +926,7 @@ static bool folio_referenced_one(struct folio *folio, + address = pvmw.address; + nr = 1; + +- if (vma->vm_flags & VM_LOCKED) { ++ if (vma_test(vma, VMA_LOCKED_BIT)) { + ptes++; + pra->mapcount--; + +@@ -947,7 +947,7 @@ static bool folio_referenced_one(struct folio *folio, + /* Restore the mlock which got missed */ + mlock_vma_folio(folio, vma); + page_vma_mapped_walk_done(&pvmw); +- pra->vm_flags |= VM_LOCKED; ++ vma_flags_set(&pra->vma_flags, VMA_LOCKED_BIT); + return false; /* To break the loop */ + } + +@@ -1015,8 +1015,11 @@ static bool folio_referenced_one(struct folio *folio, + referenced++; + + if (referenced) { ++ vma_flags_t vma_flags = vma->flags; ++ + pra->referenced++; +- pra->vm_flags |= vma->vm_flags & ~VM_LOCKED; ++ vma_flags_clear(&vma_flags, VMA_LOCKED_BIT); ++ vma_flags_set_mask(&pra->vma_flags, vma_flags); + } + + if (!pra->mapcount) +@@ -1054,7 +1057,7 @@ static bool invalid_folio_referenced_vma(struct vm_area_struct *vma, void *arg) + * @folio: The folio to test. + * @is_locked: Caller holds lock on the folio. + * @memcg: target memory cgroup +- * @vm_flags: A combination of all the vma->vm_flags which referenced the folio. ++ * @vma_flags: A combination of all the vma->flags which referenced the folio. + * + * Quick test_and_clear_referenced for all mappings of a folio, + * +@@ -1062,7 +1065,7 @@ static bool invalid_folio_referenced_vma(struct vm_area_struct *vma, void *arg) + * the function bailed out due to rmap lock contention. + */ + int folio_referenced(struct folio *folio, int is_locked, +- struct mem_cgroup *memcg, vm_flags_t *vm_flags) ++ struct mem_cgroup *memcg, vma_flags_t *vma_flags) + { + bool we_locked = false; + struct folio_referenced_arg pra = { +@@ -1078,7 +1081,7 @@ int folio_referenced(struct folio *folio, int is_locked, + }; + + VM_WARN_ON_ONCE_FOLIO(folio_is_zone_device(folio), folio); +- *vm_flags = 0; ++ vma_flags_clear_all(vma_flags); + if (!pra.mapcount) + return 0; + +@@ -1092,7 +1095,7 @@ int folio_referenced(struct folio *folio, int is_locked, + } + + rmap_walk(folio, &rwc); +- *vm_flags = pra.vm_flags; ++ vma_flags_set_mask(vma_flags, pra.vma_flags); + + if (we_locked) + folio_unlock(folio); +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -262,6 +262,12 @@ static bool writeback_throttling_sane(struct scan_control *sc) + } + #endif + ++static inline bool is_exec_file_folio(const struct folio *folio, ++ const vma_flags_t *vma_flags) ++{ ++ return vma_flags_test(vma_flags, VMA_EXEC_BIT) && folio_is_file_lru(folio); ++} ++ + static void set_task_reclaim_state(struct task_struct *task, + struct reclaim_state *rs) + { +@@ -830,10 +836,16 @@ enum folio_references { + * with PG_active set. In contrast, the aging (page table walk) path uses + * folio_update_gen(). + */ +-static bool lru_gen_set_refs(struct folio *folio) ++static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_flags) + { + /* see the comment on LRU_REFS_FLAGS */ + if (!folio_test_referenced(folio) && !folio_test_workingset(folio)) { ++ /* Activate file-backed executable folios after first usage. */ ++ if (is_exec_file_folio(folio, vma_flags)) { ++ set_mask_bits(&folio->flags.f, LRU_REFS_FLAGS, BIT(PG_workingset)); ++ return true; ++ } ++ + set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced)); + return false; + } +@@ -846,7 +858,7 @@ static bool lru_gen_set_refs(struct folio *folio) + return true; + } + #else +-static bool lru_gen_set_refs(struct folio *folio) ++static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_flags) + { + return false; + } +@@ -856,16 +868,16 @@ static enum folio_references folio_check_references(struct folio *folio, + struct scan_control *sc) + { + int referenced_ptes, referenced_folio; +- vm_flags_t vm_flags; ++ vma_flags_t vma_flags; + + referenced_ptes = folio_referenced(folio, 1, sc->target_mem_cgroup, +- &vm_flags); ++ &vma_flags); + + /* + * The supposedly reclaimable folio was found to be in a VM_LOCKED vma. + * Let the folio, now marked Mlocked, be moved to the unevictable list. + */ +- if (vm_flags & VM_LOCKED) ++ if (vma_flags_test(&vma_flags, VMA_LOCKED_BIT)) + return FOLIOREF_ACTIVATE; + + /* +@@ -881,7 +893,7 @@ static enum folio_references folio_check_references(struct folio *folio, + if (!referenced_ptes) + return FOLIOREF_RECLAIM; + +- return lru_gen_set_refs(folio) ? FOLIOREF_ACTIVATE : FOLIOREF_KEEP; ++ return lru_gen_set_refs(folio, &vma_flags) ? FOLIOREF_ACTIVATE : FOLIOREF_KEEP; + } + + referenced_folio = folio_test_clear_referenced(folio); +@@ -909,7 +921,7 @@ static enum folio_references folio_check_references(struct folio *folio, + /* + * Activate file-backed executable folios after first usage. + */ +- if ((vm_flags & VM_EXEC) && folio_is_file_lru(folio)) ++ if (is_exec_file_folio(folio, &vma_flags)) + return FOLIOREF_ACTIVATE; + + return FOLIOREF_KEEP; +@@ -2067,7 +2079,7 @@ static void shrink_active_list(unsigned long nr_to_scan, + { + unsigned long nr_taken; + unsigned long nr_scanned; +- vm_flags_t vm_flags; ++ vma_flags_t vma_flags; + LIST_HEAD(l_hold); /* The folios which were snipped off */ + LIST_HEAD(l_active); + LIST_HEAD(l_inactive); +@@ -2111,7 +2123,7 @@ static void shrink_active_list(unsigned long nr_to_scan, + + /* Referenced or rmap lock contention: rotate */ + if (folio_referenced(folio, 0, sc->target_mem_cgroup, +- &vm_flags) != 0) { ++ &vma_flags) != 0) { + /* + * Identify referenced, file-backed active folios and + * give them one more trip around the active list. So +@@ -2121,7 +2133,7 @@ static void shrink_active_list(unsigned long nr_to_scan, + * IO, plus JVM can create lots of anon VM_EXEC folios, + * so we ignore them here. + */ +- if ((vm_flags & VM_EXEC) && folio_is_file_lru(folio)) { ++ if (is_exec_file_folio(folio, &vma_flags)) { + nr_rotated += folio_nr_pages(folio); + list_add(&folio->lru, &l_active); + continue; +@@ -3190,14 +3202,19 @@ static bool positive_ctrl_err(struct ctrl_pos *sp, struct ctrl_pos *pv) + ******************************************************************************/ + + /* promote pages accessed through page tables */ +-static int folio_update_gen(struct folio *folio, int gen) ++static int folio_update_gen(struct folio *folio, int gen, const vma_flags_t *vma_flags) + { + unsigned long new_flags, old_flags = READ_ONCE(folio->flags.f); + + VM_WARN_ON_ONCE(gen >= MAX_NR_GENS); + +- /* see the comment on LRU_REFS_FLAGS */ +- if (!folio_test_referenced(folio) && !folio_test_workingset(folio)) { ++ /* ++ * See the comment on LRU_REFS_FLAGS, and activate file-backed ++ * executable folios after first usage to avoid typical IO ++ * thrashing from reclaiming. ++ */ ++ if (!folio_test_referenced(folio) && !folio_test_workingset(folio) && ++ !is_exec_file_folio(folio, vma_flags)) { + set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced)); + return -1; + } +@@ -3430,8 +3447,8 @@ static bool suitable_to_scan(int total, int young) + return young * n >= total; + } + +-static void walk_update_folio(struct lru_gen_mm_walk *walk, struct folio *folio, +- int new_gen, bool dirty) ++static void walk_update_folio(struct lru_gen_mm_walk *walk, struct vm_area_struct *vma, ++ struct folio *folio, int new_gen, bool dirty) + { + int old_gen; + +@@ -3444,10 +3461,10 @@ static void walk_update_folio(struct lru_gen_mm_walk *walk, struct folio *folio, + folio_mark_dirty(folio); + + if (walk) { +- old_gen = folio_update_gen(folio, new_gen); ++ old_gen = folio_update_gen(folio, new_gen, &vma->flags); + if (old_gen >= 0 && old_gen != new_gen) + update_batch_size(walk, folio, old_gen, new_gen); +- } else if (lru_gen_set_refs(folio)) { ++ } else if (lru_gen_set_refs(folio, &vma->flags)) { + old_gen = folio_lru_gen(folio); + if (old_gen >= 0 && old_gen != new_gen) + folio_activate(folio); +@@ -3520,7 +3537,7 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end, + continue; + + if (last != folio) { +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, args->vma, last, gen, dirty); + + last = folio; + dirty = false; +@@ -3533,7 +3550,7 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end, + walk->mm_stats[MM_LEAF_YOUNG] += nr; + } + +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, args->vma, last, gen, dirty); + last = NULL; + + if (i < PTRS_PER_PTE && get_next_vma(PMD_MASK, PAGE_SIZE, args, &start, &end)) +@@ -3611,7 +3628,7 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area + goto next; + + if (last != folio) { +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + last = folio; + dirty = false; +@@ -3625,7 +3642,7 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area + i = i > MIN_LRU_BATCH ? 0 : find_next_bit(bitmap, MIN_LRU_BATCH, i) + 1; + } while (i <= MIN_LRU_BATCH); + +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + lazy_mmu_mode_disable(); + spin_unlock(ptl); +@@ -4260,7 +4277,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr) + continue; + + if (last != folio) { +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + last = folio; + dirty = false; +@@ -4272,7 +4289,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr) + young += nr; + } + +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + lazy_mmu_mode_disable(); + diff --git a/pkgbuilds/linux-omarchy/0270-ksm-rmap-walk.patch b/pkgbuilds/linux-omarchy/0270-ksm-rmap-walk.patch new file mode 100644 index 0000000..afe8241 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0270-ksm-rmap-walk.patch @@ -0,0 +1,161 @@ +diff --git a/mm/ksm.c b/mm/ksm.c +--- a/mm/ksm.c ++++ b/mm/ksm.c +@@ -195,22 +195,28 @@ struct ksm_stable_node { + * @node: rb node of this rmap_item in the unstable tree + * @head: pointer to stable_node heading this list in the stable tree + * @hlist: link into hlist of rmap_items hanging off that stable_node +- * @age: number of scan iterations since creation +- * @remaining_skips: how many scans to skip ++ * @age: number of scan iterations since creation (unstable node) ++ * @remaining_skips: how many scans to skip (unstable node) ++ * @linear_page_index: the original page's index before merged by KSM (stable node) + */ + struct ksm_rmap_item { + struct ksm_rmap_item *rmap_list; + union { +- struct anon_vma *anon_vma; /* when stable */ ++ struct anon_vma *anon_vma; /* for reverse mapping, when stable */ + #ifdef CONFIG_NUMA + int nid; /* when node of unstable tree */ + #endif + }; + struct mm_struct *mm; + unsigned long address; /* + low bits used for flags below */ +- unsigned int oldchecksum; /* when unstable */ +- rmap_age_t age; +- rmap_age_t remaining_skips; ++ union { ++ struct { ++ unsigned int oldchecksum; ++ rmap_age_t age; ++ rmap_age_t remaining_skips; ++ }; /* when unstable */ ++ unsigned long linear_page_index; /* for reverse mapping, when stable */ ++ }; + union { + struct rb_node node; /* when node of unstable tree */ + struct { /* when listed from stable tree */ +@@ -776,6 +782,11 @@ static struct vm_area_struct *find_mergeable_vma(struct mm_struct *mm, + return vma; + } + ++/* ++ * break_cow: actively break COW, replacing the KSM page by a fresh anonymous ++ * page. This is called when rmap_item has not yet become stable, but page ++ * has been merged. ++ */ + static void break_cow(struct ksm_rmap_item *rmap_item) + { + struct mm_struct *mm = rmap_item->mm; +@@ -787,6 +798,11 @@ static void break_cow(struct ksm_rmap_item *rmap_item) + * to undo, we also need to drop a reference to the anon_vma. + */ + put_anon_vma(rmap_item->anon_vma); ++ /* ++ * Reset linear_page_index that might overlay age-related ++ * information. (it's still unstable node) ++ */ ++ rmap_item->linear_page_index = 0; + + mmap_read_lock(mm); + vma = find_mergeable_vma(mm, addr); +@@ -899,6 +915,8 @@ static void remove_node_from_stable_tree(struct ksm_stable_node *stable_node) + VM_BUG_ON(stable_node->rmap_hlist_len <= 0); + stable_node->rmap_hlist_len--; + put_anon_vma(rmap_item->anon_vma); ++ /* Reset linear_page_index that might overlay age-related information. */ ++ rmap_item->linear_page_index = 0; + rmap_item->address &= PAGE_MASK; + cond_resched(); + } +@@ -1052,6 +1070,8 @@ static void remove_rmap_item_from_tree(struct ksm_rmap_item *rmap_item) + stable_node->rmap_hlist_len--; + + put_anon_vma(rmap_item->anon_vma); ++ /* Reset linear_page_index that might overlay age-related information. */ ++ rmap_item->linear_page_index = 0; + rmap_item->head = NULL; + rmap_item->address &= PAGE_MASK; + +@@ -1598,8 +1618,15 @@ static int try_to_merge_with_ksm_page(struct ksm_rmap_item *rmap_item, + /* Unstable nid is in union with stable anon_vma: remove first */ + remove_rmap_item_from_tree(rmap_item); + +- /* Must get reference to anon_vma while still holding mmap_lock */ ++ /* ++ * We can consider the VMA only while still holding the mmap lock, ++ * so lock, so reference the anon_vma and calculate the linear ++ * page index early, before stable_tree_append(). If anything goes ++ * wrong that prevents the rmap_item from being added to the ++ * stable_tree, break_cow() will clean it up. ++ */ + rmap_item->anon_vma = vma->anon_vma; ++ rmap_item->linear_page_index = linear_page_index(vma, rmap_item->address); + get_anon_vma(vma->anon_vma); + out: + mmap_read_unlock(mm); +@@ -2458,6 +2485,13 @@ static bool should_skip_rmap_item(struct folio *folio, + if (folio_test_ksm(folio)) + return false; + ++ /* ++ * There is no age information in stable-tree nodes. We might end up ++ * here without a KSM page for example after COW. ++ */ ++ if (rmap_item->address & STABLE_FLAG) ++ return false; ++ + age = rmap_item->age; + if (age != U8_MAX) + rmap_item->age++; +@@ -3173,6 +3207,7 @@ void rmap_walk_ksm(struct folio *folio, struct rmap_walk_control *rwc) + hlist_for_each_entry(rmap_item, &stable_node->hlist, hlist) { + /* Ignore the stable/unstable/sqnr flags */ + const unsigned long addr = rmap_item->address & PAGE_MASK; ++ const unsigned long index = rmap_item->linear_page_index; + struct anon_vma *anon_vma = rmap_item->anon_vma; + struct anon_vma_chain *vmac; + struct vm_area_struct *vma; +@@ -3186,8 +3221,18 @@ void rmap_walk_ksm(struct folio *folio, struct rmap_walk_control *rwc) + anon_vma_lock_read(anon_vma); + } + ++ /* ++ * Currently, KSM folios are always small folios, so it's ++ * sufficient to search for a single page. We can simply use ++ * the linear_page_index of the original de-duplicate ++ * anonymous page that we remembered in the rmap_item while ++ * de-duplicating. Note that mremap() always de-duplicates KSM ++ * folios: so if there was mremap() in our parent or our child, ++ * we wouldn't have the KSM folio mapped in these processes ++ * anymore. ++ */ + anon_vma_interval_tree_foreach(vmac, &anon_vma->rb_root, +- 0, ULONG_MAX) { ++ index, index) { + + cond_resched(); + vma = vmac->vma; +@@ -3243,17 +3288,17 @@ void collect_procs_ksm(const struct folio *folio, const struct page *page, + rcu_read_lock(); + for_each_process(tsk) { + struct anon_vma_chain *vmac; +- unsigned long addr; ++ const unsigned long addr = rmap_item->address & PAGE_MASK; ++ const unsigned long index = rmap_item->linear_page_index; + struct task_struct *t = + task_early_kill(tsk, force_early); + if (!t) + continue; +- anon_vma_interval_tree_foreach(vmac, &av->rb_root, 0, +- ULONG_MAX) ++ anon_vma_interval_tree_foreach(vmac, &av->rb_root, index, ++ index) + { + vma = vmac->vma; + if (vma->vm_mm == t->mm) { +- addr = rmap_item->address & PAGE_MASK; + add_to_kill_ksm(t, page, vma, to_kill, + addr); + } diff --git a/pkgbuilds/linux-omarchy/0280-mm-updates.patch b/pkgbuilds/linux-omarchy/0280-mm-updates.patch new file mode 100644 index 0000000..46a53c5 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0280-mm-updates.patch @@ -0,0 +1,1270 @@ +diff --git a/drivers/block/zram/backend_842.c b/drivers/block/zram/backend_842.c +--- a/drivers/block/zram/backend_842.c ++++ b/drivers/block/zram/backend_842.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "842: " fmt ++ + #include + #include + #include +@@ -13,6 +15,14 @@ static void release_params_842(struct zcomp_params *params) + + static int setup_params_842(struct zcomp_params *params) + { ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ if (params->level != ZCOMP_PARAM_NOT_SET) { ++ pr_err("compression level is not supported\n"); ++ return -EOPNOTSUPP; ++ } + return 0; + } + +diff --git a/drivers/block/zram/backend_deflate.c b/drivers/block/zram/backend_deflate.c +--- a/drivers/block/zram/backend_deflate.c ++++ b/drivers/block/zram/backend_deflate.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "deflate: " fmt ++ + #include + #include + #include +@@ -22,15 +24,26 @@ static void deflate_release_params(struct zcomp_params *params) + + static int deflate_setup_params(struct zcomp_params *params) + { +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = Z_DEFAULT_COMPRESSION; ++ } else if (params->level < Z_DEFAULT_COMPRESSION || ++ params->level > Z_BEST_COMPRESSION) { ++ pr_err("invalid compression level %d\n", params->level); ++ return -EINVAL; ++ } ++ + if (params->deflate.winbits == ZCOMP_PARAM_NOT_SET) { + params->deflate.winbits = DEFLATE_DEF_WINBITS; + } else { + s32 wb = params->deflate.winbits; + + if ((wb < -15 || wb > -9) && (wb < 9 || wb > 15)) { +- pr_err("invalid deflate winbits: %d\n", wb); ++ pr_err("invalid winbits %d\n", wb); + return -EINVAL; + } + } +diff --git a/drivers/block/zram/backend_lz4.c b/drivers/block/zram/backend_lz4.c +--- a/drivers/block/zram/backend_lz4.c ++++ b/drivers/block/zram/backend_lz4.c +@@ -1,3 +1,7 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#define pr_fmt(fmt) "lz4: " fmt ++ + #include + #include + #include +@@ -28,8 +32,12 @@ static int lz4_setup_params(struct zcomp_params *params) + LZ4_stream_t *dict_stream; + int ret; + +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = LZ4_ACCELERATION_DEFAULT; ++ } else if (params->level < LZ4_ACCELERATION_DEFAULT) { ++ pr_err("invalid compression level %d\n", params->level); ++ return -EINVAL; ++ } + + if (!params->dict || !params->dict_sz) + return 0; +diff --git a/drivers/block/zram/backend_lz4hc.c b/drivers/block/zram/backend_lz4hc.c +--- a/drivers/block/zram/backend_lz4hc.c ++++ b/drivers/block/zram/backend_lz4hc.c +@@ -1,3 +1,7 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#define pr_fmt(fmt) "lz4hc: " fmt ++ + #include + #include + #include +@@ -18,8 +22,18 @@ static void lz4hc_release_params(struct zcomp_params *params) + + static int lz4hc_setup_params(struct zcomp_params *params) + { +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = LZ4HC_DEFAULT_CLEVEL; ++ } else if (params->level < 1 || params->level > LZ4HC_MAX_CLEVEL) { ++ /* ++ * Use < 1 rather than < LZ4HC_MIN_CLEVEL here because ++ * LZ4HC_compress_generic() only clamps levels below 1 ++ * (levels 1 and 2 are valid). LZ4HC_MIN_CLEVEL (3) is ++ * advisory and not enforced by the library. ++ */ ++ pr_err("invalid compression level %d\n", params->level); ++ return -EINVAL; ++ } + + return 0; + } +diff --git a/drivers/block/zram/backend_lzo.c b/drivers/block/zram/backend_lzo.c +--- a/drivers/block/zram/backend_lzo.c ++++ b/drivers/block/zram/backend_lzo.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "lzo: " fmt ++ + #include + #include + #include +@@ -12,6 +14,14 @@ static void lzo_release_params(struct zcomp_params *params) + + static int lzo_setup_params(struct zcomp_params *params) + { ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ if (params->level != ZCOMP_PARAM_NOT_SET) { ++ pr_err("compression level is not supported\n"); ++ return -EOPNOTSUPP; ++ } + return 0; + } + +diff --git a/drivers/block/zram/backend_lzorle.c b/drivers/block/zram/backend_lzorle.c +--- a/drivers/block/zram/backend_lzorle.c ++++ b/drivers/block/zram/backend_lzorle.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "lzo-rle: " fmt ++ + #include + #include + #include +@@ -12,6 +14,14 @@ static void lzorle_release_params(struct zcomp_params *params) + + static int lzorle_setup_params(struct zcomp_params *params) + { ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ if (params->level != ZCOMP_PARAM_NOT_SET) { ++ pr_err("compression level is not supported\n"); ++ return -EOPNOTSUPP; ++ } + return 0; + } + +diff --git a/drivers/block/zram/backend_zstd.c b/drivers/block/zram/backend_zstd.c +--- a/drivers/block/zram/backend_zstd.c ++++ b/drivers/block/zram/backend_zstd.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "zstd: " fmt ++ + #include + #include + #include +@@ -58,8 +60,13 @@ static int zstd_setup_params(struct zcomp_params *params) + return -ENOMEM; + + params->drv_data = zp; +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = zstd_default_clevel(); ++ } else if (params->level < zstd_min_clevel() || ++ params->level > zstd_max_clevel()) { ++ pr_err("invalid compression level %d\n", params->level); ++ goto error; ++ } + + zp->cprm = zstd_get_params(params->level, PAGE_SIZE); + +@@ -85,7 +92,6 @@ static int zstd_setup_params(struct zcomp_params *params) + return 0; + + error: +- zstd_release_params(params); + return -EINVAL; + } + +@@ -161,7 +167,6 @@ static int zstd_create(struct zcomp_params *params, struct zcomp_ctx *ctx) + return 0; + + error: +- zstd_release_params(params); + zstd_destroy(ctx); + return -EINVAL; + } +diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c +--- a/drivers/block/zram/zram_drv.c ++++ b/drivers/block/zram/zram_drv.c +@@ -1664,6 +1664,17 @@ static void comp_algorithm_set(struct zram *zram, u32 prio, const char *alg) + zram->comp_algs[prio] = alg; + } + ++static void comp_params_reset(struct zram *zram, u32 prio) ++{ ++ struct zcomp_params *params = &zram->params[prio]; ++ ++ vfree(params->dict); ++ params->level = ZCOMP_PARAM_NOT_SET; ++ params->deflate.winbits = ZCOMP_PARAM_NOT_SET; ++ params->dict_sz = 0; ++ params->dict = NULL; ++} ++ + static int __comp_algorithm_store(struct zram *zram, u32 prio, const char *buf) + { + const char *alg; +@@ -1684,20 +1695,10 @@ static int __comp_algorithm_store(struct zram *zram, u32 prio, const char *buf) + } + + comp_algorithm_set(zram, prio, alg); ++ comp_params_reset(zram, prio); + return 0; + } + +-static void comp_params_reset(struct zram *zram, u32 prio) +-{ +- struct zcomp_params *params = &zram->params[prio]; +- +- vfree(params->dict); +- params->level = ZCOMP_PARAM_NOT_SET; +- params->deflate.winbits = ZCOMP_PARAM_NOT_SET; +- params->dict_sz = 0; +- params->dict = NULL; +-} +- + static int comp_params_store(struct zram *zram, u32 prio, s32 level, + const char *dict_path, + struct deflate_params *deflate_params) +@@ -1712,8 +1713,16 @@ static int comp_params_store(struct zram *zram, u32 prio, s32 level, + INT_MAX, + NULL, + READING_POLICY); +- if (sz < 0) ++ if (sz < 0) { ++ pr_err("failed to load dictionary %s (err=%zd)\n", ++ dict_path, sz); ++ return sz; ++ } ++ if (sz == 0) { ++ pr_err("failed to load dictionary %s (empty file)\n", ++ dict_path); + return -EINVAL; ++ } + } + + zram->params[prio].dict_sz = sz; +diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h +--- a/include/linux/memcontrol.h ++++ b/include/linux/memcontrol.h +@@ -270,10 +270,15 @@ struct mem_cgroup { + #endif + int kmemcg_id; + +- struct memcg_vmstats_percpu __percpu *vmstats_percpu; +- + #ifdef CONFIG_CGROUP_WRITEBACK + struct list_head cgwb_list; ++#endif ++ ++ /* Keep the hot per-CPU stats pointer away from memory event counters. */ ++ struct memcg_vmstats_percpu __percpu *vmstats_percpu ++ ____cacheline_aligned_in_smp; ++ ++#ifdef CONFIG_CGROUP_WRITEBACK + struct wb_domain cgwb_domain; + struct memcg_cgwb_frn cgwb_frn[MEMCG_CGWB_FRN_CNT]; + #endif +@@ -947,6 +952,8 @@ unsigned long memcg_page_state_output(struct mem_cgroup *memcg, int item); + bool memcg_stat_item_valid(int idx); + bool memcg_vm_event_item_valid(enum vm_event_item idx); + unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx); ++unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec, ++ enum node_stat_item idx); + unsigned long lruvec_page_state_local(struct lruvec *lruvec, + enum node_stat_item idx); + +@@ -1399,6 +1406,12 @@ static inline unsigned long lruvec_page_state(struct lruvec *lruvec, + return node_page_state(lruvec_pgdat(lruvec), idx); + } + ++static inline unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec, ++ enum node_stat_item idx) ++{ ++ return node_page_state_monotonic(lruvec_pgdat(lruvec), idx); ++} ++ + static inline unsigned long lruvec_page_state_local(struct lruvec *lruvec, + enum node_stat_item idx) + { +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h +@@ -323,6 +323,8 @@ enum node_stat_item { + PGSCAN_PROACTIVE, + PGSCAN_ANON, + PGSCAN_FILE, ++ PGROTATE_ANON, ++ PGROTATE_FILE, + PGREFILL, + #ifdef CONFIG_HUGETLB_PAGE + NR_HUGETLB, +@@ -755,6 +757,12 @@ void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, + + #endif /* CONFIG_LRU_GEN */ + ++struct lru_cost { ++ unsigned long count; ++ unsigned long last_rotated; ++ unsigned long last_io; ++}; ++ + struct lruvec { + struct list_head lists[NR_LRU_LISTS]; + /* per lruvec lru_lock for memcg */ +@@ -763,9 +771,12 @@ struct lruvec { + * These track the cost of reclaiming one LRU - file or anon - + * over the other. As the observed cost of reclaiming one LRU + * increases, the reclaim scan balance tips toward the other. ++ * Updated and decayed at prepare_scan_control() time; cost_lock ++ * serialises that update. + */ +- unsigned long anon_cost; +- unsigned long file_cost; ++ struct lru_cost cost[ANON_AND_FILE]; ++ /* Protects cost[]. */ ++ spinlock_t cost_lock; + /* Non-resident age, driven by LRU movement */ + atomic_long_t nonresident_age; + /* Refaults at the time of last reclaim cycle */ +diff --git a/include/linux/swap.h b/include/linux/swap.h +--- a/include/linux/swap.h ++++ b/include/linux/swap.h +@@ -309,9 +309,6 @@ extern unsigned long totalreserve_pages; + + + /* linux/mm/swap.c */ +-void lru_note_cost_unlock_irq(struct lruvec *lruvec, bool file, +- unsigned int nr_io, unsigned int nr_rotated); +-void lru_note_cost_refault(struct folio *); + void folio_add_lru(struct folio *); + void folio_add_lru_vma(struct folio *, struct vm_area_struct *); + void mark_page_accessed(struct page *); +diff --git a/include/linux/vmstat.h b/include/linux/vmstat.h +--- a/include/linux/vmstat.h ++++ b/include/linux/vmstat.h +@@ -20,7 +20,6 @@ struct reclaim_stat { + unsigned nr_congested; + unsigned nr_writeback; + unsigned nr_immediate; +- unsigned nr_pageout; + unsigned nr_activate[ANON_AND_FILE]; + unsigned nr_ref_keep; + unsigned nr_unmap_fail; +@@ -194,6 +193,19 @@ unsigned long global_node_page_state_pages(enum node_stat_item item) + return x; + } + ++/* ++ * Non-clamping variant of global_node_page_state() intended for callers that ++ * snapshot a monotonically-incremented counter and subtract two samples. ++ * Returns the raw wrapping value so that unsigned modular subtraction stays ++ * correct across a signed-long overflow (a real hazard on 32-bit) that the ++ * clamp in global_node_page_state() would otherwise turn into a huge spurious ++ * delta. Do NOT use for non-monotonic page-count reads. ++ */ ++static inline unsigned long global_node_page_state_monotonic(enum node_stat_item item) ++{ ++ return (unsigned long)atomic_long_read(&vm_node_stat[item]); ++} ++ + static inline unsigned long global_node_page_state(enum node_stat_item item) + { + VM_WARN_ON_ONCE(vmstat_item_in_bytes(item)); +@@ -259,11 +271,14 @@ extern unsigned long node_page_state(struct pglist_data *pgdat, + enum node_stat_item item); + extern unsigned long node_page_state_pages(struct pglist_data *pgdat, + enum node_stat_item item); ++extern unsigned long node_page_state_monotonic(struct pglist_data *pgdat, ++ enum node_stat_item item); + extern void fold_vm_numa_events(void); + #else + #define sum_zone_node_page_state(node, item) global_zone_page_state(item) + #define node_page_state(node, item) global_node_page_state(item) + #define node_page_state_pages(node, item) global_node_page_state_pages(item) ++#define node_page_state_monotonic(node, item) global_node_page_state_monotonic(item) + static inline void fold_vm_numa_events(void) + { + } +diff --git a/lib/maple_tree.c b/lib/maple_tree.c +--- a/lib/maple_tree.c ++++ b/lib/maple_tree.c +@@ -1501,14 +1501,26 @@ static inline void mas_parent_gap(struct ma_state *mas, unsigned char offset, + goto ascend; + } + ++static __always_inline void mas_update_gap_known(struct ma_state *mas, ++ unsigned long gap) ++{ ++ unsigned char pslot; ++ unsigned long p_gap; ++ ++ pslot = mte_parent_slot(mas->node); ++ p_gap = ma_gaps(mte_parent(mas->node), ++ mas_parent_type(mas, mas->node))[pslot]; ++ ++ if (p_gap != gap) ++ mas_parent_gap(mas, pslot, gap); ++} ++ + /* + * mas_update_gap() - Update a nodes gaps and propagate up if necessary. + * @mas: the maple state. + */ + static inline void mas_update_gap(struct ma_state *mas) + { +- unsigned char pslot; +- unsigned long p_gap; + unsigned long max_gap; + + if (!mt_is_alloc(mas->tree)) +@@ -1518,13 +1530,7 @@ static inline void mas_update_gap(struct ma_state *mas) + return; + + max_gap = mas_max_gap(mas); +- +- pslot = mte_parent_slot(mas->node); +- p_gap = ma_gaps(mte_parent(mas->node), +- mas_parent_type(mas, mas->node))[pslot]; +- +- if (p_gap != max_gap) +- mas_parent_gap(mas, pslot, max_gap); ++ mas_update_gap_known(mas, max_gap); + } + + /* +@@ -2081,8 +2087,8 @@ static inline void mas_wmb_replace(struct ma_state *mas, struct maple_copy *cp) + mas->node = mt_slot_locked(mas->tree, cp->slot, 0); + /* Insert the new data in the tree */ + mas_topiary_replace(mas, old_enode, cp->height); +- if (!mte_is_leaf(mas->node)) +- mas_update_gap(mas); ++ if (mt_is_alloc(mas->tree) && !mte_is_root(mas->node)) ++ mas_update_gap_known(mas, cp->gap[0]); + + mtree_range_walk(mas); + } +@@ -3125,7 +3131,7 @@ static void mas_wr_spanning_store(struct ma_wr_state *wr_mas) + static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + { + unsigned char dst_offset, offset_end; +- unsigned char copy_size, node_pivots; ++ unsigned char copy_size, node_pivots, node_slots; + struct maple_node reuse, *newnode; + unsigned long *dst_pivots; + void __rcu **dst_slots; +@@ -3138,6 +3144,7 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + in_rcu = mt_in_rcu(mas->tree); + offset_end = wr_mas->offset_end; + node_pivots = mt_pivots[wr_mas->type]; ++ node_slots = mt_slots[wr_mas->type]; + /* Assume last adds an entry */ + new_end = mas->end + 1 - offset_end + mas->offset; + if (mas->last == wr_mas->end_piv) { +@@ -3149,7 +3156,6 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + if (in_rcu) { + newnode = mas_pop_node(mas); + } else { +- memset(&reuse, 0, sizeof(struct maple_node)); + newnode = &reuse; + } + +@@ -3193,7 +3199,21 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + dst_pivots[new_end] = mas->max; + + done: +- mas_leaf_set_meta(newnode, maple_leaf_64, new_end); ++ if (!in_rcu && new_end + 2 < node_slots) { ++ unsigned char clear_from = new_end + 1; ++ ++ /* ++ * Note that the last slot is never cleared, since the metadata ++ * will be stored there or it has a value. ++ */ ++ memset(dst_slots + clear_from, 0, ++ sizeof(void __rcu *) * (node_slots - clear_from)); ++ if (clear_from < node_pivots) ++ memset(dst_pivots + clear_from, 0, ++ sizeof(unsigned long) * (node_pivots - clear_from)); ++ } ++ ++ mas_leaf_set_meta(newnode, wr_mas->type, new_end); + if (in_rcu) { + struct maple_enode *old_enode = mas->node; + +@@ -3218,7 +3238,7 @@ static inline void mas_wr_slot_store(struct ma_wr_state *wr_mas) + void __rcu **slots = wr_mas->slots; + bool gap = false; + +- gap |= !mt_slot_locked(mas->tree, slots, offset); ++ gap |= !wr_mas->content; + gap |= !mt_slot_locked(mas->tree, slots, offset + 1); + + if (wr_mas->offset_end - offset == 1) { +@@ -3661,6 +3681,9 @@ static inline enum store_type mas_wr_store_type(struct ma_wr_state *wr_mas) + { + struct ma_state *mas = wr_mas->mas; + unsigned char new_end; ++ bool appending; ++ bool one_slot; ++ bool in_rcu; + + if (unlikely(mas_is_none(mas) || mas_is_ptr(mas))) + return wr_store_root; +@@ -3680,21 +3703,30 @@ static inline enum store_type mas_wr_store_type(struct ma_wr_state *wr_mas) + return wr_new_root; + + new_end = mas_wr_new_end(wr_mas); ++ in_rcu = mt_in_rcu(mas->tree); ++ appending = mas->offset == mas->end; ++ one_slot = wr_mas->offset_end - mas->offset == 1; ++ + /* Potential spanning rebalance collapsing a node */ + if (new_end < mt_min_slots[wr_mas->type]) { + if (!mte_is_root(mas->node)) + return wr_rebalance; ++ if (!in_rcu) { ++ if (appending) ++ return wr_append; ++ else if (mas->end == new_end && one_slot) ++ return wr_slot_store; ++ } + return wr_node_store; + } + + if (new_end >= mt_slots[wr_mas->type]) + return wr_split_store; + +- if (!mt_in_rcu(mas->tree) && (mas->offset == mas->end)) ++ if (!in_rcu && appending) + return wr_append; + +- if ((new_end == mas->end) && (!mt_in_rcu(mas->tree) || +- (wr_mas->offset_end - mas->offset == 1))) ++ if (new_end == mas->end && (!in_rcu || one_slot)) + return wr_slot_store; + + return wr_node_store; +@@ -3793,35 +3825,40 @@ int mas_alloc_cyclic(struct ma_state *mas, unsigned long *startp, + void *entry, unsigned long range_lo, unsigned long range_hi, + unsigned long *next, gfp_t gfp) + { +- unsigned long min = range_lo; +- int ret = 0; ++ int ret; ++ unsigned long min; ++ ++ min = range_lo; ++ do { ++ range_lo = max(min, *next); ++ ret = mas_empty_area(mas, range_lo, range_hi, 1); ++ if (ret < 0 && range_lo > min) { ++ mas_reset(mas); ++ ret = mas_empty_area(mas, min, range_hi, 1); ++ if (ret == 0) ++ ret = 1; ++ } ++ if (ret < 0) ++ goto out; ++ ++ mas_insert(mas, entry); ++ } while (mas_nomem(mas, gfp)); ++ ++ if (mas_is_err(mas)) { ++ ret = xa_err(mas->node); ++ goto out; ++ } + +- range_lo = max(min, *next); +- ret = mas_empty_area(mas, range_lo, range_hi, 1); + if ((mas->tree->ma_flags & MT_FLAGS_ALLOC_WRAPPED) && ret == 0) { + mas->tree->ma_flags &= ~MT_FLAGS_ALLOC_WRAPPED; + ret = 1; + } +- if (ret < 0 && range_lo > min) { +- mas_reset(mas); +- ret = mas_empty_area(mas, min, range_hi, 1); +- if (ret == 0) +- ret = 1; +- } +- if (ret < 0) +- return ret; +- +- do { +- mas_insert(mas, entry); +- } while (mas_nomem(mas, gfp)); +- if (mas_is_err(mas)) +- return xa_err(mas->node); +- + *startp = mas->index; + *next = *startp + 1; + if (*next == 0) + mas->tree->ma_flags |= MT_FLAGS_ALLOC_WRAPPED; + ++out: + mas_destroy(mas); + return ret; + } +diff --git a/mm/khugepaged.c b/mm/khugepaged.c +--- a/mm/khugepaged.c ++++ b/mm/khugepaged.c +@@ -1442,10 +1442,10 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s + + result = SCAN_SUCCEED; + out_up_write: +- if (anon_vma_locked) +- anon_vma_unlock_write(vma->anon_vma); + if (pte) + pte_unmap(pte); ++ if (anon_vma_locked) ++ anon_vma_unlock_write(vma->anon_vma); + mmap_write_unlock(mm); + out_nolock: + if (folio) +diff --git a/mm/ksm.c b/mm/ksm.c +--- a/mm/ksm.c ++++ b/mm/ksm.c +@@ -3061,10 +3061,9 @@ int __ksm_enter(struct mm_struct *mm) + + slot = &mm_slot->slot; + ++ spin_lock(&ksm_mmlist_lock); + /* Check ksm_run too? Would need tighter locking */ + needs_wakeup = list_empty(&ksm_mm_head.slot.mm_node); +- +- spin_lock(&ksm_mmlist_lock); + mm_slot_insert(mm_slots_hash, mm, slot); + /* + * When KSM_RUN_MERGE (or KSM_RUN_STOP), +diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c +--- a/mm/memcontrol-v1.c ++++ b/mm/memcontrol-v1.c +@@ -1998,8 +1998,8 @@ void memcg1_stat_format(struct mem_cgroup *memcg, struct seq_buf *s) + for_each_online_pgdat(pgdat) { + mz = memcg->nodeinfo[pgdat->node_id]; + +- anon_cost += mz->lruvec.anon_cost; +- file_cost += mz->lruvec.file_cost; ++ anon_cost += mz->lruvec.cost[WORKINGSET_ANON].count; ++ file_cost += mz->lruvec.cost[WORKINGSET_FILE].count; + } + seq_buf_printf(s, "anon_cost %lu\n", anon_cost); + seq_buf_printf(s, "file_cost %lu\n", file_cost); +diff --git a/mm/memcontrol.c b/mm/memcontrol.c +--- a/mm/memcontrol.c ++++ b/mm/memcontrol.c +@@ -393,6 +393,7 @@ static const unsigned int memcg_node_stat_items[] = { + NR_SHMEM_THPS, + NR_FILE_THPS, + NR_ANON_THPS, ++ NR_VMSCAN_WRITE, + NR_VMALLOC, + NR_KERNEL_STACK_KB, + NR_PAGETABLE, +@@ -419,6 +420,8 @@ static const unsigned int memcg_node_stat_items[] = { + PGSCAN_PROACTIVE, + PGSCAN_ANON, + PGSCAN_FILE, ++ PGROTATE_ANON, ++ PGROTATE_FILE, + PGREFILL, + #ifdef CONFIG_HUGETLB_PAGE + NR_HUGETLB, +@@ -502,6 +505,42 @@ unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx) + return x; + } + ++/** ++ * lruvec_page_state_monotonic - non-clamping lruvec stat read for delta sampling ++ * @lruvec: the LRU vector to read from ++ * @idx: the node_stat_item to read ++ * ++ * Returns the raw state[idx] value cast to unsigned long, skipping the ++ * clamp-negative-to-zero step in lruvec_page_state(). Intended for callers ++ * that snapshot a monotonically-incremented counter and subtract two ++ * samples: unsigned modular arithmetic then yields the correct delta across ++ * a signed-long wraparound (a real hazard on 32-bit) that the clamp would ++ * otherwise turn into a huge spurious delta. ++ * ++ * Do NOT use for non-monotonic page-count reads where a transient negative ++ * reading from per-CPU delta skew must present as zero. ++ * ++ * XXX: This helper (and its node/global peers) exists because some ++ * monotonically-incremented event counters are stored in ++ * enum node_stat_item. ++ */ ++unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec, ++ enum node_stat_item idx) ++{ ++ struct mem_cgroup_per_node *pn; ++ int i; ++ ++ if (mem_cgroup_disabled()) ++ return node_page_state_monotonic(lruvec_pgdat(lruvec), idx); ++ ++ i = memcg_stats_index(idx); ++ if (WARN_ONCE(BAD_STAT_IDX(i), "%s: missing stat item %d\n", __func__, idx)) ++ return 0; ++ ++ pn = container_of(lruvec, struct mem_cgroup_per_node, lruvec); ++ return (unsigned long)READ_ONCE(pn->lruvec_stats->state[i]); ++} ++ + unsigned long lruvec_page_state_local(struct lruvec *lruvec, + enum node_stat_item idx) + { +@@ -2095,7 +2134,12 @@ static bool consume_stock(struct mem_cgroup *memcg, unsigned int nr_pages) + + stock_pages = READ_ONCE(stock->nr_pages[i]); + if (stock_pages >= nr_pages) { +- WRITE_ONCE(stock->nr_pages[i], stock_pages - nr_pages); ++ stock_pages -= nr_pages; ++ WRITE_ONCE(stock->nr_pages[i], stock_pages); ++ if (!stock_pages) { ++ css_put(&memcg->css); ++ WRITE_ONCE(stock->cached[i], NULL); ++ } + ret = true; + } + break; +diff --git a/mm/migrate_device.c b/mm/migrate_device.c +--- a/mm/migrate_device.c ++++ b/mm/migrate_device.c +@@ -872,7 +872,7 @@ static int migrate_vma_insert_huge_pmd_page(struct migrate_vma *migrate, + + if (flush) { + pte_free(vma->vm_mm, pgtable); +- flush_cache_page(vma, addr, addr + HPAGE_PMD_SIZE); ++ flush_cache_range(vma, addr, addr + HPAGE_PMD_SIZE); + pmdp_invalidate(vma, addr, pmdp); + } else { + pgtable_trans_huge_deposit(vma->vm_mm, pmdp, pgtable); +diff --git a/mm/mmzone.c b/mm/mmzone.c +--- a/mm/mmzone.c ++++ b/mm/mmzone.c +@@ -78,6 +78,7 @@ void lruvec_init(struct lruvec *lruvec) + + memset(lruvec, 0, sizeof(struct lruvec)); + spin_lock_init(&lruvec->lru_lock); ++ spin_lock_init(&lruvec->cost_lock); + zswap_lruvec_state_init(lruvec); + + for_each_lru(lru) +diff --git a/mm/mremap.c b/mm/mremap.c +--- a/mm/mremap.c ++++ b/mm/mremap.c +@@ -264,7 +264,7 @@ static int move_ptes(struct pagetable_move_control *pmc, + + for (; old_addr < old_end; old_ptep += nr_ptes, old_addr += nr_ptes * PAGE_SIZE, + new_ptep += nr_ptes, new_addr += nr_ptes * PAGE_SIZE) { +- VM_WARN_ON_ONCE(!pte_none(*new_ptep)); ++ VM_WARN_ON_ONCE(!pte_none(ptep_get(new_ptep))); + + nr_ptes = 1; + max_nr_ptes = (old_end - old_addr) >> PAGE_SHIFT; +diff --git a/mm/shmem.c b/mm/shmem.c +--- a/mm/shmem.c ++++ b/mm/shmem.c +@@ -3612,6 +3612,7 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset, + struct shmem_inode_info *info = SHMEM_I(inode); + struct shmem_falloc shmem_falloc; + pgoff_t start, index, end, undo_fallocend; ++ loff_t aligned_end; + int error; + + if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE)) +@@ -3668,8 +3669,15 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset, + goto out; + } + ++ /* Check for wraparound */ ++ if (check_add_overflow(offset + len, (loff_t)PAGE_SIZE - 1, ++ &aligned_end)) { ++ error = -EFBIG; ++ goto out; ++ } ++ + start = offset >> PAGE_SHIFT; +- end = (offset + len + PAGE_SIZE - 1) >> PAGE_SHIFT; ++ end = aligned_end >> PAGE_SHIFT; + /* Try to avoid a swapstorm if len is impossible to satisfy */ + if (sbinfo->max_blocks && end - start > sbinfo->max_blocks) { + error = -ENOSPC; +diff --git a/mm/swap.c b/mm/swap.c +--- a/mm/swap.c ++++ b/mm/swap.c +@@ -272,73 +272,6 @@ void folio_rotate_reclaimable(struct folio *folio) + folio_batch_add_and_move(folio, lru_move_tail); + } + +-void lru_note_cost_unlock_irq(struct lruvec *lruvec, bool file, +- unsigned int nr_io, unsigned int nr_rotated) +- __releases(lruvec->lru_lock) +- __releases(rcu) +-{ +- unsigned long cost; +- +- /* +- * Reflect the relative cost of incurring IO and spending CPU +- * time on rotations. This doesn't attempt to make a precise +- * comparison, it just says: if reloads are about comparable +- * between the LRU lists, or rotations are overwhelmingly +- * different between them, adjust scan balance for CPU work. +- */ +- cost = nr_io * SWAP_CLUSTER_MAX + nr_rotated; +- if (!cost) { +- spin_unlock_irq(&lruvec->lru_lock); +- rcu_read_unlock(); +- return; +- } +- +- for (;;) { +- unsigned long lrusize; +- +- /* Record cost event */ +- if (file) +- lruvec->file_cost += cost; +- else +- lruvec->anon_cost += cost; +- +- /* +- * Decay previous events +- * +- * Because workloads change over time (and to avoid +- * overflow) we keep these statistics as a floating +- * average, which ends up weighing recent refaults +- * more than old ones. +- */ +- lrusize = lruvec_page_state(lruvec, NR_INACTIVE_ANON) + +- lruvec_page_state(lruvec, NR_ACTIVE_ANON) + +- lruvec_page_state(lruvec, NR_INACTIVE_FILE) + +- lruvec_page_state(lruvec, NR_ACTIVE_FILE); +- +- if (lruvec->file_cost + lruvec->anon_cost > lrusize / 4) { +- lruvec->file_cost /= 2; +- lruvec->anon_cost /= 2; +- } +- +- spin_unlock_irq(&lruvec->lru_lock); +- lruvec = parent_lruvec(lruvec); +- if (!lruvec) { +- rcu_read_unlock(); +- break; +- } +- spin_lock_irq(&lruvec->lru_lock); +- } +-} +- +-void lru_note_cost_refault(struct folio *folio) +-{ +- struct lruvec *lruvec; +- +- lruvec = folio_lruvec_lock_irq(folio); +- lru_note_cost_unlock_irq(lruvec, folio_is_file_lru(folio), +- folio_nr_pages(folio), 0); +-} +- + static void lru_activate(struct lruvec *lruvec, struct folio *folio) + { + long nr_pages = folio_nr_pages(folio); +@@ -1153,7 +1086,16 @@ static void lruvec_reparent_lru(struct lruvec *child_lruvec, + for_each_managed_zone_pgdat(zone, NODE_DATA(nid), zid, MAX_NR_ZONES - 1) { + unsigned long size = mem_cgroup_get_zone_lru_size(child_lruvec, lru, zid); + ++ if (!size) ++ continue; ++ ++ /* ++ * The folios are accounted to the parent from now on, so the ++ * size has to be moved, not just copied. Leaving it behind ++ * makes the dying child describe folios it no longer owns. ++ */ + mem_cgroup_update_lru_size(parent_lruvec, lru, zid, size); ++ mem_cgroup_update_lru_size(child_lruvec, lru, zid, -(long)size); + } + } + +@@ -1164,8 +1106,6 @@ void lru_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, int + + child_lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(nid)); + parent_lruvec = mem_cgroup_lruvec(parent, NODE_DATA(nid)); +- parent_lruvec->anon_cost += child_lruvec->anon_cost; +- parent_lruvec->file_cost += child_lruvec->file_cost; + + for_each_lru(lru) + lruvec_reparent_lru(child_lruvec, parent_lruvec, lru, nid); +diff --git a/mm/vma.c b/mm/vma.c +--- a/mm/vma.c ++++ b/mm/vma.c +@@ -1866,10 +1866,10 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, + bool *need_rmap_locks) + { + struct vm_area_struct *vma = *vmap; +- unsigned long vma_start = vma->vm_start; ++ unsigned long old_vma_start = vma->vm_start; + struct mm_struct *mm = vma->vm_mm; + struct vm_area_struct *new_vma; +- bool faulted_in_anon_vma = true; ++ bool can_self_merge = false; + VMA_ITERATOR(vmi, mm, addr); + VMG_VMA_STATE(vmg, &vmi, NULL, vma, addr, addr + len); + +@@ -1879,7 +1879,7 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, + */ + if (unlikely(vma_is_anonymous(vma) && !vma->anon_vma)) { + pgoff = addr >> PAGE_SHIFT; +- faulted_in_anon_vma = false; ++ can_self_merge = true; + } + + /* +@@ -1899,24 +1899,21 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, + new_vma = vma_merge_copied_range(&vmg); + + if (new_vma) { +- /* +- * Source vma may have been merged into new_vma +- */ +- if (unlikely(vma_start >= new_vma->vm_start && +- vma_start < new_vma->vm_end)) { ++ /* Self-merged and VMA replaced. */ ++ if (unlikely(new_vma->vm_start < old_vma_start && ++ new_vma->vm_end > old_vma_start)) { + /* +- * The only way we can get a vma_merge with +- * self during an mremap is if the vma hasn't +- * been faulted in yet and we were allowed to +- * reset the dst vma->vm_pgoff to the +- * destination address of the mremap to allow +- * the merge to happen. mremap must change the +- * vm_pgoff linearity between src and dst vmas +- * (in turn preventing a vma_merge) to be +- * safe. It is only safe to keep the vm_pgoff +- * linear if there are no pages mapped yet. ++ * The only way a VMA can both self-merge and be ++ * replaced is if the remap places the new VMA ++ * immediately prior to its old self ('next') and ++ * immediately after another VMA ('prev') causing the ++ * next to be removed and prev to be expanded to cover ++ * the entire range. ++ * ++ * This should only be possible if the page offset was ++ * updated, i.e. the VMA is unfaulted. + */ +- VM_BUG_ON_VMA(faulted_in_anon_vma, new_vma); ++ VM_WARN_ON_ONCE_VMA(!can_self_merge, new_vma); + *vmap = vma = new_vma; + } + *need_rmap_locks = (new_vma->vm_pgoff <= vma->vm_pgoff); +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -669,7 +669,7 @@ static pageout_t pageout(struct folio *folio, struct address_space *mapping, + folio_clear_reclaim(folio); + + trace_mm_vmscan_write_folio(folio); +- node_stat_add_folio(folio, NR_VMSCAN_WRITE); ++ lruvec_stat_mod_folio(folio, NR_VMSCAN_WRITE, folio_nr_pages(folio)); + return PAGE_SUCCESS; + } + +@@ -1420,8 +1420,6 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, + sc->nr_scanned -= (nr_pages - 1); + nr_pages = 1; + } +- stat->nr_pageout += nr_pages; +- + if (folio_test_writeback(folio)) + goto keep; + if (folio_test_dirty(folio)) +@@ -2045,10 +2043,10 @@ static unsigned long shrink_inactive_list(unsigned long nr_to_scan, + item = PGSTEAL_KSWAPD + reclaimer_offset(sc); + mod_lruvec_state(lruvec, item, nr_reclaimed); + mod_lruvec_state(lruvec, PGSTEAL_ANON + file, nr_reclaimed); ++ if (nr_scanned > nr_reclaimed) ++ mod_lruvec_state(lruvec, PGROTATE_ANON + file, ++ nr_scanned - nr_reclaimed); + +- lruvec_lock_irq(lruvec); +- lru_note_cost_unlock_irq(lruvec, file, stat.nr_pageout, +- nr_scanned - nr_reclaimed); + handle_reclaim_writeback(nr_taken, pgdat, sc, &stat); + trace_mm_vmscan_lru_shrink_inactive(pgdat->node_id, + nr_scanned, nr_reclaimed, &stat, sc->priority, file); +@@ -2154,9 +2152,9 @@ static void shrink_active_list(unsigned long nr_to_scan, + count_vm_events(PGDEACTIVATE, nr_deactivate); + count_memcg_events(lruvec_memcg(lruvec), PGDEACTIVATE, nr_deactivate); + mod_node_page_state(pgdat, NR_ISOLATED_ANON + file, -nr_taken); ++ if (nr_rotated) ++ mod_lruvec_state(lruvec, PGROTATE_ANON + file, nr_rotated); + +- lruvec_lock_irq(lruvec); +- lru_note_cost_unlock_irq(lruvec, file, 0, nr_rotated); + trace_mm_vmscan_lru_shrink_active(pgdat->node_id, nr_taken, nr_activate, + nr_deactivate, nr_rotated, sc->priority, file); + } +@@ -2289,8 +2287,10 @@ enum scan_balance { + + static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc) + { +- unsigned long file; ++ struct lru_cost *anon_cost, *file_cost; + struct lruvec *target_lruvec; ++ unsigned long lrusize; ++ unsigned long file; + + if (lru_gen_enabled() && !lru_gen_switching()) + return; +@@ -2306,11 +2306,69 @@ static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc) + + /* + * Determine the scan balance between anon and file LRUs. ++ * ++ * The cost model is based on rotations, refaults and ++ * reclaim-driven writes (anon only) on each side. ++ * ++ * These event counters are monotonic, so each reclaim cycle ++ * the delta since the last scan is extracted and incorporated ++ * into a decaying average. This ensures currency, as workloads ++ * change over time, and avoids overflow in the calculations. ++ * ++ * Use lruvec_page_state_monotonic() so unsigned subtraction ++ * yields the correct delta across a signed-long wraparound of ++ * the underlying counter (a real hazard on 32-bit that the ++ * clamp in lruvec_page_state() would otherwise turn into a huge ++ * spurious delta). + */ +- spin_lock_irq(&target_lruvec->lru_lock); +- sc->anon_cost = target_lruvec->anon_cost; +- sc->file_cost = target_lruvec->file_cost; +- spin_unlock_irq(&target_lruvec->lru_lock); ++ spin_lock(&target_lruvec->cost_lock); ++ ++ for (int f = 0; f <= 1; f++) { ++ struct lru_cost *cost = &target_lruvec->cost[f]; ++ unsigned long rotated, io, nr_rotated, nr_io; ++ ++ rotated = lruvec_page_state_monotonic(target_lruvec, ++ PGROTATE_ANON + f); ++ io = lruvec_page_state_monotonic(target_lruvec, ++ WORKINGSET_RESTORE_BASE + f); ++ if (f == WORKINGSET_ANON) ++ io += lruvec_page_state_monotonic(target_lruvec, ++ NR_VMSCAN_WRITE); ++ ++ nr_rotated = rotated - cost->last_rotated; ++ nr_io = io - cost->last_io; ++ ++ /* ++ * Reflect the relative cost of incurring IO and spending ++ * CPU time on rotations. This doesn't attempt to make a ++ * precise comparison, it just says: if reloads are about ++ * comparable between the LRU lists, or rotations are ++ * overwhelmingly different between them, adjust scan ++ * balance for CPU work. ++ */ ++ cost->count += nr_io * SWAP_CLUSTER_MAX + nr_rotated; ++ ++ cost->last_rotated = rotated; ++ cost->last_io = io; ++ } ++ ++ anon_cost = &target_lruvec->cost[WORKINGSET_ANON]; ++ file_cost = &target_lruvec->cost[WORKINGSET_FILE]; ++ ++ lrusize = lruvec_page_state(target_lruvec, NR_INACTIVE_ANON) + ++ lruvec_page_state(target_lruvec, NR_ACTIVE_ANON) + ++ lruvec_page_state(target_lruvec, NR_INACTIVE_FILE) + ++ lruvec_page_state(target_lruvec, NR_ACTIVE_FILE); ++ ++ while (anon_cost->count + file_cost->count > lrusize / 4) { ++ anon_cost->count /= 2; ++ file_cost->count /= 2; ++ } ++ ++ sc->anon_cost = anon_cost->count; ++ sc->file_cost = file_cost->count; ++ ++ spin_unlock(&target_lruvec->cost_lock); + + /* + * Target desirable inactive:active list ratios for the anon +@@ -4197,7 +4255,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr) + unsigned long end; + struct lru_gen_mm_walk *walk; + struct folio *last = NULL; +- int young = 1; ++ int young = nr; + pte_t *pte = pvmw->pte; + unsigned long addr = pvmw->address; + struct vm_area_struct *vma = pvmw->vma; +@@ -4571,7 +4629,12 @@ void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, + for_each_managed_zone_pgdat(zone, NODE_DATA(nid), zid, MAX_NR_ZONES - 1) { + unsigned long size = mem_cgroup_get_zone_lru_size(child_lruvec, lru, zid); + ++ if (!size) ++ continue; ++ ++ /* Move the accounting, do not duplicate it. */ + mem_cgroup_update_lru_size(parent_lruvec, lru, zid, size); ++ mem_cgroup_update_lru_size(child_lruvec, lru, zid, -(long)size); + } + } + } +@@ -4814,7 +4877,8 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + struct reclaim_stat stat; + struct lru_gen_mm_walk *walk; + int scanned, reclaimed; +- int isolated = 0, type, type_scanned; ++ int isolated = 0, nr_isolated = 0, type, type_scanned; ++ unsigned long total_reclaimed = 0; + bool skip_retry = false; + struct mem_cgroup *memcg = lruvec_memcg(lruvec); + struct pglist_data *pgdat = lruvec_pgdat(lruvec); +@@ -4826,6 +4890,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + + scanned = isolate_folios(nr_to_scan, lruvec, sc, swappiness, + &list, &isolated, &type, &type_scanned); ++ nr_isolated = isolated; + + /* Scanning may have emptied the oldest gen, flush it */ + if (scanned) +@@ -4838,6 +4903,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + retry: + reclaimed = shrink_folio_list(&list, pgdat, sc, &stat, false, memcg); + sc->nr_reclaimed += reclaimed; ++ total_reclaimed += reclaimed; + /* Retry pass is only meant for clean folios without new isolation */ + if (isolated) + handle_reclaim_writeback(isolated, pgdat, sc, &stat); +@@ -4887,6 +4953,10 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + goto retry; + } + ++ if (nr_isolated > total_reclaimed) ++ mod_lruvec_state(lruvec, PGROTATE_ANON + type, ++ nr_isolated - total_reclaimed); ++ + return scanned; + } + +diff --git a/mm/vmstat.c b/mm/vmstat.c +--- a/mm/vmstat.c ++++ b/mm/vmstat.c +@@ -1024,6 +1024,17 @@ unsigned long node_page_state(struct pglist_data *pgdat, + + return node_page_state_pages(pgdat, item); + } ++ ++/* ++ * Non-clamping variant of node_page_state() intended for callers that ++ * snapshot a monotonically-incremented counter and subtract two samples. ++ * See global_node_page_state_monotonic() for the rationale. ++ */ ++unsigned long node_page_state_monotonic(struct pglist_data *pgdat, ++ enum node_stat_item item) ++{ ++ return (unsigned long)atomic_long_read(&pgdat->vm_stat[item]); ++} + #endif + + /* +@@ -1289,6 +1300,8 @@ const char * const vmstat_text[] = { + [I(PGSCAN_PROACTIVE)] = "pgscan_proactive", + [I(PGSCAN_ANON)] = "pgscan_anon", + [I(PGSCAN_FILE)] = "pgscan_file", ++ [I(PGROTATE_ANON)] = "pgrotate_anon", ++ [I(PGROTATE_FILE)] = "pgrotate_file", + [I(PGREFILL)] = "pgrefill", + #ifdef CONFIG_HUGETLB_PAGE + [I(NR_HUGETLB)] = "nr_hugetlb", +diff --git a/mm/workingset.c b/mm/workingset.c +--- a/mm/workingset.c ++++ b/mm/workingset.c +@@ -584,11 +584,6 @@ void workingset_refault(struct folio *folio, void *shadow) + /* Folio was active prior to eviction */ + if (workingset) { + folio_set_workingset(folio); +- /* +- * XXX: Move to folio_add_lru() when it supports new vs +- * putback +- */ +- lru_note_cost_refault(folio); + mod_lruvec_state(lruvec, WORKINGSET_RESTORE_BASE + file, nr); + } + out: +diff --git a/mm/zswap.c b/mm/zswap.c +--- a/mm/zswap.c ++++ b/mm/zswap.c +@@ -1217,7 +1217,7 @@ static unsigned long zswap_shrinker_count(struct shrinker *shrinker, + * Without memcg, use the zswap pool-wide metrics. + */ + if (!mem_cgroup_disabled()) { +- mem_cgroup_flush_stats(memcg); ++ mem_cgroup_flush_stats_ratelimited(memcg); + nr_backing = memcg_page_state(memcg, MEMCG_ZSWAP_B) >> PAGE_SHIFT; + nr_stored = memcg_page_state(memcg, MEMCG_ZSWAPPED); + } else { +@@ -1275,6 +1275,14 @@ static struct shrinker *zswap_alloc_shrinker(void) + return shrinker; + } + ++/* ++ * Scan up to SWAP_CLUSTER_MAX pages on each per-node zswap LRU of @memcg ++ * and write back the reclaimable ones. ++ * ++ * Return: 0 if at least one entry was written back, -EAGAIN if entries ++ * were scanned but none could be written back, or -ENOENT if @memcg has ++ * writeback disabled, is a zombie cgroup, or has empty zswap LRUs. ++ */ + static int shrink_memcg(struct mem_cgroup *memcg) + { + int nid, shrunk = 0, scanned = 0; +@@ -1290,13 +1298,14 @@ static int shrink_memcg(struct mem_cgroup *memcg) + return -ENOENT; + + for_each_node_state(nid, N_NORMAL_MEMORY) { +- unsigned long nr_to_walk = 1; ++ unsigned long nr_to_walk = SWAP_CLUSTER_MAX; + + shrunk += list_lru_walk_one(&zswap_list_lru, nid, memcg, + &shrink_memcg_cb, NULL, &nr_to_walk); +- scanned += 1 - nr_to_walk; ++ scanned += SWAP_CLUSTER_MAX - nr_to_walk; + } + ++ /* Nothing was scanned: every LRU under @memcg was empty. */ + if (!scanned) + return -ENOENT; + diff --git a/pkgbuilds/linux-omarchy/0290-zstd-bmi2-fallback-aliases.patch b/pkgbuilds/linux-omarchy/0290-zstd-bmi2-fallback-aliases.patch new file mode 100644 index 0000000..a14613c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0290-zstd-bmi2-fallback-aliases.patch @@ -0,0 +1,97 @@ +diff --git a/lib/zstd/common/entropy_common.c b/lib/zstd/common/entropy_common.c +--- a/lib/zstd/common/entropy_common.c ++++ b/lib/zstd/common/entropy_common.c +@@ -202,6 +202,8 @@ BMI2_TARGET_ATTRIBUTE static size_t FSE_readNCount_body_bmi2( + { + return FSE_readNCount_body(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize); + } ++#else ++#define FSE_readNCount_body_bmi2 FSE_readNCount_body_default + #endif + + size_t FSE_readNCount_bmi2( +@@ -323,6 +325,8 @@ static BMI2_TARGET_ATTRIBUTE size_t HUF_readStats_body_bmi2(BYTE* huffWeight, si + { + return HUF_readStats_body(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize, 1); + } ++#else ++#define HUF_readStats_body_bmi2 HUF_readStats_body_default + #endif + + size_t HUF_readStats_wksp(BYTE* huffWeight, size_t hwSize, U32* rankStats, +diff --git a/lib/zstd/common/fse_decompress.c b/lib/zstd/common/fse_decompress.c +--- a/lib/zstd/common/fse_decompress.c ++++ b/lib/zstd/common/fse_decompress.c +@@ -300,6 +300,8 @@ BMI2_TARGET_ATTRIBUTE static size_t FSE_decompress_wksp_body_bmi2(void* dst, siz + { + return FSE_decompress_wksp_body(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize, 1); + } ++#else ++#define FSE_decompress_wksp_body_bmi2 FSE_decompress_wksp_body_default + #endif + + size_t FSE_decompress_wksp_bmi2(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize, int bmi2) +diff --git a/lib/zstd/compress/zstd_compress_sequences.c b/lib/zstd/compress/zstd_compress_sequences.c +--- a/lib/zstd/compress/zstd_compress_sequences.c ++++ b/lib/zstd/compress/zstd_compress_sequences.c +@@ -415,6 +415,10 @@ ZSTD_encodeSequences_bmi2( + sequences, nbSeq, longOffsets); + } + ++#else ++ ++#define ZSTD_encodeSequences_bmi2 ZSTD_encodeSequences_default ++ + #endif + + size_t ZSTD_encodeSequences( +diff --git a/lib/zstd/decompress/huf_decompress.c b/lib/zstd/decompress/huf_decompress.c +--- a/lib/zstd/decompress/huf_decompress.c ++++ b/lib/zstd/decompress/huf_decompress.c +@@ -700,6 +700,8 @@ size_t HUF_decompress4X1_usingDTable_internal_bmi2(void* dst, size_t dstSize, vo + size_t cSrcSize, HUF_DTable const* DTable) { + return HUF_decompress4X1_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable); + } ++#else ++#define HUF_decompress4X1_usingDTable_internal_bmi2 HUF_decompress4X1_usingDTable_internal_default + #endif + + static +@@ -1503,6 +1505,8 @@ size_t HUF_decompress4X2_usingDTable_internal_bmi2(void* dst, size_t dstSize, vo + size_t cSrcSize, HUF_DTable const* DTable) { + return HUF_decompress4X2_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable); + } ++#else ++#define HUF_decompress4X2_usingDTable_internal_bmi2 HUF_decompress4X2_usingDTable_internal_default + #endif + + static +diff --git a/lib/zstd/decompress/zstd_decompress_block.c b/lib/zstd/decompress/zstd_decompress_block.c +--- a/lib/zstd/decompress/zstd_decompress_block.c ++++ b/lib/zstd/decompress/zstd_decompress_block.c +@@ -622,6 +622,8 @@ BMI2_TARGET_ATTRIBUTE static void ZSTD_buildFSETable_body_bmi2(ZSTD_seqSymbol* d + ZSTD_buildFSETable_body(dt, normalizedCounter, maxSymbolValue, + baseValue, nbAdditionalBits, tableLog, wksp, wkspSize); + } ++#else ++#define ZSTD_buildFSETable_body_bmi2 ZSTD_buildFSETable_body_default + #endif + + void ZSTD_buildFSETable(ZSTD_seqSymbol* dt, +@@ -1934,6 +1936,16 @@ ZSTD_decompressSequencesLong_bmi2(ZSTD_DCtx* dctx, + } + #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */ + ++#else ++ ++#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG ++#define ZSTD_decompressSequences_bmi2 ZSTD_decompressSequences_default ++#define ZSTD_decompressSequencesSplitLitBuffer_bmi2 ZSTD_decompressSequencesSplitLitBuffer_default ++#endif ++#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT ++#define ZSTD_decompressSequencesLong_bmi2 ZSTD_decompressSequencesLong_default ++#endif ++ + #endif /* DYNAMIC_BMI2 */ + + #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG diff --git a/pkgbuilds/linux-omarchy/0291-zstd-bmi2-cpu-feature-dispatch.patch b/pkgbuilds/linux-omarchy/0291-zstd-bmi2-cpu-feature-dispatch.patch new file mode 100644 index 0000000..9f0b872 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0291-zstd-bmi2-cpu-feature-dispatch.patch @@ -0,0 +1,413 @@ +diff --git a/lib/zstd/common/compiler.h b/lib/zstd/common/compiler.h +--- a/lib/zstd/common/compiler.h ++++ b/lib/zstd/common/compiler.h +@@ -14,6 +14,7 @@ + + #include + ++#include "zstd_deps.h" + #include "portability_macros.h" + + /*-******************************************************* +@@ -96,6 +97,17 @@ + */ + #define BMI2_TARGET_ATTRIBUTE TARGET_ATTRIBUTE("lzcnt,bmi,bmi2") + ++#if !DYNAMIC_BMI2 ++# define ZSTD_USE_BMI2(bmi2) 0 ++# define ZSTD_SET_BMI2(state, value) do { } while (0) ++#elif defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++# define ZSTD_USE_BMI2(bmi2) cpu_feature_enabled(X86_FEATURE_BMI2) ++# define ZSTD_SET_BMI2(state, value) do { } while (0) ++#else ++# define ZSTD_USE_BMI2(bmi2) (bmi2) ++# define ZSTD_SET_BMI2(state, value) do { (state) = (value); } while (0) ++#endif ++ + /* prefetch + * can be disabled, by declaring NO_PREFETCH build macro */ + #if ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) ) +diff --git a/lib/zstd/common/entropy_common.c b/lib/zstd/common/entropy_common.c +--- a/lib/zstd/common/entropy_common.c ++++ b/lib/zstd/common/entropy_common.c +@@ -16,6 +16,10 @@ + /* ************************************* + * Dependencies + ***************************************/ ++#include "zstd_deps.h" ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "mem.h" + #include "error_private.h" /* ERR_*, ERROR */ + #define FSE_STATIC_LINKING_ONLY /* FSE_MIN_TABLELOG */ +@@ -210,11 +214,9 @@ size_t FSE_readNCount_bmi2( + short* normalizedCounter, unsigned* maxSVPtr, unsigned* tableLogPtr, + const void* headerBuffer, size_t hbSize, int bmi2) + { +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + return FSE_readNCount_body_bmi2(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize); + } +-#endif + (void)bmi2; + return FSE_readNCount_body_default(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize); + } +@@ -335,11 +337,9 @@ size_t HUF_readStats_wksp(BYTE* huffWeight, size_t hwSize, U32* rankStats, + void* workSpace, size_t wkspSize, + int flags) + { +-#if DYNAMIC_BMI2 +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + return HUF_readStats_body_bmi2(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize); + } +-#endif + (void)flags; + return HUF_readStats_body_default(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize); + } +diff --git a/lib/zstd/common/fse_decompress.c b/lib/zstd/common/fse_decompress.c +--- a/lib/zstd/common/fse_decompress.c ++++ b/lib/zstd/common/fse_decompress.c +@@ -24,6 +24,9 @@ + #include "fse.h" + #include "error_private.h" + #include "zstd_deps.h" /* ZSTD_memcpy */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "bits.h" /* ZSTD_highbit32 */ + + +@@ -306,11 +309,9 @@ BMI2_TARGET_ATTRIBUTE static size_t FSE_decompress_wksp_body_bmi2(void* dst, siz + + size_t FSE_decompress_wksp_bmi2(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize, int bmi2) + { +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + return FSE_decompress_wksp_body_bmi2(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize); + } +-#endif + (void)bmi2; + return FSE_decompress_wksp_body_default(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize); + } +diff --git a/lib/zstd/common/zstd_deps.h b/lib/zstd/common/zstd_deps.h +--- a/lib/zstd/common/zstd_deps.h ++++ b/lib/zstd/common/zstd_deps.h +@@ -26,6 +26,11 @@ + #ifndef ZSTD_DEPS_COMMON + #define ZSTD_DEPS_COMMON + ++#if defined(__KERNEL__) && defined(CONFIG_X86) && \ ++ !defined(__DISABLE_EXPORTS) ++#define ZSTD_USE_KERNEL_CPU_FEATURES ++#endif ++ + #include + #include + +diff --git a/lib/zstd/compress/huf_compress.c b/lib/zstd/compress/huf_compress.c +--- a/lib/zstd/compress/huf_compress.c ++++ b/lib/zstd/compress/huf_compress.c +@@ -22,6 +22,9 @@ + * Includes + ****************************************************************/ + #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memset */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "../common/compiler.h" + #include "../common/bitstream.h" + #include "hist.h" +@@ -1138,9 +1141,10 @@ HUF_compress1X_usingCTable_internal(void* dst, size_t dstSize, + const void* src, size_t srcSize, + const HUF_CElt* CTable, const int flags) + { +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + return HUF_compress1X_usingCTable_internal_bmi2(dst, dstSize, src, srcSize, CTable); + } ++ (void)flags; + return HUF_compress1X_usingCTable_internal_default(dst, dstSize, src, srcSize, CTable); + } + +diff --git a/lib/zstd/compress/zstd_compress.c b/lib/zstd/compress/zstd_compress.c +--- a/lib/zstd/compress/zstd_compress.c ++++ b/lib/zstd/compress/zstd_compress.c +@@ -102,7 +102,7 @@ static void ZSTD_initCCtx(ZSTD_CCtx* cctx, ZSTD_customMem memManager) + assert(cctx != NULL); + ZSTD_memset(cctx, 0, sizeof(*cctx)); + cctx->customMem = memManager; +- cctx->bmi2 = ZSTD_cpuSupportsBmi2(); ++ ZSTD_SET_BMI2(cctx->bmi2, ZSTD_cpuSupportsBmi2()); + { size_t const err = ZSTD_CCtx_reset(cctx, ZSTD_reset_parameters); + assert(!ZSTD_isError(err)); + (void)err; +@@ -142,7 +142,7 @@ ZSTD_CCtx* ZSTD_initStaticCCtx(void* workspace, size_t workspaceSize) + cctx->blockState.nextCBlock = (ZSTD_compressedBlockState_t*)ZSTD_cwksp_reserve_object(&cctx->workspace, sizeof(ZSTD_compressedBlockState_t)); + cctx->tmpWorkspace = ZSTD_cwksp_reserve_object(&cctx->workspace, TMP_WORKSPACE_SIZE); + cctx->tmpWkspSize = TMP_WORKSPACE_SIZE; +- cctx->bmi2 = ZSTD_cpuid_bmi2(ZSTD_cpuid()); ++ ZSTD_SET_BMI2(cctx->bmi2, ZSTD_cpuid_bmi2(ZSTD_cpuid())); + return cctx; + } + +@@ -4042,7 +4042,7 @@ ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx* zc, + op + ZSTD_blockHeaderSize, dstCapacity - ZSTD_blockHeaderSize, + srcSize, + zc->tmpWorkspace, zc->tmpWkspSize /* statically allocated in resetCCtx */, +- zc->bmi2); ++ ZSTD_CCtx_get_bmi2(zc)); + FORWARD_IF_ERROR(cSeqsSize, "ZSTD_entropyCompressSeqStore failed!"); + + if (!zc->isFirstBlock && +@@ -4332,7 +4332,7 @@ ZSTD_compressBlock_internal(ZSTD_CCtx* zc, + dst, dstCapacity, + srcSize, + zc->tmpWorkspace, zc->tmpWkspSize /* statically allocated in resetCCtx */, +- zc->bmi2); ++ ZSTD_CCtx_get_bmi2(zc)); + + if (frame && + /* We don't want to emit our first block as a RLE even if it qualifies because +@@ -6796,7 +6796,7 @@ ZSTD_compressSequences_internal(ZSTD_CCtx* cctx, + op + ZSTD_blockHeaderSize /* Leave space for block header */, dstCapacity - ZSTD_blockHeaderSize, + blockSize, + cctx->tmpWorkspace, cctx->tmpWkspSize /* statically allocated in resetCCtx */, +- cctx->bmi2); ++ ZSTD_CCtx_get_bmi2(cctx)); + FORWARD_IF_ERROR(compressedSeqsSize, "Compressing sequences of block failed"); + DEBUGLOG(5, "Compressed sequences size: %zu", compressedSeqsSize); + +@@ -7321,7 +7321,7 @@ ZSTD_compressSequencesAndLiterals_internal(ZSTD_CCtx* cctx, + &cctx->blockState.prevCBlock->entropy, &cctx->blockState.nextCBlock->entropy, + &cctx->appliedParams, + cctx->tmpWorkspace, cctx->tmpWkspSize /* statically allocated in resetCCtx */, +- cctx->bmi2); ++ ZSTD_CCtx_get_bmi2(cctx)); + FORWARD_IF_ERROR(compressedSeqsSize, "Compressing sequences of block failed"); + /* note: the spec forbids for any compressed block to be larger than maximum block size */ + if (compressedSeqsSize > cctx->blockSizeMax) compressedSeqsSize = 0; +diff --git a/lib/zstd/compress/zstd_compress_internal.h b/lib/zstd/compress/zstd_compress_internal.h +--- a/lib/zstd/compress/zstd_compress_internal.h ++++ b/lib/zstd/compress/zstd_compress_internal.h +@@ -537,6 +537,15 @@ struct ZSTD_CCtx_s { + size_t extSeqBufCapacity; + }; + ++MEM_STATIC int ZSTD_CCtx_get_bmi2(const struct ZSTD_CCtx_s *cctx) { ++#if DYNAMIC_BMI2 && !defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++ return cctx->bmi2; ++#else ++ (void)cctx; ++ return 0; ++#endif ++} ++ + typedef enum { ZSTD_dtlm_fast, ZSTD_dtlm_full } ZSTD_dictTableLoadMethod_e; + typedef enum { ZSTD_tfp_forCCtx, ZSTD_tfp_forCDict } ZSTD_tableFillPurpose_e; + +diff --git a/lib/zstd/compress/zstd_compress_sequences.c b/lib/zstd/compress/zstd_compress_sequences.c +--- a/lib/zstd/compress/zstd_compress_sequences.c ++++ b/lib/zstd/compress/zstd_compress_sequences.c +@@ -12,6 +12,10 @@ + /*-************************************* + * Dependencies + ***************************************/ ++#include "../common/zstd_deps.h" ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "zstd_compress_sequences.h" + + /* +@@ -429,15 +433,13 @@ size_t ZSTD_encodeSequences( + SeqDef const* sequences, size_t nbSeq, int longOffsets, int bmi2) + { + DEBUGLOG(5, "ZSTD_encodeSequences: dstCapacity = %u", (unsigned)dstCapacity); +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + return ZSTD_encodeSequences_bmi2(dst, dstCapacity, + CTable_MatchLength, mlCodeTable, + CTable_OffsetBits, ofCodeTable, + CTable_LitLength, llCodeTable, + sequences, nbSeq, longOffsets); + } +-#endif + (void)bmi2; + return ZSTD_encodeSequences_default(dst, dstCapacity, + CTable_MatchLength, mlCodeTable, +diff --git a/lib/zstd/compress/zstd_compress_superblock.c b/lib/zstd/compress/zstd_compress_superblock.c +--- a/lib/zstd/compress/zstd_compress_superblock.c ++++ b/lib/zstd/compress/zstd_compress_superblock.c +@@ -684,6 +684,6 @@ size_t ZSTD_compressSuperBlock(ZSTD_CCtx* zc, + &zc->appliedParams, + dst, dstCapacity, + src, srcSize, +- zc->bmi2, lastBlock, ++ ZSTD_CCtx_get_bmi2(zc), lastBlock, + zc->tmpWorkspace, zc->tmpWkspSize /* statically allocated in resetCCtx */); + } +diff --git a/lib/zstd/decompress/huf_decompress.c b/lib/zstd/decompress/huf_decompress.c +--- a/lib/zstd/decompress/huf_decompress.c ++++ b/lib/zstd/decompress/huf_decompress.c +@@ -17,6 +17,9 @@ + * Dependencies + ****************************************************************/ + #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memset */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "../common/compiler.h" + #include "../common/bitstream.h" /* BIT_* */ + #include "../common/fse.h" /* to compress headers */ +@@ -113,9 +116,10 @@ typedef size_t (*HUF_DecompressUsingDTableFn)(void *dst, size_t dstSize, + static size_t fn(void* dst, size_t dstSize, void const* cSrc, \ + size_t cSrcSize, HUF_DTable const* DTable, int flags) \ + { \ +- if (flags & HUF_flags_bmi2) { \ ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { \ + return fn##_bmi2(dst, dstSize, cSrc, cSrcSize, DTable); \ + } \ ++ (void)flags; \ + return fn##_default(dst, dstSize, cSrc, cSrcSize, DTable); \ + } + +@@ -899,18 +903,16 @@ static size_t HUF_decompress4X1_usingDTable_internal(void* dst, size_t dstSize, + HUF_DecompressUsingDTableFn fallbackFn = HUF_decompress4X1_usingDTable_internal_default; + HUF_DecompressFastLoopFn loopFn = HUF_decompress4X1_usingDTable_internal_fast_c_loop; + +-#if DYNAMIC_BMI2 +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + fallbackFn = HUF_decompress4X1_usingDTable_internal_bmi2; + # if ZSTD_ENABLE_ASM_X86_64_BMI2 + if (!(flags & HUF_flags_disableAsm)) { + loopFn = HUF_decompress4X1_usingDTable_internal_fast_asm_loop; + } + # endif +- } else { ++ } else if (DYNAMIC_BMI2) { + return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); + } +-#endif + + #if ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__) + if (!(flags & HUF_flags_disableAsm)) { +@@ -1723,18 +1725,16 @@ static size_t HUF_decompress4X2_usingDTable_internal(void* dst, size_t dstSize, + HUF_DecompressUsingDTableFn fallbackFn = HUF_decompress4X2_usingDTable_internal_default; + HUF_DecompressFastLoopFn loopFn = HUF_decompress4X2_usingDTable_internal_fast_c_loop; + +-#if DYNAMIC_BMI2 +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + fallbackFn = HUF_decompress4X2_usingDTable_internal_bmi2; + # if ZSTD_ENABLE_ASM_X86_64_BMI2 + if (!(flags & HUF_flags_disableAsm)) { + loopFn = HUF_decompress4X2_usingDTable_internal_fast_asm_loop; + } + # endif +- } else { ++ } else if (DYNAMIC_BMI2) { + return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); + } +-#endif + + #if ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__) + if (!(flags & HUF_flags_disableAsm)) { +diff --git a/lib/zstd/decompress/zstd_decompress.c b/lib/zstd/decompress/zstd_decompress.c +--- a/lib/zstd/decompress/zstd_decompress.c ++++ b/lib/zstd/decompress/zstd_decompress.c +@@ -259,9 +259,7 @@ static void ZSTD_initDCtx_internal(ZSTD_DCtx* dctx) + dctx->noForwardProgress = 0; + dctx->oversizedDuration = 0; + dctx->isFrameDecompression = 1; +-#if DYNAMIC_BMI2 +- dctx->bmi2 = ZSTD_cpuSupportsBmi2(); +-#endif ++ ZSTD_SET_BMI2(dctx->bmi2, ZSTD_cpuSupportsBmi2()); + dctx->ddictSet = NULL; + ZSTD_DCtx_resetParameters(dctx); + #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +diff --git a/lib/zstd/decompress/zstd_decompress_block.c b/lib/zstd/decompress/zstd_decompress_block.c +--- a/lib/zstd/decompress/zstd_decompress_block.c ++++ b/lib/zstd/decompress/zstd_decompress_block.c +@@ -16,6 +16,9 @@ + * Dependencies + *********************************************************/ + #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memmove, ZSTD_memset */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "../common/compiler.h" /* prefetch */ + #include "../common/cpu.h" /* bmi2 */ + #include "../common/mem.h" /* low level memory routines */ +@@ -631,13 +634,11 @@ void ZSTD_buildFSETable(ZSTD_seqSymbol* dt, + const U32* baseValue, const U8* nbAdditionalBits, + unsigned tableLog, void* wksp, size_t wkspSize, int bmi2) + { +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + ZSTD_buildFSETable_body_bmi2(dt, normalizedCounter, maxSymbolValue, + baseValue, nbAdditionalBits, tableLog, wksp, wkspSize); + return; + } +-#endif + (void)bmi2; + ZSTD_buildFSETable_body_default(dt, normalizedCounter, maxSymbolValue, + baseValue, nbAdditionalBits, tableLog, wksp, wkspSize); +@@ -1955,11 +1956,9 @@ ZSTD_decompressSequences(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, + const ZSTD_longOffset_e isLongOffset) + { + DEBUGLOG(5, "ZSTD_decompressSequences"); +-#if DYNAMIC_BMI2 +- if (ZSTD_DCtx_get_bmi2(dctx)) { ++ if (ZSTD_USE_BMI2(ZSTD_DCtx_get_bmi2(dctx))) { + return ZSTD_decompressSequences_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } +-#endif + return ZSTD_decompressSequences_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } + static size_t +@@ -1968,11 +1967,9 @@ ZSTD_decompressSequencesSplitLitBuffer(ZSTD_DCtx* dctx, void* dst, size_t maxDst + const ZSTD_longOffset_e isLongOffset) + { + DEBUGLOG(5, "ZSTD_decompressSequencesSplitLitBuffer"); +-#if DYNAMIC_BMI2 +- if (ZSTD_DCtx_get_bmi2(dctx)) { ++ if (ZSTD_USE_BMI2(ZSTD_DCtx_get_bmi2(dctx))) { + return ZSTD_decompressSequencesSplitLitBuffer_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } +-#endif + return ZSTD_decompressSequencesSplitLitBuffer_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } + #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG */ +@@ -1991,11 +1988,9 @@ ZSTD_decompressSequencesLong(ZSTD_DCtx* dctx, + const ZSTD_longOffset_e isLongOffset) + { + DEBUGLOG(5, "ZSTD_decompressSequencesLong"); +-#if DYNAMIC_BMI2 +- if (ZSTD_DCtx_get_bmi2(dctx)) { ++ if (ZSTD_USE_BMI2(ZSTD_DCtx_get_bmi2(dctx))) { + return ZSTD_decompressSequencesLong_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } +-#endif + return ZSTD_decompressSequencesLong_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } + #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */ +diff --git a/lib/zstd/decompress/zstd_decompress_internal.h b/lib/zstd/decompress/zstd_decompress_internal.h +--- a/lib/zstd/decompress/zstd_decompress_internal.h ++++ b/lib/zstd/decompress/zstd_decompress_internal.h +@@ -204,7 +204,7 @@ struct ZSTD_DCtx_s + }; /* typedef'd to ZSTD_DCtx within "zstd.h" */ + + MEM_STATIC int ZSTD_DCtx_get_bmi2(const struct ZSTD_DCtx_s *dctx) { +-#if DYNAMIC_BMI2 ++#if DYNAMIC_BMI2 && !defined(ZSTD_USE_KERNEL_CPU_FEATURES) + return dctx->bmi2; + #else + (void)dctx; diff --git a/pkgbuilds/linux-omarchy/0292-crypto-zstd-defer-cstream-init.patch b/pkgbuilds/linux-omarchy/0292-crypto-zstd-defer-cstream-init.patch new file mode 100644 index 0000000..4007e61 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0292-crypto-zstd-defer-cstream-init.patch @@ -0,0 +1,44 @@ +diff --git a/crypto/zstd.c b/crypto/zstd.c +--- a/crypto/zstd.c ++++ b/crypto/zstd.c +@@ -96,6 +96,7 @@ static int zstd_compress_one(struct acomp_req *req, struct zstd_ctx *ctx, + + static int zstd_compress(struct acomp_req *req) + { ++ bool stream_initialized = false; + struct crypto_acomp_stream *s; + unsigned int pos, scur, dcur; + unsigned int total_out = 0; +@@ -115,12 +116,6 @@ static int zstd_compress(struct acomp_req *req) + if (ret) + goto out; + +- ctx->cctx = zstd_init_cstream(&ctx->params, 0, ctx->wksp, ctx->wksp_size); +- if (!ctx->cctx) { +- ret = -EINVAL; +- goto out; +- } +- + do { + dcur = acomp_walk_next_dst(&walk); + if (!dcur) { +@@ -142,6 +137,19 @@ static int zstd_compress(struct acomp_req *req) + goto out; + } + ++ if (!stream_initialized) { ++ ctx->cctx = zstd_init_cstream(&ctx->params, 0, ++ ctx->wksp, ctx->wksp_size); ++ if (!ctx->cctx) { ++ /* Release in the reverse of the map order. */ ++ acomp_walk_done_src(&walk, 0); ++ acomp_walk_done_dst(&walk, 0); ++ ret = -EINVAL; ++ goto out; ++ } ++ stream_initialized = true; ++ } ++ + if (scur) { + inbuf.pos = 0; + inbuf.src = walk.src.virt.addr; diff --git a/pkgbuilds/linux-omarchy/0293-crypto-zstd-defer-dstream-init.patch b/pkgbuilds/linux-omarchy/0293-crypto-zstd-defer-dstream-init.patch new file mode 100644 index 0000000..224f1ad --- /dev/null +++ b/pkgbuilds/linux-omarchy/0293-crypto-zstd-defer-dstream-init.patch @@ -0,0 +1,44 @@ +diff --git a/crypto/zstd.c b/crypto/zstd.c +--- a/crypto/zstd.c ++++ b/crypto/zstd.c +@@ -215,6 +215,7 @@ static int zstd_decompress_one(struct acomp_req *req, struct zstd_ctx *ctx, + + static int zstd_decompress(struct acomp_req *req) + { ++ bool stream_initialized = false; + struct crypto_acomp_stream *s; + unsigned int total_out = 0; + unsigned int scur, dcur; +@@ -232,12 +233,6 @@ static int zstd_decompress(struct acomp_req *req) + if (ret) + goto out; + +- ctx->dctx = zstd_init_dstream(ZSTD_MAX_SIZE, ctx->wksp, ctx->wksp_size); +- if (!ctx->dctx) { +- ret = -EINVAL; +- goto out; +- } +- + do { + scur = acomp_walk_next_src(&walk); + if (scur) { +@@ -263,6 +258,19 @@ static int zstd_decompress(struct acomp_req *req) + goto out; + } + ++ if (!stream_initialized) { ++ ctx->dctx = zstd_init_dstream(ZSTD_MAX_SIZE, ctx->wksp, ++ ctx->wksp_size); ++ if (!ctx->dctx) { ++ /* Release in the reverse of the map order. */ ++ acomp_walk_done_dst(&walk, 0); ++ acomp_walk_done_src(&walk, 0); ++ ret = -EINVAL; ++ goto out; ++ } ++ stream_initialized = true; ++ } ++ + outbuf.pos = 0; + outbuf.dst = (u8 *)walk.dst.virt.addr; + outbuf.size = dcur; diff --git a/pkgbuilds/linux-omarchy/0295-af-alg-restrict.patch b/pkgbuilds/linux-omarchy/0295-af-alg-restrict.patch new file mode 100644 index 0000000..2700fc9 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0295-af-alg-restrict.patch @@ -0,0 +1,363 @@ +--- a/crypto/af_alg.c ++++ b/crypto/af_alg.c +@@ -8,6 +8,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -22,10 +23,28 @@ + #include + #include + #include ++#include ++#include + #include + #include + #include + ++static int af_alg_restrict = 1; ++ ++static const struct ctl_table af_alg_table[] = { ++ { ++ .procname = "af_alg_restrict", ++ .data = &af_alg_restrict, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_TWO, ++ }, ++}; ++ ++static struct ctl_table_header *af_alg_header; ++ + struct alg_type_list { + const struct af_alg_type *type; + struct list_head list; +@@ -110,6 +129,43 @@ + } + EXPORT_SYMBOL_GPL(af_alg_unregister_type); + ++static bool af_alg_capable(void) ++{ ++ return ns_capable_noaudit(&init_user_ns, CAP_NET_ADMIN) || ++ capable(CAP_SYS_ADMIN); ++} ++ ++int af_alg_check_restriction(const char *name, ++ const struct af_alg_allowlist_entry allowlist[]) ++{ ++ int level = READ_ONCE(af_alg_restrict); ++ ++ if (level == 0) ++ return 0; ++ if (level == 1) { ++ for (const struct af_alg_allowlist_entry *ent = allowlist; ++ ent->name; ent++) { ++ if (strcmp(name, ent->name) == 0) { ++ if ((ent->flags & AF_ALG_UNPRIVILEGED) || ++ af_alg_capable()) ++ return 0; ++ /* List contains at most one entry per name. */ ++ break; ++ } ++ } ++ } ++ /* ++ * Use -ENOENT (the error code for "algorithm not found") instead of ++ * -EACCES or -EPERM, for the highest chance of correctly triggering ++ * fallback code paths in userspace programs. ++ * ++ * Don't log a warning, since it would be noisy. iwd tries to bind a ++ * bunch of algorithms that it never uses. ++ */ ++ return -ENOENT; ++} ++EXPORT_SYMBOL_GPL(af_alg_check_restriction); ++ + static void alg_do_release(const struct af_alg_type *type, void *private) + { + if (!type) +@@ -506,6 +562,9 @@ + struct sock *sk; + int err; + ++ if (READ_ONCE(af_alg_restrict) == 2) ++ return -EAFNOSUPPORT; ++ + if (sock->type != SOCK_SEQPACKET) + return -ESOCKTNOSUPPORT; + if (protocol != 0) +@@ -1222,27 +1281,32 @@ + + static int __init af_alg_init(void) + { +- int err = proto_register(&alg_proto, 0); ++ int err; ++ ++ af_alg_header = register_sysctl("crypto", af_alg_table); + ++ err = proto_register(&alg_proto, 0); + if (err) +- goto out; ++ goto out_unregister_sysctl; + + err = sock_register(&alg_family); +- if (err != 0) ++ if (err) + goto out_unregister_proto; + +-out: +- return err; ++ return 0; + + out_unregister_proto: + proto_unregister(&alg_proto); +- goto out; ++out_unregister_sysctl: ++ unregister_sysctl_table(af_alg_header); ++ return err; + } + + static void __exit af_alg_exit(void) + { + sock_unregister(PF_ALG); + proto_unregister(&alg_proto); ++ unregister_sysctl_table(af_alg_header); + } + + module_init(af_alg_init); +--- a/crypto/algif_aead.c ++++ b/crypto/algif_aead.c +@@ -34,6 +34,11 @@ + #include + #include + ++static const struct af_alg_allowlist_entry aead_allowlist[] = { ++ { "ccm(aes)" }, /* bluez */ ++ {}, ++}; ++ + static inline bool aead_sufficient_data(struct sock *sk) + { + struct alg_sock *ask = alg_sk(sk); +@@ -344,6 +349,12 @@ + + static void *aead_bind(const char *name) + { ++ int err; ++ ++ err = af_alg_check_restriction(name, aead_allowlist); ++ if (err) ++ return ERR_PTR(err); ++ + return crypto_alloc_aead(name, 0, AF_ALG_CRYPTOAPI_MASK); + } + +--- a/crypto/algif_hash.c ++++ b/crypto/algif_hash.c +@@ -16,6 +16,24 @@ + #include + #include + ++static const struct af_alg_allowlist_entry hash_allowlist[] = { ++ { "cmac(aes)" }, /* iwd, bluez */ ++ { "hmac(md5)" }, /* iwd */ ++ { "hmac(sha1)" }, /* iwd */ ++ { "hmac(sha224)" }, /* iwd */ ++ { "hmac(sha256)" }, /* iwd */ ++ { "hmac(sha384)" }, /* iwd */ ++ { "hmac(sha512)" }, /* iwd, sha512hmac */ ++ { "md4" }, /* iwd */ ++ { "md5" }, /* iwd */ ++ { "sha1", AF_ALG_UNPRIVILEGED }, /* iwd, iproute2 < 7.0 */ ++ { "sha224" }, /* iwd */ ++ { "sha256" }, /* iwd */ ++ { "sha384" }, /* iwd */ ++ { "sha512" }, /* iwd */ ++ {}, ++}; ++ + struct hash_ctx { + struct af_alg_sgl sgl; + +@@ -382,6 +400,12 @@ + + static void *hash_bind(const char *name) + { ++ int err; ++ ++ err = af_alg_check_restriction(name, hash_allowlist); ++ if (err) ++ return ERR_PTR(err); ++ + return crypto_alloc_ahash(name, 0, AF_ALG_CRYPTOAPI_MASK); + } + +--- a/crypto/algif_rng.c ++++ b/crypto/algif_rng.c +@@ -50,6 +50,10 @@ + MODULE_AUTHOR("Stephan Mueller "); + MODULE_DESCRIPTION("User-space interface for random number generators"); + ++static const struct af_alg_allowlist_entry rng_allowlist[] = { ++ {}, ++}; ++ + struct rng_ctx { + #define MAXSIZE 128 + unsigned int len; +@@ -201,6 +205,11 @@ + { + struct rng_parent_ctx *pctx; + struct crypto_rng *rng; ++ int err; ++ ++ err = af_alg_check_restriction(name, rng_allowlist); ++ if (err) ++ return ERR_PTR(err); + + pctx = kzalloc_obj(*pctx); + if (!pctx) +--- a/crypto/algif_skcipher.c ++++ b/crypto/algif_skcipher.c +@@ -35,6 +35,24 @@ + #include + #include + ++static const struct af_alg_allowlist_entry skcipher_allowlist[] = { ++ { "adiantum(xchacha12,aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ { "adiantum(xchacha20,aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ { "cbc(aes)" }, /* iwd */ ++ { "cbc(des)" }, /* iwd */ ++ { "cbc(des3_ede)" }, /* iwd */ ++ { "cbc(paes)" }, /* caam and others */ ++ { "ctr(aes)" }, /* iwd */ ++ { "ecb(aes)" }, /* iwd, bluez */ ++ { "ecb(des)" }, /* iwd */ ++ { "hctr2(aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ { "xts(aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup benchmark */ ++ { "xts(camellia)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ { "xts(serpent)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ { "xts(twofish)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ {}, ++}; ++ + static int skcipher_sendmsg(struct socket *sock, struct msghdr *msg, + size_t size) + { +@@ -311,6 +329,11 @@ + static void *skcipher_bind(const char *name) + { + u32 mask = AF_ALG_CRYPTOAPI_MASK; ++ int err; ++ ++ err = af_alg_check_restriction(name, skcipher_allowlist); ++ if (err) ++ return ERR_PTR(err); + + if (strcmp(name, "cbc(paes)") == 0) + mask = 0; +--- a/include/crypto/if_alg.h ++++ b/include/crypto/if_alg.h +@@ -8,6 +8,7 @@ + #ifndef _CRYPTO_IF_ALG_H + #define _CRYPTO_IF_ALG_H + ++#include + #include + #include + #include +@@ -121,7 +122,7 @@ + * @iv: IV for cipher operation + * @state: Existing state for continuing operation + * @aead_assoclen: Length of AAD for AEAD cipher operations +- * @completion: Work queue for synchronous operation ++ * @wait: For waiting for completion of async crypto ops + * @used: TX bytes sent to kernel. This variable is used to + * ensure that user space cannot cause the kernel + * to allocate too much memory in sendmsg operation. +@@ -161,9 +162,20 @@ + unsigned int inflight; + }; + ++/* Flags for af_alg_allowlist_entry::flags: */ ++#define AF_ALG_UNPRIVILEGED BIT(0) /* Unprivileged use is allowed */ ++ ++struct af_alg_allowlist_entry { ++ const char *name; ++ u32 flags; ++}; ++ + int af_alg_register_type(const struct af_alg_type *type); + int af_alg_unregister_type(const struct af_alg_type *type); + ++int af_alg_check_restriction(const char *name, ++ const struct af_alg_allowlist_entry allowlist[]); ++ + int af_alg_release(struct socket *sock); + void af_alg_release_parent(struct sock *sk); + int af_alg_accept(struct sock *sk, struct socket *newsock, +@@ -177,10 +189,11 @@ + } + + /** +- * Size of available buffer for sending data from user space to kernel. ++ * af_alg_sndbuf - Size of available buffer for sending data from user space to kernel. + * +- * @sk socket of connection to user space +- * @return number of bytes still available ++ * @sk: socket of connection to user space ++ * ++ * Returns: number of bytes still available + */ + static inline int af_alg_sndbuf(struct sock *sk) + { +@@ -192,10 +205,11 @@ + } + + /** +- * Can the send buffer still be written to? ++ * af_alg_writable - Can the send buffer still be written to? ++ * ++ * @sk: socket of connection to user space + * +- * @sk socket of connection to user space +- * @return true => writable, false => not writable ++ * Returns: true => writable, false => not writable + */ + static inline bool af_alg_writable(struct sock *sk) + { +@@ -203,10 +217,11 @@ + } + + /** +- * Size of available buffer used by kernel for the RX user space operation. ++ * af_alg_rcvbuf - Size of available buffer used by kernel for the RX user space operation. + * +- * @sk socket of connection to user space +- * @return number of bytes still available ++ * @sk: socket of connection to user space ++ * ++ * Returns: number of bytes still available + */ + static inline int af_alg_rcvbuf(struct sock *sk) + { +@@ -218,10 +233,11 @@ + } + + /** +- * Can the RX buffer still be written to? ++ * af_alg_readable - Can the RX buffer still be read from? ++ * ++ * @sk: socket of connection to user space + * +- * @sk socket of connection to user space +- * @return true => writable, false => not writable ++ * Returns: true => readable, false => not readable + */ + static inline bool af_alg_readable(struct sock *sk) + { diff --git a/pkgbuilds/linux-omarchy/0296-x86-mm-pmd-modify-keep-dirty-bit.patch b/pkgbuilds/linux-omarchy/0296-x86-mm-pmd-modify-keep-dirty-bit.patch new file mode 100644 index 0000000..602ef86 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0296-x86-mm-pmd-modify-keep-dirty-bit.patch @@ -0,0 +1,12 @@ +diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h +--- a/arch/x86/include/asm/pgtable.h ++++ b/arch/x86/include/asm/pgtable.h +@@ -806,7 +806,7 @@ static inline pmd_t pmd_modify(pmd_t pmd, pgprot_t newprot) + pmdval_t val = pmd_val(pmd), oldval = val; + pmd_t pmd_result; + +- val &= (_HPAGE_CHG_MASK & ~_PAGE_DIRTY); ++ val &= _HPAGE_CHG_MASK; + val |= check_pgprot(newprot) & ~_HPAGE_CHG_MASK; + val = flip_protnone_guard(oldval, val, PHYSICAL_PMD_PAGE_MASK); + diff --git a/pkgbuilds/linux-omarchy/0300-btrfs.patch b/pkgbuilds/linux-omarchy/0300-btrfs.patch new file mode 100644 index 0000000..0cb9f8d --- /dev/null +++ b/pkgbuilds/linux-omarchy/0300-btrfs.patch @@ -0,0 +1,5023 @@ +diff --git a/fs/btrfs/acl.c b/fs/btrfs/acl.c +--- a/fs/btrfs/acl.c ++++ b/fs/btrfs/acl.c +@@ -15,6 +15,7 @@ + #include "xattr.h" + #include "acl.h" + #include "misc.h" ++#include "btrfs_inode.h" + + struct posix_acl *btrfs_get_acl(struct inode *inode, int type, bool rcu) + { +@@ -107,6 +108,9 @@ int btrfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, + struct inode *inode = d_inode(dentry); + umode_t old_mode = inode->i_mode; + ++ if (btrfs_root_readonly(BTRFS_I(inode)->root)) ++ return -EROFS; ++ + if (type == ACL_TYPE_ACCESS && acl) { + ret = posix_acl_update_mode(idmap, inode, + &inode->i_mode, &acl); +diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c +--- a/fs/btrfs/block-group.c ++++ b/fs/btrfs/block-group.c +@@ -2047,6 +2047,11 @@ static int btrfs_reclaim_block_group(struct btrfs_block_group *bg, int *reclaime + + trace_btrfs_reclaim_block_group(bg); + ret = btrfs_relocate_chunk(fs_info, bg->start, false); ++ if (btrfs_is_zoned(fs_info) && ret == -EAGAIN) { ++ btrfs_dec_block_group_ro(bg); ++ btrfs_debug(fs_info, "deferring reclaim of chunk %llu", bg->start); ++ return ret; ++ } + if (ret) { + btrfs_dec_block_group_ro(bg); + btrfs_err(fs_info, "error relocating chunk %llu", +@@ -2113,7 +2118,8 @@ void btrfs_reclaim_block_groups(struct btrfs_fs_info *fs_info, unsigned int limi + spin_unlock(&fs_info->unused_bgs_lock); + ret = btrfs_reclaim_block_group(bg, &reclaimed); + +- if (ret && !READ_ONCE(space_info->periodic_reclaim)) ++ if ((btrfs_is_zoned(fs_info) && ret == -EAGAIN) || ++ (ret && !READ_ONCE(space_info->periodic_reclaim))) + btrfs_link_bg_list(bg, &retry_list); + btrfs_put_block_group(bg); + +@@ -2624,10 +2630,9 @@ static int fill_dummy_bgs(struct btrfs_fs_info *fs_info) + + /* Fill dummy cache as FULL */ + bg->length = map->chunk_len; +- bg->flags = map->type; ++ bg->flags = map->on_disk_type; + bg->cached = BTRFS_CACHE_FINISHED; + bg->used = map->chunk_len; +- bg->flags = map->type; + bg->space_info = btrfs_find_space_info(fs_info, bg->flags); + ret = btrfs_add_block_group_cache(bg); + /* +@@ -3916,7 +3921,7 @@ int btrfs_update_block_group(struct btrfs_trans_handle *trans, + old_val += num_bytes; + cache->used = old_val; + cache->reserved -= num_bytes; +- cache->reclaim_mark = 0; ++ cache->reclaim_mark = false; + space_info->bytes_reserved -= num_bytes; + space_info->bytes_used += num_bytes; + space_info->disk_used += num_bytes * factor; +diff --git a/fs/btrfs/block-group.h b/fs/btrfs/block-group.h +--- a/fs/btrfs/block-group.h ++++ b/fs/btrfs/block-group.h +@@ -263,6 +263,9 @@ struct btrfs_block_group { + + enum btrfs_block_group_size_class size_class:8; + ++ /* If set, this blockgroup is not used for allocation between two reclaim sweeps. */ ++ bool reclaim_mark; ++ + /* + * Number of extents in this block group used for swap files. + * All accesses protected by the spinlock 'lock'. +@@ -281,7 +284,6 @@ struct btrfs_block_group { + struct list_head active_bg_list; + struct work_struct zone_finish_work; + struct extent_buffer *last_eb; +- u64 reclaim_mark; + }; + + static inline u64 btrfs_block_group_end(const struct btrfs_block_group *block_group) +diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c +--- a/fs/btrfs/compression.c ++++ b/fs/btrfs/compression.c +@@ -651,9 +651,9 @@ struct heuristic_ws { + u8 *sample; + u32 sample_size; + /* Buckets store counters for each byte value */ +- struct bucket_item *bucket; ++ struct bucket_item bucket[BUCKET_SIZE]; + /* Sorting buffer */ +- struct bucket_item *bucket_b; ++ struct bucket_item bucket_b[BUCKET_SIZE]; + struct list_head list; + }; + +@@ -664,8 +664,6 @@ static void free_heuristic_ws(struct list_head *ws) + workspace = list_entry(ws, struct heuristic_ws, list); + + kvfree(workspace->sample); +- kfree(workspace->bucket); +- kfree(workspace->bucket_b); + kfree(workspace); + } + +@@ -681,14 +679,6 @@ static struct list_head *alloc_heuristic_ws(struct btrfs_fs_info *fs_info) + if (!ws->sample) + goto fail; + +- ws->bucket = kzalloc_objs(*ws->bucket, BUCKET_SIZE); +- if (!ws->bucket) +- goto fail; +- +- ws->bucket_b = kzalloc_objs(*ws->bucket_b, BUCKET_SIZE); +- if (!ws->bucket_b) +- goto fail; +- + INIT_LIST_HEAD(&ws->list); + return &ws->list; + fail: +diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c +--- a/fs/btrfs/ctree.c ++++ b/fs/btrfs/ctree.c +@@ -1460,6 +1460,7 @@ static noinline void unlock_up(struct btrfs_path *path, int level, + */ + static int + read_block_for_search(struct btrfs_root *root, struct btrfs_path *p, ++ struct btrfs_eb_prealloc *pa, + struct extent_buffer **eb_ret, int slot, + const struct btrfs_key *key) + { +@@ -1546,7 +1547,8 @@ read_block_for_search(struct btrfs_root *root, struct btrfs_path *p, + if (p->reada != READA_NONE) + reada_for_search(fs_info, p, parent_level, slot, key->objectid); + +- tmp = btrfs_find_create_tree_block(fs_info, blocknr, check.owner_root, check.level); ++ tmp = btrfs_find_create_tree_block(fs_info, pa, blocknr, ++ check.owner_root, check.level); + if (IS_ERR(tmp)) { + ret = PTR_ERR(tmp); + tmp = NULL; +@@ -2004,6 +2006,7 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + u8 lowest_level = 0; + int min_write_lock_level; + int prev_cmp; ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + + if (!root) + return -EINVAL; +@@ -2058,6 +2061,11 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + } + + again: ++ if (pa.needs_prealloc) { ++ ret = btrfs_init_eb_prealloc(fs_info, &pa, false); ++ if (ret) ++ goto done; ++ } + prev_cmp = -1; + b = btrfs_search_slot_get_root(root, p, write_lock_level); + if (IS_ERR(b)) { +@@ -2187,7 +2195,7 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + goto done; + } + +- ret2 = read_block_for_search(root, p, &b, slot, key); ++ ret2 = read_block_for_search(root, p, &pa, &b, slot, key); + if (ret2 == -EAGAIN && !p->nowait) { + trace_btrfs_search_slot_restart(root, level, "read_block"); + goto again; +@@ -2234,6 +2242,8 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + ret = ret2; + } + ++ btrfs_free_eb_prealloc(&pa); ++ + return ret; + } + ALLOW_ERROR_INJECTION(btrfs_search_slot, ERRNO); +@@ -2259,6 +2269,7 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + int level; + int lowest_unlock = 1; + u8 lowest_level = 0; ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + + lowest_level = p->lowest_level; + WARN_ON(p->nodes[0] != NULL); +@@ -2270,6 +2281,11 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + } + + again: ++ if (pa.needs_prealloc) { ++ ret = btrfs_init_eb_prealloc(fs_info, &pa, false); ++ if (ret) ++ goto done; ++ } + b = btrfs_get_old_root(root, time_seq); + if (unlikely(!b)) { + ret = -EIO; +@@ -2316,7 +2332,7 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + goto done; + } + +- ret2 = read_block_for_search(root, p, &b, slot, key); ++ ret2 = read_block_for_search(root, p, &pa, &b, slot, key); + if (ret2 == -EAGAIN && !p->nowait) + goto again; + if (ret2) { +@@ -2339,6 +2355,8 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + if (ret < 0) + btrfs_release_path(p); + ++ btrfs_free_eb_prealloc(&pa); ++ + return ret; + } + +@@ -4780,6 +4798,7 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + struct extent_buffer *next; + struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_key key; ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + bool need_commit_sem = false; + u32 nritems; + int ret; +@@ -4798,6 +4817,11 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + + btrfs_item_key_to_cpu(path->nodes[0], &key, nritems - 1); + again: ++ if (pa.needs_prealloc) { ++ ret = btrfs_init_eb_prealloc(fs_info, &pa, false); ++ if (ret) ++ goto done; ++ } + level = 1; + next = NULL; + btrfs_release_path(path); +@@ -4880,7 +4904,7 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + } + + next = c; +- ret = read_block_for_search(root, path, &next, slot, &key); ++ ret = read_block_for_search(root, path, &pa, &next, slot, &key); + if (ret == -EAGAIN && !path->nowait) + goto again; + +@@ -4923,7 +4947,7 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + if (!level) + break; + +- ret = read_block_for_search(root, path, &next, 0, &key); ++ ret = read_block_for_search(root, path, &pa, &next, 0, &key); + if (ret == -EAGAIN && !path->nowait) + goto again; + +@@ -4956,6 +4980,8 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + ret = ret2; + } + ++ btrfs_free_eb_prealloc(&pa); ++ + return ret; + } + +diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h +--- a/fs/btrfs/ctree.h ++++ b/fs/btrfs/ctree.h +@@ -131,7 +131,6 @@ enum { + BTRFS_ROOT_ORPHAN_ITEM_INSERTED, + BTRFS_ROOT_DEFRAG_RUNNING, + BTRFS_ROOT_FORCE_COW, +- BTRFS_ROOT_MULTI_LOG_TASKS, + BTRFS_ROOT_DIRTY, + BTRFS_ROOT_DELETING, + +@@ -196,9 +195,7 @@ struct btrfs_root { + struct list_head log_ctxs[2]; + /* Used only for log trees of subvolumes, not for the log root tree */ + atomic_t log_writers; +- atomic_t log_commit[2]; +- /* Used only for log trees of subvolumes, not for the log root tree */ +- atomic_t log_batch; ++ bool log_commit[2]; + /* + * Protected by the 'log_mutex' lock but can be read without holding + * that lock to avoid unnecessary lock contention, in which case it +@@ -216,7 +213,6 @@ struct btrfs_root { + * to access this field. + */ + int last_log_commit; +- pid_t log_start_pid; + + u64 last_trans; + +diff --git a/fs/btrfs/defrag.c b/fs/btrfs/defrag.c +--- a/fs/btrfs/defrag.c ++++ b/fs/btrfs/defrag.c +@@ -1093,7 +1093,7 @@ static int defrag_collect_targets(struct btrfs_inode *inode, + struct defrag_target_range *tmp; + + list_for_each_entry_safe(entry, tmp, target_list, list) { +- list_del_init(&entry->list); ++ list_del(&entry->list); + kfree(entry); + } + } +@@ -1130,20 +1130,15 @@ static_assert(PAGE_ALIGNED(CLUSTER_SIZE)); + * + * - Extent bits are locked + */ +-static int defrag_one_locked_target(struct btrfs_inode *inode, +- struct defrag_target_range *target, +- struct folio **folios, int nr_pages, +- struct extent_state **cached_state) ++static void defrag_one_locked_target(struct btrfs_inode *inode, ++ struct defrag_target_range *target, ++ struct folio **folios, int nr_pages, ++ struct extent_state **cached_state) + { + struct btrfs_fs_info *fs_info = inode->root->fs_info; +- struct extent_changeset *data_reserved = NULL; + const u64 start = target->start; + const u64 len = target->len; +- int ret = 0; + +- ret = btrfs_delalloc_reserve_space(inode, &data_reserved, start, len); +- if (ret < 0) +- return ret; + btrfs_clear_extent_bit(&inode->io_tree, start, start + len - 1, + EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | + EXTENT_DEFRAG, cached_state); +@@ -1164,10 +1159,6 @@ static int defrag_one_locked_target(struct btrfs_inode *inode, + continue; + btrfs_folio_clamp_set_dirty(fs_info, folio, start, len); + } +- btrfs_delalloc_release_extents(inode, len); +- extent_changeset_free(data_reserved); +- +- return ret; + } + + static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, +@@ -1178,11 +1169,13 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + struct defrag_target_range *entry; + struct defrag_target_range *tmp; + LIST_HEAD(target_list); +- struct folio **folios; ++ struct folio AUTO_KFREE(*folios); + const u32 sectorsize = inode->root->fs_info->sectorsize; + u64 cur = start; + const unsigned int nr_pages = ((start + len - 1) >> PAGE_SHIFT) - + (start >> PAGE_SHIFT) + 1; ++ struct extent_changeset *data_reserved = NULL; ++ u64 last_defrag_end = start; + int ret = 0; + + ASSERT(nr_pages <= CLUSTER_SIZE / PAGE_SIZE); +@@ -1192,6 +1185,20 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + if (!folios) + return -ENOMEM; + ++ /* ++ * Reserve delalloc space before locking the range and before locking ++ * and dirtying any folios - otherwise we could deadlock, for example ++ * after defrag of one range we dirty folios and keep them locked when ++ * we move to the next range, so reserving delalloc space right before ++ * each range could trigger flushing of delalloc and deadlock on the ++ * extent lock or trigger a transaction commit with flushoncommit, which ++ * can either deadlock on the lock of a folio made dirty in the previous ++ * range or the extent lock. ++ */ ++ ret = btrfs_delalloc_reserve_space(inode, &data_reserved, start, len); ++ if (ret < 0) ++ return ret; ++ + /* Prepare all pages */ + for (int i = 0; cur < start + len && i < nr_pages; i++) { + folios[i] = defrag_prepare_one_folio(inode, cur >> PAGE_SHIFT); +@@ -1225,15 +1232,12 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + if (ret < 0) + goto unlock_extent; + +- list_for_each_entry(entry, &target_list, list) { +- ret = defrag_one_locked_target(inode, entry, folios, nr_pages, +- &cached_state); +- if (ret < 0) +- break; +- } +- + list_for_each_entry_safe(entry, tmp, &target_list, list) { +- list_del_init(&entry->list); ++ defrag_one_locked_target(inode, entry, folios, nr_pages, &cached_state); ++ if (entry->start > last_defrag_end) ++ btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end, ++ entry->start - last_defrag_end, true); ++ last_defrag_end = entry->start + entry->len; + kfree(entry); + } + unlock_extent: +@@ -1245,7 +1249,12 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + folio_unlock(folios[i]); + folio_put(folios[i]); + } +- kfree(folios); ++ btrfs_delalloc_release_extents(inode, len); ++ if (last_defrag_end < start + len) ++ btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end, ++ start + len - last_defrag_end, true); ++ extent_changeset_free(data_reserved); ++ + return ret; + } + +@@ -1310,10 +1319,8 @@ static int defrag_one_cluster(struct btrfs_inode *inode, + inode->root->fs_info->sectorsize_bits; + } + out: +- list_for_each_entry_safe(entry, tmp, &target_list, list) { +- list_del_init(&entry->list); ++ list_for_each_entry_safe(entry, tmp, &target_list, list) + kfree(entry); +- } + if (ret >= 0) + *last_scanned_ret = max(*last_scanned_ret, start + len); + return ret; +diff --git a/fs/btrfs/delayed-inode.c b/fs/btrfs/delayed-inode.c +--- a/fs/btrfs/delayed-inode.c ++++ b/fs/btrfs/delayed-inode.c +@@ -1523,10 +1523,10 @@ int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans, + ret = __btrfs_add_delayed_item(delayed_node, delayed_item); + if (unlikely(ret)) { + btrfs_err(trans->fs_info, +-"error adding delayed dir index item, name: %.*s, index: %llu, root: %llu, dir: %llu, dir->index_cnt: %llu, delayed_node->index_cnt: %llu, error: %d", ++"error adding delayed dir index item, name: %.*s, index: %llu, root: %llu, dir: %llu, dir->index_cnt: %llu, delayed_node->index_cnt: %llu, error: %pe", + name_len, name, index, btrfs_root_id(delayed_node->root), + delayed_node->inode_id, dir->index_cnt, +- delayed_node->index_cnt, ret); ++ delayed_node->index_cnt, ERR_PTR(ret)); + btrfs_release_delayed_item(delayed_item); + btrfs_release_dir_index_item_space(trans); + mutex_unlock(&delayed_node->mutex); +@@ -1645,8 +1645,8 @@ int btrfs_delete_delayed_dir_index(struct btrfs_trans_handle *trans, + */ + if (ret < 0) { + btrfs_err(trans->fs_info, +-"metadata reservation failed for delayed dir item deletion, index: %llu, root: %llu, inode: %llu, error: %d", +- index, btrfs_root_id(node->root), node->inode_id, ret); ++"metadata reservation failed for delayed dir item deletion, index: %llu, root: %llu, inode: %llu, error: %pe", ++ index, btrfs_root_id(node->root), node->inode_id, ERR_PTR(ret)); + btrfs_release_delayed_item(item); + goto end; + } +@@ -1655,8 +1655,8 @@ int btrfs_delete_delayed_dir_index(struct btrfs_trans_handle *trans, + ret = __btrfs_add_delayed_item(node, item); + if (unlikely(ret)) { + btrfs_err(trans->fs_info, +-"failed to add delayed dir index item, root: %llu, inode: %llu, index: %llu, error: %d", +- btrfs_root_id(node->root), node->inode_id, index, ret); ++"failed to add delayed dir index item, root: %llu, inode: %llu, index: %llu, error: %pe", ++ btrfs_root_id(node->root), node->inode_id, index, ERR_PTR(ret)); + btrfs_delayed_item_release_metadata(dir->root, item); + btrfs_release_delayed_item(item); + } +diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c +--- a/fs/btrfs/direct-io.c ++++ b/fs/btrfs/direct-io.c +@@ -14,7 +14,6 @@ + #include "ordered-data.h" + + struct btrfs_dio_data { +- ssize_t submitted; + loff_t old_isize; + struct extent_changeset *data_reserved; + struct btrfs_ordered_extent *ordered; +@@ -151,7 +150,7 @@ static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode, + if (type != BTRFS_ORDERED_NOCOW) { + em = btrfs_create_io_em(inode, start, file_extent, type); + if (IS_ERR(em)) +- goto out; ++ return em; + } + + ordered = btrfs_alloc_ordered_extent(inode, start, file_extent, +@@ -168,7 +167,6 @@ static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode, + ASSERT(!dio_data->ordered); + dio_data->ordered = ordered; + } +- out: + + return em; + } +@@ -626,78 +624,81 @@ static int btrfs_dio_iomap_end(struct inode *inode, loff_t pos, loff_t length, + { + struct iomap_iter *iter = container_of(iomap, struct iomap_iter, iomap); + struct btrfs_dio_data *dio_data = iter->private; +- size_t submitted = dio_data->submitted; + const bool write = !!(flags & IOMAP_WRITE); + int ret = 0; + +- if (!write && (iomap->type == IOMAP_HOLE)) { +- /* If reading from a hole, unlock and return */ +- btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, +- pos + length - 1, NULL); ++ if (!write) { ++ /* ++ * Hole read, nothing is submitted, thus we have to unlock ++ * the whole range. ++ */ ++ if (iomap->type == IOMAP_HOLE) { ++ btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, ++ pos + length - 1, NULL); ++ return 0; ++ } ++ /* ++ * Short read, needs to unlock the remaining range, and ++ * return -ENOTBLK so we can later fault in the pages and retry. ++ */ ++ if (written < length) { ++ btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos + written, ++ pos + length - 1, NULL); ++ return -ENOTBLK; ++ } ++ /* The full range is submitted, endio will do the unlock. */ + return 0; + } + +- if (submitted < length) { +- pos += submitted; +- length -= submitted; +- if (write) { +- /* +- * Got a short write and have updated the isize, need to +- * revert the isize change. +- * +- * Normally we need to update isize with extent lock hold, +- * but we're safe due to the following factors: +- * +- * - Only a single writer can be enlarging isize +- * Enlarging isize will take the exclusive inode lock. +- * +- * - Buffered readers need to wait for the OE we're holding +- * Buffered readers will lock extent and wait for OE +- * of the folio range, and since page cache is invalidated +- * the OE wait can not be skipped. +- * +- * So here we are safe to revert the isize before +- * finishing the OE, and no reader of the remaining range +- * can see the enlarged size. +- * +- * TODO: Extend the DIO_LOCKED lifespan for direct writes, +- * and only enlarge isize after a successful write. +- */ +- if (dio_data->updated_isize) { +- u64 new_isize; ++ if (written < length) { ++ /* ++ * Got a short write and have updated the i_size, need to revert ++ * the i_size change. ++ * ++ * Normally we need to update i_size with extent lock held, but ++ * we're safe due to the following factors: ++ * ++ * - Only a single writer can be enlarging i_size ++ * Enlarging i_size will take the exclusive inode lock. ++ * ++ * - Buffered readers need to wait for the OE we're holding ++ * Buffered readers will lock extent and wait for OE ++ * of the folio range, and since page cache is invalidated ++ * the OE wait cannot be skipped. ++ * ++ * So here we are safe to revert the isize before finishing the ++ * OE, and no reader of the remaining range can see the enlarged ++ * size. ++ * ++ * TODO: Extend the DIO_LOCKED lifespan for direct writes, ++ * and only enlarge isize after a successful write. ++ */ ++ if (dio_data->updated_isize) { ++ u64 new_isize; + +- if (submitted == 0) +- new_isize = dio_data->old_isize; +- else +- new_isize = max(dio_data->old_isize, pos); +- i_size_write(inode, new_isize); +- dio_data->updated_isize = false; +- } +- /* +- * We have a short write, if there is any range +- * that is submitted properly, that part will have +- * its own OE split from the original one. +- * +- * So for the OE at dio_data->ordered, it's the part +- * that is not submitted, and should be marked +- * as fully truncated. +- */ +- btrfs_mark_ordered_extent_truncated(dio_data->ordered, 0); +- btrfs_finish_ordered_extent(dio_data->ordered, +- pos, length, true); +- } else { +- btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, +- pos + length - 1, NULL); ++ if (written == 0) ++ new_isize = dio_data->old_isize; ++ else ++ new_isize = max(dio_data->old_isize, pos + written); ++ i_size_write(inode, new_isize); ++ dio_data->updated_isize = false; + } ++ /* ++ * We have a short write, if there is any range that is submitted ++ * properly, that part will have its own OE split from the ++ * original one. ++ * ++ * So for the OE at dio_data->ordered, it's the part that is not ++ * submitted, and should be marked as fully truncated. ++ */ ++ btrfs_mark_ordered_extent_truncated(dio_data->ordered, 0); ++ btrfs_finish_ordered_extent(dio_data->ordered, ++ pos + written, length - written, true); + ret = -ENOTBLK; + } +- if (write) { +- btrfs_put_ordered_extent(dio_data->ordered); +- dio_data->ordered = NULL; +- } +- +- if (write) +- extent_changeset_free(dio_data->data_reserved); ++ btrfs_put_ordered_extent(dio_data->ordered); ++ dio_data->ordered = NULL; ++ extent_changeset_free(dio_data->data_reserved); + return ret; + } + +@@ -779,8 +780,6 @@ static void btrfs_dio_submit_io(const struct iomap_iter *iter, struct bio *bio, + dip->file_offset = file_offset; + dip->bytes = bio->bi_iter.bi_size; + +- dio_data->submitted += bio->bi_iter.bi_size; +- + /* + * Check if we are doing a partial write. If we are, we need to split + * the ordered extent to match the submitted bio. Hang on to the +@@ -824,13 +823,41 @@ static ssize_t btrfs_dio_read(struct kiocb *iocb, struct iov_iter *iter, + IOMAP_DIO_PARTIAL | IOMAP_DIO_FSBLOCK_ALIGNED, &data, done_before); + } + ++static bool need_stable_write(struct btrfs_inode *inode) ++{ ++ const u64 data_profile = btrfs_data_alloc_profile(inode->root->fs_info) & ++ BTRFS_BLOCK_GROUP_PROFILE_MASK; ++ ++ /* Data checksum requires stable buffer. */ ++ if (!(inode->flags & BTRFS_INODE_NODATASUM)) ++ return true; ++ /* ++ * Any profile with mirror/parity will require stable buffer. ++ * Otherwise the mirror may differ from each other. ++ * ++ * Thus only SINGLE and RAID0 doesn't require stable buffer. ++ */ ++ if (data_profile != 0 && data_profile != BTRFS_BLOCK_GROUP_RAID0) ++ return true; ++ return false; ++} ++ + static struct iomap_dio *btrfs_dio_write(struct kiocb *iocb, struct iov_iter *iter, + size_t done_before) + { + struct btrfs_dio_data data = { 0 }; ++ unsigned int dio_flags = IOMAP_DIO_PARTIAL | IOMAP_DIO_FSBLOCK_ALIGNED; ++ ++ if (need_stable_write(BTRFS_I(file_inode(iocb->ki_filp)))) { ++ /* For now no support for BOUNCE and NOWAIT direct write. */ ++ if (iocb->ki_flags & IOCB_NOWAIT) ++ return ERR_PTR(-EAGAIN); ++ ++ dio_flags |= IOMAP_DIO_BOUNCE; ++ } + + return __iomap_dio_rw(iocb, iter, &btrfs_dio_iomap_ops, &btrfs_dio_ops, +- IOMAP_DIO_PARTIAL | IOMAP_DIO_FSBLOCK_ALIGNED, &data, done_before); ++ dio_flags, &data, done_before); + } + + static ssize_t check_direct_IO(struct btrfs_fs_info *fs_info, +@@ -859,8 +886,6 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from) + ssize_t ret; + unsigned int ilock_flags = 0; + struct iomap_dio *dio; +- const u64 data_profile = btrfs_data_alloc_profile(fs_info) & +- BTRFS_BLOCK_GROUP_PROFILE_MASK; + + if (iocb->ki_flags & IOCB_NOWAIT) + ilock_flags |= BTRFS_ILOCK_TRY; +@@ -874,16 +899,6 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from) + if (iocb->ki_pos + iov_iter_count(from) <= i_size_read(inode) && IS_NOSEC(inode)) + ilock_flags |= BTRFS_ILOCK_SHARED; + +- /* +- * If our data profile has duplication (either extra mirrors or RAID56), +- * we can not trust the direct IO buffer, the content may change during +- * writeback and cause different contents written to different mirrors. +- * +- * Thus only RAID0 and SINGLE can go true zero-copy direct IO. +- */ +- if (data_profile != BTRFS_BLOCK_GROUP_RAID0 && data_profile != 0) +- goto buffered; +- + relock: + ret = btrfs_inode_lock(BTRFS_I(inode), ilock_flags); + if (ret < 0) +@@ -924,22 +939,6 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from) + btrfs_inode_unlock(BTRFS_I(inode), ilock_flags); + goto buffered; + } +- /* +- * We can't control the folios being passed in, applications can write +- * to them while a direct IO write is in progress. This means the +- * content might change after we calculated the data checksum. +- * Therefore we can end up storing a checksum that doesn't match the +- * persisted data. +- * +- * To be extra safe and avoid false data checksum mismatch, if the +- * inode requires data checksum, just fallback to buffered IO. +- * For buffered IO we have full control of page cache and can ensure +- * no one is modifying the content during writeback. +- */ +- if (!(BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM)) { +- btrfs_inode_unlock(BTRFS_I(inode), ilock_flags); +- goto buffered; +- } + + /* + * The iov_iter can be mapped to the same file range we are writing to. +diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c +--- a/fs/btrfs/disk-io.c ++++ b/fs/btrfs/disk-io.c +@@ -271,14 +271,15 @@ int btree_csum_one_bio(struct btrfs_bio *bbio) + return -EIO; + + /* +- * If an extent_buffer is marked as EXTENT_BUFFER_ZONED_ZEROOUT, don't +- * checksum it but zero-out its content. This is done to preserve +- * ordering of I/O without unnecessarily writing out data. ++ * An extent_buffer marked EXTENT_BUFFER_ZONED_ZEROOUT is written out as ++ * zeros to preserve ordering of I/O without persisting the now ++ * unnecessary block. The bio is fed from the shared zero page (see ++ * write_one_eb()), so there is nothing to checksum here. Crucially, the ++ * buffer's own content is left intact: it may still be referenced, e.g. ++ * btrfs_free_tree_block() reads its header to add a delayed reference. + */ +- if (test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) { +- memzero_extent_buffer(eb, 0, eb->len); ++ if (test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) + return 0; +- } + + if (WARN_ON_ONCE(found_start != eb->start)) + return -EIO; +@@ -590,12 +591,13 @@ static const struct address_space_operations btree_aops = { + + struct extent_buffer *btrfs_find_create_tree_block( + struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 bytenr, u64 owner_root, + int level) + { + if (btrfs_is_testing(fs_info)) + return alloc_test_extent_buffer(fs_info, bytenr); +- return alloc_extent_buffer(fs_info, bytenr, owner_root, level); ++ return alloc_extent_buffer(fs_info, pa, bytenr, owner_root, level); + } + + /* +@@ -608,12 +610,13 @@ struct extent_buffer *btrfs_find_create_tree_block( + struct extent_buffer *read_tree_block(struct btrfs_fs_info *fs_info, u64 bytenr, + struct btrfs_tree_parent_check *check) + { ++ struct btrfs_eb_prealloc pa = { 0 }; + struct extent_buffer *buf = NULL; + int ret; + + ASSERT(check); + +- buf = btrfs_find_create_tree_block(fs_info, bytenr, check->owner_root, ++ buf = btrfs_find_create_tree_block(fs_info, &pa, bytenr, check->owner_root, + check->level); + if (IS_ERR(buf)) + return buf; +@@ -666,10 +669,7 @@ static struct btrfs_root *btrfs_alloc_root(struct btrfs_fs_info *fs_info, + init_waitqueue_head(&root->log_commit_wait[1]); + INIT_LIST_HEAD(&root->log_ctxs[0]); + INIT_LIST_HEAD(&root->log_ctxs[1]); +- atomic_set(&root->log_commit[0], 0); +- atomic_set(&root->log_commit[1], 0); + atomic_set(&root->log_writers, 0); +- atomic_set(&root->log_batch, 0); + refcount_set(&root->refs, 1); + atomic_set(&root->snapshot_force_cow, 0); + atomic_set(&root->nr_swapfiles, 0); +@@ -2052,7 +2052,7 @@ static int btrfs_replay_log(struct btrfs_fs_info *fs_info, + if (IS_ERR(log_tree_root->node)) { + ret = PTR_ERR(log_tree_root->node); + log_tree_root->node = NULL; +- btrfs_err(fs_info, "failed to read log tree with error: %d", ret); ++ btrfs_err(fs_info, "failed to read log tree with error: %pe", ERR_PTR(ret)); + btrfs_put_root(log_tree_root); + return ret; + } +@@ -2062,7 +2062,7 @@ static int btrfs_replay_log(struct btrfs_fs_info *fs_info, + btrfs_put_root(log_tree_root); + if (unlikely(ret)) { + ASSERT(BTRFS_FS_ERROR(fs_info) != 0); +- btrfs_err(fs_info, "failed to recover log trees with error: %d", ret); ++ btrfs_err(fs_info, "failed to recover log trees with error: %pe", ERR_PTR(ret)); + return ret; + } + +@@ -2303,8 +2303,8 @@ static int btrfs_read_roots(struct btrfs_fs_info *fs_info) + + return 0; + out: +- btrfs_warn(fs_info, "failed to read root (objectid=%llu): %d", +- location.objectid, ret); ++ btrfs_warn(fs_info, "failed to read root (objectid=%llu): %pe", ++ location.objectid, ERR_PTR(ret)); + return ret; + } + +@@ -2395,8 +2395,8 @@ static int validate_sys_chunk_array(const struct btrfs_fs_info *fs_info, + int btrfs_validate_super(const struct btrfs_fs_info *fs_info, + const struct btrfs_super_block *sb, int mirror_num) + { +- u64 nodesize = btrfs_super_nodesize(sb); +- u64 sectorsize = btrfs_super_sectorsize(sb); ++ const u32 nodesize = btrfs_super_nodesize(sb); ++ const u32 sectorsize = btrfs_super_sectorsize(sb); + int ret = 0; + const bool ignore_flags = btrfs_test_opt(fs_info, IGNORESUPERFLAGS); + +@@ -2438,24 +2438,24 @@ int btrfs_validate_super(const struct btrfs_fs_info *fs_info, + */ + if (unlikely(!is_power_of_2(sectorsize) || sectorsize < BTRFS_MIN_BLOCKSIZE || + sectorsize > BTRFS_MAX_METADATA_BLOCKSIZE)) { +- btrfs_err(fs_info, "invalid sectorsize %llu", sectorsize); ++ btrfs_err(fs_info, "invalid sectorsize %u", sectorsize); + ret = -EINVAL; + } + + if (unlikely(!btrfs_supported_blocksize(sectorsize))) { + btrfs_err(fs_info, +- "sectorsize %llu not yet supported for page size %lu", ++ "sectorsize %u not yet supported for page size %lu", + sectorsize, PAGE_SIZE); + ret = -EINVAL; + } + + if (unlikely(!is_power_of_2(nodesize) || nodesize < sectorsize || + nodesize > BTRFS_MAX_METADATA_BLOCKSIZE)) { +- btrfs_err(fs_info, "invalid nodesize %llu", nodesize); ++ btrfs_err(fs_info, "invalid nodesize %u", nodesize); + ret = -EINVAL; + } + if (unlikely(nodesize != le32_to_cpu(sb->__unused_leafsize))) { +- btrfs_err(fs_info, "invalid leafsize %u, should be %llu", ++ btrfs_err(fs_info, "invalid leafsize %u, should be %u", + le32_to_cpu(sb->__unused_leafsize), nodesize); + ret = -EINVAL; + } +@@ -2905,7 +2905,6 @@ void btrfs_init_fs_info(struct btrfs_fs_info *fs_info) + fs_info->nodesize = 4096; + fs_info->sectorsize = 4096; + fs_info->sectorsize_bits = ilog2(4096); +- fs_info->stripesize = 4096; + + /* Default compress algorithm when user does -o compress */ + fs_info->compress_type = BTRFS_COMPRESS_ZLIB; +@@ -2979,8 +2978,8 @@ static int btrfs_uuid_rescan_kthread(void *data) + ret = btrfs_uuid_tree_iterate(fs_info); + if (ret < 0) { + if (ret != -EINTR) +- btrfs_warn(fs_info, "iterating uuid_tree failed %d", +- ret); ++ btrfs_warn(fs_info, "iterating uuid_tree failed %pe", ++ ERR_PTR(ret)); + up(&fs_info->uuid_tree_rescan_sem); + return ret; + } +@@ -3083,7 +3082,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_rebuild_free_space_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to rebuild free space tree: %d", ret); ++ "failed to rebuild free space tree: %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3094,7 +3093,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_delete_free_space_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to disable free space tree: %d", ret); ++ "failed to disable free space tree: %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3105,7 +3104,8 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + */ + ret = btrfs_delete_orphan_free_space_entries(fs_info); + if (ret < 0) { +- btrfs_err(fs_info, "failed to delete orphan free space tree entries: %d", ret); ++ btrfs_err(fs_info, "failed to delete orphan free space tree entries: %pe", ++ ERR_PTR(ret)); + return ret; + } + /* +@@ -3139,7 +3139,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_recover_relocation(fs_info); + mutex_unlock(&fs_info->cleaner_mutex); + if (ret < 0) { +- btrfs_warn(fs_info, "failed to recover relocation: %d", ret); ++ btrfs_warn(fs_info, "failed to recover relocation: %pe", ERR_PTR(ret)); + return ret; + } + +@@ -3149,7 +3149,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_create_free_space_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to create free space tree: %d", ret); ++ "failed to create free space tree: %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3177,7 +3177,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_create_uuid_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to create the UUID tree %d", ret); ++ "failed to create the UUID tree %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3314,6 +3314,8 @@ static void invalidate_and_check_btree_folios(struct btrfs_fs_info *fs_info) + */ + rcu_read_lock(); + xa_for_each(&fs_info->buffer_tree, index, eb) { ++ unsigned int refs; ++ + /* Increase the ref so that the eb won't disappear. */ + if (!refcount_inc_not_zero(&eb->refs)) + continue; +@@ -3323,17 +3325,27 @@ static void invalidate_and_check_btree_folios(struct btrfs_fs_info *fs_info) + if (test_bit(EXTENT_BUFFER_READING, &eb->bflags)) + wait_on_bit_io(&eb->bflags, EXTENT_BUFFER_READING, + TASK_UNINTERRUPTIBLE); ++ /* ++ * We hold the spinlock to make sure above ++ * EXTENT_BUFFER_READING flag is cleared with the held ++ * ref dropped. ++ * Or we can hit a race window and lead to false alerts. ++ */ ++ spin_lock(&eb->refs_lock); ++ refs = refcount_read(&eb->refs); ++ spin_unlock(&eb->refs_lock); ++ + /* + * The refs threshold is 2, one held by us at the beginning + * of the loop, one for the ownership in the buffer tree. + */ +- if (unlikely(refcount_read(&eb->refs) > 2 || extent_buffer_under_io(eb))) { ++ if (unlikely(refs > 2 || extent_buffer_under_io(eb))) { + WARN_ON_ONCE(IS_ENABLED(CONFIG_BTRFS_DEBUG)); + btrfs_warn(fs_info, + "unable to release extent buffer %llu owner %llu gen %llu refs %u flags 0x%lx", + eb->start, btrfs_header_owner(eb), + btrfs_header_generation(eb), +- refcount_read(&eb->refs), eb->bflags); ++ refs, eb->bflags); + } + free_extent_buffer(eb); + rcu_read_lock(); +@@ -3355,7 +3367,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + { + u32 sectorsize; + u32 nodesize; +- u32 stripesize; + u64 generation; + u16 csum_type; + struct btrfs_super_block *disk_super; +@@ -3464,7 +3475,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + /* Set up fs_info before parsing mount options */ + nodesize = btrfs_super_nodesize(disk_super); + sectorsize = btrfs_super_sectorsize(disk_super); +- stripesize = sectorsize; + fs_info->dirty_metadata_batch = nodesize * (1 + ilog2(nr_cpu_ids)); + fs_info->delalloc_batch = sectorsize * 512 * (1 + ilog2(nr_cpu_ids)); + +@@ -3483,7 +3493,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + else + fs_info->block_max_order = calc_block_max_order(fs_info->sectorsize_bits); + fs_info->csums_per_leaf = BTRFS_MAX_ITEM_SIZE(fs_info) / fs_info->csum_size; +- fs_info->stripesize = stripesize; + fs_info->fs_devices->fs_info = fs_info; + + if (fs_info->sectorsize > PAGE_SIZE) +@@ -3549,7 +3558,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + ret = btrfs_read_sys_array(fs_info); + mutex_unlock(&fs_info->chunk_mutex); + if (ret) { +- btrfs_err(fs_info, "failed to read the system array: %d", ret); ++ btrfs_err(fs_info, "failed to read the system array: %pe", ERR_PTR(ret)); + goto fail_sb_buffer; + } + +@@ -3568,7 +3577,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + + ret = btrfs_read_chunk_tree(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to read chunk tree: %d", ret); ++ btrfs_err(fs_info, "failed to read chunk tree: %pe", ERR_PTR(ret)); + goto fail_tree_roots; + } + +@@ -3598,7 +3607,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + ret = btrfs_get_dev_zone_info_all_devices(fs_info); + if (ret) { + btrfs_err(fs_info, +- "zoned: failed to read device zone info: %d", ret); ++ "zoned: failed to read device zone info: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + +@@ -3621,72 +3630,73 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + ret = btrfs_verify_dev_extents(fs_info); + if (ret) { + btrfs_err(fs_info, +- "failed to verify dev extents against chunks: %d", +- ret); ++ "failed to verify dev extents against chunks: %pe", ++ ERR_PTR(ret)); + goto fail_block_groups; + } + ret = btrfs_recover_balance(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to recover balance: %d", ret); ++ btrfs_err(fs_info, "failed to recover balance: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_init_dev_stats(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to init dev_stats: %d", ret); ++ btrfs_err(fs_info, "failed to init dev_stats: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_init_dev_replace(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to init dev_replace: %d", ret); ++ btrfs_err(fs_info, "failed to init dev_replace: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_check_zoned_mode(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to initialize zoned mode: %d", +- ret); ++ btrfs_err(fs_info, "failed to initialize zoned mode: %pe", ++ ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_sysfs_add_fsid(fs_devices); + if (ret) { +- btrfs_err(fs_info, "failed to init sysfs fsid interface: %d", +- ret); ++ btrfs_err(fs_info, "failed to init sysfs fsid interface: %pe", ++ ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_sysfs_add_mounted(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to init sysfs interface: %d", ret); ++ btrfs_err(fs_info, "failed to init sysfs interface: %pe", ERR_PTR(ret)); + goto fail_fsdev_sysfs; + } + + ret = btrfs_init_space_info(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to initialize space info: %d", ret); ++ btrfs_err(fs_info, "failed to initialize space info: %pe", ERR_PTR(ret)); + goto fail_sysfs; + } + + ret = btrfs_read_block_groups(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to read block groups: %d", ret); ++ btrfs_err(fs_info, "failed to read block groups: %pe", ERR_PTR(ret)); + goto fail_sysfs; + } + + if (btrfs_fs_incompat(fs_info, REMAP_TREE)) { + ret = btrfs_populate_fully_remapped_bgs_list(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to populate fully_remapped_bgs list: %d", ret); ++ btrfs_err(fs_info, "failed to populate fully_remapped_bgs list: %pe", ++ ERR_PTR(ret)); + goto fail_sysfs; + } + } + + ret = btrfs_init_writeback_bio_size(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to get optimum writeback size: %d", +- ret); ++ btrfs_err(fs_info, "failed to get optimum writeback size: %pe", ++ ERR_PTR(ret)); + goto fail_sysfs; + } + +@@ -3742,7 +3752,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + fs_info->fs_root = btrfs_get_fs_root(fs_info, BTRFS_FS_TREE_OBJECTID, true); + if (IS_ERR(fs_info->fs_root)) { + ret = PTR_ERR(fs_info->fs_root); +- btrfs_err(fs_info, "failed to read fs tree: %d", ret); ++ btrfs_err(fs_info, "failed to read fs tree: %pe", ERR_PTR(ret)); + fs_info->fs_root = NULL; + goto fail_qgroup; + } +@@ -3763,7 +3773,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + btrfs_info(fs_info, "checking UUID tree"); + ret = btrfs_check_uuid_tree(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to check the UUID tree: %d", ret); ++ btrfs_err(fs_info, "failed to check the UUID tree: %pe", ERR_PTR(ret)); + close_ctree(fs_info); + return ret; + } +@@ -3879,8 +3889,8 @@ static int write_dev_supers(struct btrfs_device *device, + continue; + } else if (ret < 0) { + btrfs_err(device->fs_info, +- "couldn't get super block location for mirror %d error %d", +- i, ret); ++ "couldn't get super block location for mirror %d error %pe", ++ i, ERR_PTR(ret)); + atomic_inc(&device->sb_write_errors); + continue; + } +@@ -3898,8 +3908,8 @@ static int write_dev_supers(struct btrfs_device *device, + GFP_NOFS); + if (IS_ERR(folio)) { + btrfs_err(device->fs_info, +- "couldn't get super block page for bytenr %llu error %ld", +- bytenr, PTR_ERR(folio)); ++ "couldn't get super block page for bytenr %llu error %pe", ++ bytenr, folio); + atomic_inc(&device->sb_write_errors); + continue; + } +@@ -4381,6 +4391,21 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) + */ + flush_workqueue(fs_info->fixup_workers); + ++ /* ++ * After we entered close_ctree() autodefrag could be running and before ++ * we parked the cleaner kthread, it dirtied folios of some inode. ++ * We don't want to leave any delalloc here, it may be flushed any time ++ * after this point and result in ordered extents that create delayed ++ * iputs after flushed the ordered extent queues further below, run ++ * delayed iputs and set BTRFS_FS_STATE_NO_DELAYED_IPUT. If we are ++ * mounted with flushoncommit, then btrfs_commit_super() called below ++ * will flush delalloc and wait for ordered extents but we end up ++ * getting delayed iputs than are never run. So flush delalloc and wait ++ * for ordered extents. ++ */ ++ btrfs_start_delalloc_roots(fs_info, LONG_MAX, false); ++ btrfs_wait_ordered_roots(fs_info, U64_MAX, NULL); ++ + /* + * Handle the error fs first, as it will flush and wait for all ordered + * extents. This will generate delayed iputs, thus we want to handle +@@ -4511,7 +4536,7 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) + if (!btrfs_is_shutdown(fs_info)) { + ret = btrfs_commit_super(fs_info); + if (ret) +- btrfs_err(fs_info, "commit super block returned %d", ret); ++ btrfs_err(fs_info, "commit super block returned %pe", ERR_PTR(ret)); + } + } + +@@ -4550,6 +4575,13 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) + free_root_pointers(fs_info, true); + btrfs_free_fs_roots(fs_info); + ++ /* ++ * Drop metadata left stranded ahead of a zone write pointer while the ++ * endio workqueues are still up, so the final iput() of the btree inode ++ * below does not hang submitting a write that can no longer complete. ++ */ ++ btrfs_zoned_release_dirty_metadata(fs_info); ++ + /* + * We must make sure there is not any read request to + * submit after we stop all workers. +@@ -4998,6 +5030,7 @@ static int btrfs_cleanup_transaction(struct btrfs_fs_info *fs_info) + btrfs_assert_delayed_root_empty(fs_info); + btrfs_destroy_all_delalloc_inodes(fs_info); + btrfs_drop_all_logs(fs_info); ++ btrfs_zoned_release_dirty_metadata(fs_info); + btrfs_free_all_qgroup_pertrans(fs_info); + mutex_unlock(&fs_info->transaction_kthread_mutex); + +diff --git a/fs/btrfs/disk-io.h b/fs/btrfs/disk-io.h +--- a/fs/btrfs/disk-io.h ++++ b/fs/btrfs/disk-io.h +@@ -15,6 +15,7 @@ + struct block_device; + struct super_block; + struct extent_buffer; ++struct btrfs_eb_prealloc; + struct btrfs_device; + struct btrfs_fs_devices; + struct btrfs_fs_info; +@@ -48,6 +49,7 @@ struct extent_buffer *read_tree_block(struct btrfs_fs_info *fs_info, u64 bytenr, + struct btrfs_tree_parent_check *check); + struct extent_buffer *btrfs_find_create_tree_block( + struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 bytenr, u64 owner_root, + int level); + int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info); +diff --git a/fs/btrfs/extent-io-tree.c b/fs/btrfs/extent-io-tree.c +--- a/fs/btrfs/extent-io-tree.c ++++ b/fs/btrfs/extent-io-tree.c +@@ -334,6 +334,21 @@ static inline struct extent_state *tree_search(struct extent_io_tree *tree, u64 + return tree_search_for_insert(tree, offset, NULL, NULL); + } + ++static void validate_extent_state(const struct extent_io_tree *tree, ++ const struct extent_state *state) ++{ ++ u32 blocksize; ++ ++ if (tree->owner != IO_TREE_INODE_IO) ++ return; ++ ++ blocksize = btrfs_extent_io_tree_to_fs_info(tree)->sectorsize; ++ ASSERT(IS_ALIGNED(state->start, blocksize) && ++ IS_ALIGNED(state->end + 1, blocksize), ++ "unaligned extent state, blocksize=%u start=%llu end=%llu state=0x%x", ++ blocksize, state->start, state->end, state->state); ++} ++ + #define extent_io_tree_panic(tree, state, opname, err) \ + btrfs_panic(btrfs_extent_io_tree_to_fs_info((tree)), (err), \ + "extent io tree error on %s state start %llu end %llu", \ +@@ -429,6 +444,8 @@ static struct extent_state *insert_state(struct extent_io_tree *tree, + const u64 end = state->end + 1; + const bool try_merge = !(bits & (EXTENT_LOCK_BITS | EXTENT_BOUNDARY)); + ++ validate_extent_state(tree, state); ++ + set_state_bits(tree, state, bits, changeset); + + node = &tree->state.rb_node; +@@ -481,6 +498,8 @@ static void insert_state_fast(struct extent_io_tree *tree, + struct rb_node *parent, unsigned bits, + struct extent_changeset *changeset) + { ++ validate_extent_state(tree, state); ++ + set_state_bits(tree, state, bits, changeset); + rb_link_node(&state->rb_node, parent, node); + rb_insert_color(&state->rb_node, &tree->state); +@@ -533,6 +552,8 @@ static int split_state(struct extent_io_tree *tree, struct extent_state *orig, + } + } + ++ validate_extent_state(tree, orig); ++ validate_extent_state(tree, prealloc); + rb_link_node(&prealloc->rb_node, parent, node); + rb_insert_color(&prealloc->rb_node, &tree->state); + +diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c +--- a/fs/btrfs/extent-tree.c ++++ b/fs/btrfs/extent-tree.c +@@ -4757,7 +4757,7 @@ static noinline int find_free_extent(struct btrfs_root *root, + + /* Checks */ + ffe_ctl->search_start = round_up(ffe_ctl->found_offset, +- fs_info->stripesize); ++ fs_info->sectorsize); + + /* move on to the next group */ + if (ffe_ctl->search_start + ffe_ctl->num_bytes > +@@ -5260,10 +5260,11 @@ btrfs_init_new_buffer(struct btrfs_trans_handle *trans, struct btrfs_root *root, + enum btrfs_lock_nesting nest) + { + struct btrfs_fs_info *fs_info = root->fs_info; ++ struct btrfs_eb_prealloc pa = { 0 }; + struct extent_buffer *buf; + u64 lockdep_owner = owner; + +- buf = btrfs_find_create_tree_block(fs_info, bytenr, owner, level); ++ buf = btrfs_find_create_tree_block(fs_info, &pa, bytenr, owner, level); + if (IS_ERR(buf)) + return buf; + +@@ -5880,8 +5881,8 @@ static int maybe_drop_reference(struct btrfs_trans_handle *trans, struct btrfs_r + ret = btrfs_qgroup_trace_subtree(trans, next, generation, level - 1); + if (ret) { + btrfs_err_rl(root->fs_info, +-"error %d accounting shared subtree, quota is out of sync, rescan required", +- ret); ++"error %pe accounting shared subtree, quota is out of sync, rescan required", ++ ERR_PTR(ret)); + } + } + +@@ -5917,6 +5918,7 @@ static noinline int do_walk_down(struct btrfs_trans_handle *trans, + struct walk_control *wc) + { + struct btrfs_fs_info *fs_info = root->fs_info; ++ struct btrfs_eb_prealloc pa = { 0 }; + u64 bytenr; + u64 generation; + u64 owner_root = 0; +@@ -5939,7 +5941,7 @@ static noinline int do_walk_down(struct btrfs_trans_handle *trans, + + bytenr = btrfs_node_blockptr(path->nodes[level], path->slots[level]); + +- next = btrfs_find_create_tree_block(fs_info, bytenr, btrfs_root_id(root), ++ next = btrfs_find_create_tree_block(fs_info, &pa, bytenr, btrfs_root_id(root), + level - 1); + if (IS_ERR(next)) + return PTR_ERR(next); +@@ -6096,8 +6098,8 @@ static noinline int walk_up_proc(struct btrfs_trans_handle *trans, + ret = btrfs_qgroup_trace_leaf_items(trans, eb); + if (ret) { + btrfs_err_rl(fs_info, +- "error %d accounting leaf items, quota is out of sync, rescan required", +- ret); ++ "error %pe accounting leaf items, quota is out of sync, rescan required", ++ ERR_PTR(ret)); + } + } + } +@@ -6498,8 +6500,8 @@ int btrfs_drop_snapshot(struct btrfs_root *root, bool update_ref, bool for_reloc + ret = btrfs_qgroup_cleanup_dropped_subvolume(fs_info, rootid); + if (ret < 0) + btrfs_warn_rl(fs_info, +- "failed to cleanup qgroup 0/%llu: %d", +- rootid, ret); ++ "failed to cleanup qgroup 0/%llu: %pe", ++ rootid, ERR_PTR(ret)); + ret = 0; + } + /* +@@ -6914,8 +6916,8 @@ int btrfs_trim_fs(struct btrfs_fs_info *fs_info, struct fstrim_range *range) + + if (bg_failed) + btrfs_warn(fs_info, +- "failed to trim %llu block group(s), first error %d", +- bg_failed, bg_ret); ++ "failed to trim %llu block group(s), first error %pe", ++ bg_failed, ERR_PTR(bg_ret)); + + if (ret == -ERESTARTSYS || ret == -EINTR) + return ret; +@@ -6925,8 +6927,8 @@ int btrfs_trim_fs(struct btrfs_fs_info *fs_info, struct fstrim_range *range) + + if (dev_failed) + btrfs_warn(fs_info, +- "failed to trim %llu device(s), first error %d", +- dev_failed, dev_ret); ++ "failed to trim %llu device(s), first error %pe", ++ dev_failed, ERR_PTR(dev_ret)); + range->len = trimmed; + if (ret == -ERESTARTSYS || ret == -EINTR) + return ret; +diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c +--- a/fs/btrfs/extent_io.c ++++ b/fs/btrfs/extent_io.c +@@ -1392,6 +1392,22 @@ static void lock_extents_for_read(struct btrfs_inode *inode, u64 start, u64 end, + } + } + ++static void assert_folio_range(const struct btrfs_inode *inode, ++ u64 start, u64 end) ++{ ++ const u32 blocksize = inode->root->fs_info->sectorsize; ++ ++ /* ++ * For btrfs page cache, a folio always contains at least one block, ++ * so the range should always be block size aligned. ++ */ ++ ASSERT(IS_ALIGNED(start, blocksize) && IS_ALIGNED(end + 1, blocksize), ++ "blocksize=%u root=%lld ino=%llu start=%llu end=%llu mapping min order=%u", ++ blocksize, btrfs_root_id(inode->root), btrfs_ino(inode), ++ start, end, ++ mapping_min_folio_order(inode->vfs_inode.i_mapping)); ++} ++ + int btrfs_read_folio(struct file *file, struct folio *folio) + { + struct inode *vfs_inode = folio->mapping->host; +@@ -1407,6 +1423,7 @@ int btrfs_read_folio(struct file *file, struct folio *folio) + struct fsverity_info *vi = NULL; + int ret; + ++ assert_folio_range(inode, start, end); + lock_extents_for_read(inode, start, end, &cached_state); + if (folio_pos(folio) < i_size_read(vfs_inode)) + vi = fsverity_get_info(vfs_inode); +@@ -1698,13 +1715,13 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode, + last_finished_delalloc_end = found_start + found_len; + if (unlikely(ret < 0)) + btrfs_err_rl(fs_info, +-"failed to run delalloc range, root=%lld ino=%llu folio=%llu submit_bitmap=%*pbl start=%llu len=%u: %d", ++"failed to run delalloc range, root=%lld ino=%llu folio=%llu submit_bitmap=%*pbl start=%llu len=%u: %pe", + btrfs_root_id(inode->root), + btrfs_ino(inode), + folio_pos(folio), + blocks_per_folio, + bio_ctrl->submit_bitmap, +- found_start, found_len, ret); ++ found_start, found_len, ERR_PTR(ret)); + } else { + /* + * We've hit an error during previous delalloc range, +@@ -1914,6 +1931,7 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, + ASSERT(start >= folio_start, "start=%llu folio_start=%llu", start, folio_start); + ASSERT(end <= folio_end, "start=%llu len=%u folio_start=%llu folio_size=%zu", + start, len, folio_start, folio_size(folio)); ++ assert_folio_range(inode, folio_start, folio_end - 1); + + /* + * We are about to checksum and write out the data, so it must not be +@@ -2081,10 +2099,10 @@ static int extent_writepage(struct folio *folio, struct btrfs_bio_ctrl *bio_ctrl + return 0; + if (unlikely(ret < 0)) + btrfs_err_rl(fs_info, +-"failed to submit blocks, root=%lld inode=%llu folio=%llu submit_bitmap=%*pbl: %d", ++"failed to submit blocks, root=%lld inode=%llu folio=%llu submit_bitmap=%*pbl: %pe", + btrfs_root_id(inode->root), btrfs_ino(inode), + folio_pos(folio), blocks_per_folio, +- bio_ctrl->submit_bitmap, ret); ++ bio_ctrl->submit_bitmap, ERR_PTR(ret)); + + bio_ctrl->wbc->nr_to_write--; + +@@ -2379,14 +2397,17 @@ static struct extent_buffer *find_extent_buffer_nolock( + static void end_bbio_meta_write(struct btrfs_bio *bbio) + { + struct extent_buffer *eb = bbio->private; +- struct folio_iter fi; + + if (bbio->bio.bi_status != BLK_STS_OK) + set_btree_ioerr(eb); + +- bio_for_each_folio_all(fi, &bbio->bio) { +- btrfs_meta_folio_clear_writeback(fi.folio, eb); +- } ++ /* ++ * Clear writeback on the buffer's own folios. The bio may carry the ++ * shared zero page instead (EXTENT_BUFFER_ZONED_ZEROOUT), so iterate ++ * the extent buffer folios rather than the bio folios. ++ */ ++ for (int i = 0; i < num_extent_folios(eb); i++) ++ btrfs_meta_folio_clear_writeback(eb->folios[i], eb); + + buffer_tree_clear_mark(eb, PAGECACHE_TAG_WRITEBACK); + clear_and_wake_up_bit(EXTENT_BUFFER_WRITEBACK, &eb->bflags); +@@ -2427,7 +2448,8 @@ static noinline_for_stack void write_one_eb(struct extent_buffer *eb, + struct btrfs_fs_info *fs_info = eb->fs_info; + struct btrfs_bio *bbio; + +- prepare_eb_write(eb); ++ if (!test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) ++ prepare_eb_write(eb); + + bbio = btrfs_bio_alloc(INLINE_EXTENT_BUFFER_PAGES, + REQ_OP_WRITE | REQ_META | wbc_to_write_flags(wbc), +@@ -2447,8 +2469,21 @@ static noinline_for_stack void write_one_eb(struct extent_buffer *eb, + btrfs_meta_folio_set_writeback(folio, eb); + if (!folio_test_dirty(folio)) + wbc->nr_to_write -= folio_nr_pages(folio); +- bio_add_folio_nofail(&bbio->bio, folio, range_len, +- offset_in_folio(folio, range_start)); ++ if (test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) { ++ u32 off = 0; ++ ++ while (off < range_len) { ++ u32 add = min_t(u32, PAGE_SIZE, range_len - off); ++ ++ bio_add_folio_nofail(&bbio->bio, ++ page_folio(ZERO_PAGE(0)), ++ add, 0); ++ off += add; ++ } ++ } else { ++ bio_add_folio_nofail(&bbio->bio, folio, range_len, ++ offset_in_folio(folio, range_start)); ++ } + wbc_account_cgroup_owner(wbc, folio, range_len); + folio_unlock(folio); + } +@@ -2496,6 +2531,76 @@ void btrfs_btree_wait_writeback_range(struct btrfs_fs_info *fs_info, u64 start, + } + } + ++static int write_meta_extent_buffer(struct btrfs_eb_write_context *ctx, ++ struct writeback_control *wbc) ++{ ++ struct extent_buffer *eb = ctx->eb; ++ int ret; ++ ++ ret = btrfs_check_meta_write_pointer(eb->fs_info, ctx); ++ if (ret) ++ return ret; ++ ++ if (!lock_extent_buffer_for_io(eb, wbc)) ++ return 0; ++ ++ /* Implies write in zoned mode. */ ++ if (ctx->zoned_bg) { ++ /* Mark the last eb in the block group. */ ++ btrfs_schedule_zone_finish_bg(ctx->zoned_bg, eb); ++ ctx->zoned_bg->meta_write_pointer += eb->len; ++ } ++ write_one_eb(eb, wbc); ++ return 0; ++} ++ ++/* ++ * On a zoned filesystem, write out the currently dirty metadata extent buffers ++ * of @bg. Used to flush the active metadata/system block group before the ++ * ascending-address walk in btree_writepages(), so that walk can pivot the ++ * active block group away (finishing it) instead of aborting the commit; see ++ * the caller for details. ++ */ ++static void flush_active_meta_bg(struct address_space *mapping, ++ struct writeback_control *wbc, ++ struct btrfs_eb_write_context *ctx, ++ struct btrfs_block_group *bg) ++{ ++ struct btrfs_fs_info *fs_info = inode_to_fs_info(mapping->host); ++ unsigned long index = bg->start >> fs_info->nodesize_bits; ++ unsigned long end = (btrfs_block_group_end(bg) - 1) >> fs_info->nodesize_bits; ++ struct eb_batch batch; ++ unsigned int nr_ebs; ++ ++ ASSERT(btrfs_is_zoned(fs_info)); ++ lockdep_assert_held(&fs_info->zoned_meta_io_lock); ++ ++ eb_batch_init(&batch); ++ while (index <= end && ++ (nr_ebs = buffer_tree_get_ebs_tag(fs_info, &index, end, ++ PAGECACHE_TAG_DIRTY, &batch))) { ++ struct extent_buffer *eb; ++ ++ while ((eb = eb_batch_next(&batch)) != NULL) { ++ ctx->eb = eb; ++ ++ /* ++ * If the eb is behind the write pointer (-EBUSY, e.g. ++ * already being written by someone else) skip it and ++ * carry on. Only a hole at the write pointer (-EAGAIN) ++ * stops the flush. The main walk in btree_writepages() ++ * then deals with it. ++ */ ++ if (write_meta_extent_buffer(ctx, wbc) == -EAGAIN) { ++ eb_batch_release(&batch); ++ return; ++ } ++ } ++ eb_batch_release(&batch); ++ cond_resched(); ++ } ++} ++ + int btree_writepages(struct address_space *mapping, struct writeback_control *wbc) + { + struct btrfs_eb_write_context ctx = { .wbc = wbc }; +@@ -2531,6 +2636,22 @@ int btree_writepages(struct address_space *mapping, struct writeback_control *wb + else + tag = PAGECACHE_TAG_DIRTY; + btrfs_zoned_meta_io_lock(fs_info); ++ ++ /* ++ * On a zoned filesystem, flush the currently active metadata/system ++ * block group(s) first, under this same lock, so the ascending-address ++ * walk below can pivot the active block group instead of aborting the ++ * transaction commit with -EAGAIN. ++ */ ++ if (btrfs_is_zoned(fs_info) && wbc->sync_mode == WB_SYNC_ALL && ++ !wbc->for_sync) { ++ if (fs_info->active_meta_bg) ++ flush_active_meta_bg(mapping, wbc, &ctx, ++ fs_info->active_meta_bg); ++ if (fs_info->active_system_bg) ++ flush_active_meta_bg(mapping, wbc, &ctx, ++ fs_info->active_system_bg); ++ } + retry: + if (wbc->sync_mode == WB_SYNC_ALL) + buffer_tree_tag_for_writeback(fs_info, index, end); +@@ -2541,28 +2662,13 @@ int btree_writepages(struct address_space *mapping, struct writeback_control *wb + while ((eb = eb_batch_next(&batch)) != NULL) { + ctx.eb = eb; + +- ret = btrfs_check_meta_write_pointer(eb->fs_info, &ctx); +- if (ret) { +- if (ret == -EBUSY) +- ret = 0; +- +- if (ret) { +- done = true; +- break; +- } +- continue; ++ ret = write_meta_extent_buffer(&ctx, wbc); ++ if (ret == -EBUSY) { ++ ret = 0; ++ } else if (ret) { ++ done = true; ++ break; + } +- +- if (!lock_extent_buffer_for_io(eb, wbc)) +- continue; +- +- /* Implies write in zoned mode. */ +- if (ctx.zoned_bg) { +- /* Mark the last eb in the block group. */ +- btrfs_schedule_zone_finish_bg(ctx.zoned_bg, eb); +- ctx.zoned_bg->meta_write_pointer += eb->len; +- } +- write_one_eb(eb, wbc); + } + nr_to_write_done = (wbc->nr_to_write <= 0); + eb_batch_release(&batch); +@@ -2888,13 +2994,25 @@ void btrfs_readahead(struct readahead_control *rac) + struct extent_map *em_cached = NULL; + struct fsverity_info *vi = NULL; + ++ assert_folio_range(inode, start, end); + lock_extents_for_read(inode, start, end, &cached_state); ++ /* We don't use cached state for a bulk unlock, just free it. */ ++ btrfs_free_extent_state(cached_state); + if (start < i_size_read(vfs_inode)) + vi = fsverity_get_info(vfs_inode); +- while ((folio = readahead_folio(rac)) != NULL) +- btrfs_do_readpage(folio, &em_cached, &bio_ctrl, vi); ++ while ((folio = readahead_folio(rac)) != NULL) { ++ /* ++ * Read start and end before btrfs_do_readpage(). It unlocks the ++ * folio, so our reference might not be valid after. ++ */ ++ const u64 folio_start = folio_pos(folio); ++ const u64 folio_end = folio_start + folio_size(folio) - 1; + +- btrfs_unlock_extent(&inode->io_tree, start, end, &cached_state); ++ btrfs_do_readpage(folio, &em_cached, &bio_ctrl, vi); ++ /* Only unlock the range we locked, even if readahead expands. */ ++ if (folio_start >= start && folio_end <= end) ++ btrfs_unlock_extent(&inode->io_tree, folio_start, folio_end, NULL); ++ } + + if (em_cached) + btrfs_free_extent_map(em_cached); +@@ -3129,47 +3247,71 @@ static inline void btrfs_release_extent_buffer(struct extent_buffer *eb) + kmem_cache_free(extent_buffer_cache, eb); + } + ++/* ++ * Claim a slot to track an extent buffer in, evicting the coldest tracked buffer ++ * when the array is full. ++ * ++ * Slots fill in order until the array is full. After that a CLOCK (second ++ * chance) scan advances the hand, clearing one reference bit per step, until ++ * it lands on an unreferenced slot whose buffer is evicted. Clearing a bit per ++ * step bounds the scan to BTRFS_INHIBITED_EBS_SLOTS iterations. ++ */ ++static int btrfs_inhibit_claim_slot(struct btrfs_trans_handle *trans) ++{ ++ int slot; ++ ++ if (trans->nr_inhibited_ebs < BTRFS_INHIBITED_EBS_SLOTS) ++ return trans->nr_inhibited_ebs++; ++ ++ while (trans->inhibited_ebs_referenced & (1U << trans->inhibited_ebs_hand)) { ++ trans->inhibited_ebs_referenced &= ~(1U << trans->inhibited_ebs_hand); ++ trans->inhibited_ebs_hand = ++ (trans->inhibited_ebs_hand + 1) % BTRFS_INHIBITED_EBS_SLOTS; ++ } ++ slot = trans->inhibited_ebs_hand; ++ trans->inhibited_ebs_hand = (trans->inhibited_ebs_hand + 1) % BTRFS_INHIBITED_EBS_SLOTS; ++ ++ atomic_dec(&trans->inhibited_ebs[slot]->writeback_inhibitors); ++ free_extent_buffer(trans->inhibited_ebs[slot]); ++ ++ return slot; ++} ++ + /* + * Inhibit writeback on buffer during transaction. + * + * @trans: transaction handle that will own the inhibitor + * @eb: extent buffer to inhibit writeback on + * +- * Attempt to track this extent buffer in the transaction's inhibited set. If +- * memory allocation fails, the buffer is simply not tracked. It may be written +- * back and need re-COW, which is the original behavior. This is acceptable +- * since inhibiting writeback is an optimization. ++ * Attempt to track this extent buffer in the transaction's inhibited set. When ++ * the set is full the coldest tracked buffer is evicted instead. An untracked ++ * buffer may be written back and need re-COW, which is the original behavior. ++ * This is acceptable since inhibiting writeback is an optimization. + */ + void btrfs_inhibit_eb_writeback(struct btrfs_trans_handle *trans, struct extent_buffer *eb) + { +- unsigned long index = eb->start >> trans->fs_info->nodesize_bits; +- void *old; ++ int slot; + + lockdep_assert_held(&eb->lock); +- /* Check if already inhibited by this handle. */ +- old = xa_load(&trans->writeback_inhibited_ebs, index); +- if (old == eb) +- return; + +- /* Take reference for the xarray entry. */ ++ /* Already tracked: set its reference bit (second chance) and return. */ ++ for (int i = 0; i < trans->nr_inhibited_ebs; i++) { ++ if (trans->inhibited_ebs[i] == eb) { ++ trans->inhibited_ebs_referenced |= 1U << i; ++ return; ++ } ++ } ++ ++ slot = btrfs_inhibit_claim_slot(trans); ++ ++ /* ++ * Pin the eb while the array holds a raw pointer to it; the counter is ++ * what lock_extent_buffer_for_io() checks. ++ */ + refcount_inc(&eb->refs); +- +- old = xa_store(&trans->writeback_inhibited_ebs, index, eb, GFP_NOFS); +- if (xa_is_err(old)) { +- /* Allocation failed, just skip inhibiting this buffer. */ +- free_extent_buffer(eb); +- return; +- } +- +- /* Handle replacement of different eb at same index. */ +- if (old && old != eb) { +- struct extent_buffer *old_eb = old; +- +- atomic_dec(&old_eb->writeback_inhibitors); +- free_extent_buffer(old_eb); +- } +- + atomic_inc(&eb->writeback_inhibitors); ++ trans->inhibited_ebs[slot] = eb; ++ trans->inhibited_ebs_referenced |= 1U << slot; + } + + /* +@@ -3177,22 +3319,18 @@ void btrfs_inhibit_eb_writeback(struct btrfs_trans_handle *trans, struct extent_ + */ + void btrfs_uninhibit_all_eb_writeback(struct btrfs_trans_handle *trans) + { +- struct extent_buffer *eb; +- unsigned long index; +- +- xa_for_each(&trans->writeback_inhibited_ebs, index, eb) { +- atomic_dec(&eb->writeback_inhibitors); +- free_extent_buffer(eb); ++ for (int i = 0; i < trans->nr_inhibited_ebs; i++) { ++ atomic_dec(&trans->inhibited_ebs[i]->writeback_inhibitors); ++ free_extent_buffer(trans->inhibited_ebs[i]); + } +- xa_destroy(&trans->writeback_inhibited_ebs); ++ trans->nr_inhibited_ebs = 0; ++ trans->inhibited_ebs_referenced = 0; ++ trans->inhibited_ebs_hand = 0; + } + +-static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info, +- u64 start) ++static void init_extent_buffer(struct btrfs_fs_info *fs_info, ++ struct extent_buffer *eb, u64 start) + { +- struct extent_buffer *eb = NULL; +- +- eb = kmem_cache_zalloc(extent_buffer_cache, GFP_NOFS|__GFP_NOFAIL); + eb->start = start; + eb->len = fs_info->nodesize; + eb->fs_info = fs_info; +@@ -3205,7 +3343,15 @@ static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info + refcount_set(&eb->refs, 1); + + ASSERT(eb->len <= BTRFS_MAX_METADATA_BLOCKSIZE); ++} + ++static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info, ++ u64 start) ++{ ++ struct extent_buffer *eb; ++ ++ eb = kmem_cache_zalloc(extent_buffer_cache, GFP_NOFS | __GFP_NOFAIL); ++ init_extent_buffer(fs_info, eb, start); + return eb; + } + +@@ -3502,7 +3648,7 @@ static bool check_eb_alignment(struct btrfs_fs_info *fs_info, u64 start) + * The caller needs to free the existing folios and retry using the same order. + */ + static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, +- struct btrfs_folio_state *prealloc, ++ struct btrfs_eb_prealloc *pa, + struct extent_buffer **found_eb_ret) + { + +@@ -3524,6 +3670,7 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + if (!ret) + goto finish; + ++ /* ret == -EEXIST: a folio already lives at this index. */ + existing_folio = filemap_lock_folio(mapping, index + i); + /* The page cache only exists for a very short time, just retry. */ + if (IS_ERR(existing_folio)) +@@ -3532,7 +3679,27 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + /* For now, we should only have single-page folios for btree inode. */ + ASSERT(folio_nr_pages(existing_folio) == 1); + ++ /* ++ * TODO: Special handling for a corner case where the order of ++ * folios mismatch between the new eb and filemap. ++ * ++ * This happens when: ++ * ++ * - the new eb is using higher order folio ++ * ++ * - the filemap is still using 0-order folios for the range ++ * This can happen at the previous eb allocation, and we don't ++ * have higher order folio for the call. ++ * ++ * - the existing eb has already been freed ++ * ++ * In this case, we have to free the existing folios first, and ++ * re-allocate using the same order. ++ * Thankfully this is not going to happen yet, as we're still ++ * using 0-order folios. ++ */ + if (folio_size(existing_folio) != eb->folio_size) { ++ DEBUG_WARN("folio order mismatch between new eb and filemap"); + folio_unlock(existing_folio); + folio_put(existing_folio); + return -EAGAIN; +@@ -3563,8 +3730,10 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + eb->folio_size = folio_size(eb->folios[i]); + eb->folio_shift = folio_shift(eb->folios[i]); + /* Should not fail, as we have preallocated the memory. */ +- ret = attach_extent_buffer_folio(eb, eb->folios[i], prealloc); ++ ret = attach_extent_buffer_folio(eb, eb->folios[i], pa->bfs); + ASSERT(!ret); ++ /* The subpage state, if any, is now attached to the folio or freed. */ ++ pa->bfs = NULL; + /* + * To inform we have an extra eb under allocation, so that + * detach_extent_buffer_page() won't release the folio private when the +@@ -3579,13 +3748,104 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + return 0; + } + ++/* ++ * Allocate the extent_buffer, its folios, and btrfs_folio_state, if needed. ++ * ++ * @pa: The holder struct to do the allocation in. ++ * @nowait: Whether to do a speculative GFP_NOWAIT allocation while holding locks. ++ * ++ * Return 0 on success and a negative errno otherwise. On failure, pa->eb/bfs ++ * will be NULL. If @nowait=true, then on ENOMEM, mark @pa->needs_prealloc and ++ * return -EAGAIN to signal the caller to unlock and retry. ++ */ ++int btrfs_init_eb_prealloc(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, bool nowait) ++{ ++ gfp_t gfp = nowait ? GFP_NOWAIT : GFP_NOFS | __GFP_NOFAIL; ++ int ret; ++ ++ ASSERT(!pa->eb, "unexpected non-null eb: %p", pa->eb); ++ ASSERT(!pa->bfs, "unexpected non-null bfs: %p", pa->bfs); ++ pa->needs_prealloc = false; ++ ++ pa->eb = kmem_cache_zalloc(extent_buffer_cache, gfp); ++ if (!pa->eb) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ /* alloc_eb_folio_array() needs len; init_extent_buffer() sets it again later. */ ++ pa->eb->len = fs_info->nodesize; ++ ++ /* ++ * Preallocate folio private for subpage case, so that we won't ++ * allocate memory with i_private_lock nor page lock hold. ++ * ++ * The memory will be freed by attach_extent_buffer_page() or freed ++ * manually if we exit earlier. ++ */ ++ if (btrfs_meta_is_subpage(fs_info)) { ++ pa->bfs = btrfs_alloc_folio_state(fs_info, PAGE_SIZE, ++ BTRFS_SUBPAGE_METADATA, gfp); ++ if (IS_ERR(pa->bfs)) { ++ ret = PTR_ERR(pa->bfs); ++ pa->bfs = NULL; ++ goto free_eb; ++ } ++ } ++ ++ /* ++ * Allocate pages without attaching them. Caller is ultimately responsible ++ * for attaching the folios to the mapping with attach_eb_folio_to_filemap(). ++ */ ++ ret = alloc_eb_folio_array(pa->eb, gfp | __GFP_MOVABLE); ++ if (ret < 0) ++ goto free_bfs; ++ ++ return 0; ++ ++free_bfs: ++ btrfs_free_folio_state(pa->bfs); ++ pa->bfs = NULL; ++free_eb: ++ kmem_cache_free(extent_buffer_cache, pa->eb); ++ pa->eb = NULL; ++out: ++ if (nowait && ret == -ENOMEM) { ++ pa->needs_prealloc = true; ++ ret = -EAGAIN; ++ } ++ return ret; ++} ++ ++/* ++ * Used to cleanup a btrfs_eb_prealloc which had its contents allocated but ++ * folios not yet attached and eb/bfs consumed, and refs still 0. ++ * ++ * Safe to call on a fully used btrfs_eb_prealloc as the internal structs will ++ * be null once they are owned by the context using them. ++ */ ++void btrfs_free_eb_prealloc(struct btrfs_eb_prealloc *pa) ++{ ++ if (!pa->eb) ++ return; ++ ++ for (int i = 0; i < num_extent_pages(pa->eb); i++) { ++ if (pa->eb->folios[i]) ++ folio_put(pa->eb->folios[i]); ++ } ++ btrfs_free_folio_state(pa->bfs); ++ kmem_cache_free(extent_buffer_cache, pa->eb); ++ pa->eb = NULL; ++ pa->bfs = NULL; ++} ++ + struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 start, u64 owner_root, int level) + { + int attached = 0; + struct extent_buffer *eb; + struct extent_buffer *existing_eb = NULL; +- struct btrfs_folio_state *prealloc = NULL; + u64 lockdep_owner = owner_root; + bool page_contig = true; + bool uptodate = true; +@@ -3609,9 +3869,14 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, + if (eb) + return eb; + +- eb = __alloc_extent_buffer(fs_info, start); +- if (!eb) +- return ERR_PTR(-ENOMEM); ++ if (!pa->eb) { ++ ret = btrfs_init_eb_prealloc(fs_info, pa, pa->supports_nowait); ++ if (ret) ++ return ERR_PTR(ret); ++ } ++ eb = pa->eb; ++ pa->eb = NULL; ++ init_extent_buffer(fs_info, eb, start); + + /* + * The reloc trees are just snapshots, so we need them to appear to be +@@ -3622,66 +3887,18 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, + + btrfs_set_buffer_lockdep_class(lockdep_owner, eb, level); + +- /* +- * Preallocate folio private for subpage case, so that we won't +- * allocate memory with i_private_lock nor page lock hold. +- * +- * The memory will be freed by attach_extent_buffer_page() or freed +- * manually if we exit earlier. +- */ +- if (btrfs_meta_is_subpage(fs_info)) { +- prealloc = btrfs_alloc_folio_state(fs_info, PAGE_SIZE, BTRFS_SUBPAGE_METADATA); +- if (IS_ERR(prealloc)) { +- ret = PTR_ERR(prealloc); +- goto out; +- } +- } +- +-reallocate: +- /* +- * Allocate all pages first. These will be attached to btree_inode->i_mapping +- * below (added to LRU, served by btree_migrate_folio), so request +- * __GFP_MOVABLE so the page allocator places them in MOVABLE pageblocks. +- */ +- ret = alloc_eb_folio_array(eb, GFP_NOFS | __GFP_NOFAIL | __GFP_MOVABLE); +- if (ret < 0) { +- btrfs_free_folio_state(prealloc); +- goto out; +- } +- + /* Attach all pages to the filemap. */ + for (int i = 0; i < num_extent_folios(eb); i++) { + struct folio *folio; + +- ret = attach_eb_folio_to_filemap(eb, i, prealloc, &existing_eb); ++ ret = attach_eb_folio_to_filemap(eb, i, pa, &existing_eb); + if (ret > 0) { + ASSERT(existing_eb); + goto out; + } +- +- /* +- * TODO: Special handling for a corner case where the order of +- * folios mismatch between the new eb and filemap. +- * +- * This happens when: +- * +- * - the new eb is using higher order folio +- * +- * - the filemap is still using 0-order folios for the range +- * This can happen at the previous eb allocation, and we don't +- * have higher order folio for the call. +- * +- * - the existing eb has already been freed +- * +- * In this case, we have to free the existing folios first, and +- * re-allocate using the same order. +- * Thankfully this is not going to happen yet, as we're still +- * using 0-order folios. +- */ +- if (unlikely(ret == -EAGAIN)) { +- DEBUG_WARN("folio order mismatch between new eb and filemap"); +- goto reallocate; +- } ++ /* -EAGAIN: folio order mismatch, unreachable with 0-order folios. */ ++ if (ret < 0) ++ goto out; + attached++; + + /* +@@ -3758,6 +3975,10 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, + out: + WARN_ON(!refcount_dec_and_test(&eb->refs)); + ++ /* Attach hands off pa->bfs; free it if we bailed first. */ ++ btrfs_free_folio_state(pa->bfs); ++ pa->bfs = NULL; ++ + /* + * Any attached folios need to be detached before we unlock them. This + * is because when we're inserting our new folios into the mapping, and +@@ -3842,12 +4063,31 @@ static int release_extent_buffer(struct extent_buffer *eb) + return 0; + } + +-void free_extent_buffer(struct extent_buffer *eb) ++static void clear_extent_buffer_reading(struct extent_buffer *eb) ++{ ++ clear_and_wake_up_bit(EXTENT_BUFFER_READING, &eb->bflags); ++} ++ ++static void free_extent_buffer_clear_reading(struct extent_buffer *eb, ++ bool clear_reading) + { + int refs; ++ + if (!eb) + return; + ++ /* ++ * We want to clear EXTENT_BUFFER_READING flag and decrease refs ++ * in the same critical section. ++ * This will make sure invalidate_and_check_btree_folios() won't ++ * see an eb with EXTENT_BUFFER_READING cleared but refs not yet ++ * decreased. ++ */ ++ if (clear_reading) { ++ spin_lock(&eb->refs_lock); ++ clear_extent_buffer_reading(eb); ++ } ++ + refs = refcount_read(&eb->refs); + while (1) { + if (test_bit(EXTENT_BUFFER_UNMAPPED, &eb->bflags)) { +@@ -3858,11 +4098,16 @@ void free_extent_buffer(struct extent_buffer *eb) + } + + /* Optimization to avoid locking eb->refs_lock. */ +- if (atomic_try_cmpxchg(&eb->refs.refs, &refs, refs - 1)) ++ if (atomic_try_cmpxchg(&eb->refs.refs, &refs, refs - 1)) { ++ if (clear_reading) ++ spin_unlock(&eb->refs_lock); + return; ++ } + } + +- spin_lock(&eb->refs_lock); ++ if (!clear_reading) ++ spin_lock(&eb->refs_lock); ++ + if (refcount_read(&eb->refs) == 2 && + test_bit(EXTENT_BUFFER_STALE, &eb->bflags) && + !extent_buffer_under_io(eb) && +@@ -3876,6 +4121,11 @@ void free_extent_buffer(struct extent_buffer *eb) + release_extent_buffer(eb); + } + ++void free_extent_buffer(struct extent_buffer *eb) ++{ ++ return free_extent_buffer_clear_reading(eb, false); ++} ++ + void free_extent_buffer_stale(struct extent_buffer *eb) + { + if (!eb) +@@ -3890,6 +4140,32 @@ void free_extent_buffer_stale(struct extent_buffer *eb) + release_extent_buffer(eb); + } + ++static void clear_extent_buffer_dirty(struct extent_buffer *eb) ++{ ++ struct btrfs_fs_info *fs_info = eb->fs_info; ++ ++ if (!test_and_clear_bit(EXTENT_BUFFER_DIRTY, &eb->bflags)) ++ return; ++ ++ buffer_tree_clear_mark(eb, PAGECACHE_TAG_DIRTY); ++ percpu_counter_add_batch(&fs_info->dirty_metadata_bytes, -(s64)eb->len, ++ fs_info->dirty_metadata_batch); ++ ++ for (int i = 0; i < num_extent_folios(eb); i++) { ++ struct folio *folio = eb->folios[i]; ++ bool last; ++ ++ if (!folio_test_dirty(folio)) ++ continue; ++ folio_lock(folio); ++ last = btrfs_meta_folio_clear_and_test_dirty(folio, eb); ++ if (last) ++ btrfs_clear_folio_dirty_tag(folio); ++ folio_unlock(folio); ++ } ++ WARN_ON(refcount_read(&eb->refs) == 0); ++} ++ + void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans, + struct extent_buffer *eb) + { +@@ -3914,26 +4190,42 @@ void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans, + return; + } + +- if (!test_and_clear_bit(EXTENT_BUFFER_DIRTY, &eb->bflags)) ++ clear_extent_buffer_dirty(eb); ++} ++ ++/* ++ * On a zoned filesystem a freed tree block is kept dirty and flagged as ++ * EXTENT_BUFFER_ZONED_ZEROOUT so a later writeback zeroes it out and advances ++ * the zone write pointer. Such buffers still dirty when the filesystem is torn ++ * down can no longer be written back and are stale; if left dirty they hang the ++ * final iput() of the btree inode. Drop their dirty state, and the deferred ++ * zero-out along with it. ++ */ ++void btrfs_zoned_release_dirty_metadata(struct btrfs_fs_info *fs_info) ++{ ++ struct eb_batch batch; ++ unsigned long index = 0; ++ ++ if (!btrfs_is_zoned(fs_info)) + return; + +- buffer_tree_clear_mark(eb, PAGECACHE_TAG_DIRTY); +- percpu_counter_add_batch(&fs_info->dirty_metadata_bytes, -(s64)eb->len, +- fs_info->dirty_metadata_batch); ++ btrfs_zoned_meta_io_lock(fs_info); ++ eb_batch_init(&batch); ++ while (buffer_tree_get_ebs_tag(fs_info, &index, ULONG_MAX, ++ PAGECACHE_TAG_DIRTY, &batch)) { ++ struct extent_buffer *eb; + +- for (int i = 0; i < num_extent_folios(eb); i++) { +- struct folio *folio = eb->folios[i]; +- bool last; +- +- if (!folio_test_dirty(folio)) +- continue; +- folio_lock(folio); +- last = btrfs_meta_folio_clear_and_test_dirty(folio, eb); +- if (last) +- btrfs_clear_folio_dirty_tag(folio); +- folio_unlock(folio); ++ while ((eb = eb_batch_next(&batch)) != NULL) { ++ btrfs_tree_lock(eb); ++ if (test_and_clear_bit(EXTENT_BUFFER_ZONED_ZEROOUT, ++ &eb->bflags)) ++ clear_extent_buffer_dirty(eb); ++ btrfs_tree_unlock(eb); ++ } ++ eb_batch_release(&batch); ++ cond_resched(); + } +- WARN_ON(refcount_read(&eb->refs) == 0); ++ btrfs_zoned_meta_io_unlock(fs_info); + } + + void set_extent_buffer_dirty(struct extent_buffer *eb) +@@ -4001,11 +4293,6 @@ void set_extent_buffer_uptodate(struct extent_buffer *eb) + btrfs_meta_folio_set_uptodate(eb->folios[i], eb); + } + +-static void clear_extent_buffer_reading(struct extent_buffer *eb) +-{ +- clear_and_wake_up_bit(EXTENT_BUFFER_READING, &eb->bflags); +-} +- + static void end_bbio_meta_read(struct btrfs_bio *bbio) + { + struct extent_buffer *eb = bbio->private; +@@ -4029,8 +4316,7 @@ static void end_bbio_meta_read(struct btrfs_bio *bbio) + else + clear_extent_buffer_uptodate(eb); + +- clear_extent_buffer_reading(eb); +- free_extent_buffer(eb); ++ free_extent_buffer_clear_reading(eb, true); + + bio_put(&bbio->bio); + } +@@ -4789,6 +5075,8 @@ void btrfs_readahead_tree_block(struct btrfs_fs_info *fs_info, + .level = level, + .transid = gen + }; ++ /* Readahead is best effort so prefer to fail rather than block in reclaim. */ ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + struct extent_buffer *eb; + int ret; + +@@ -4797,7 +5085,7 @@ void btrfs_readahead_tree_block(struct btrfs_fs_info *fs_info, + check.has_first_key = true; + } + +- eb = btrfs_find_create_tree_block(fs_info, bytenr, owner_root, level); ++ eb = btrfs_find_create_tree_block(fs_info, &pa, bytenr, owner_root, level); + if (IS_ERR(eb)) + return; + +diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h +--- a/fs/btrfs/extent_io.h ++++ b/fs/btrfs/extent_io.h +@@ -119,6 +119,25 @@ struct extent_buffer { + #endif + }; + ++/* ++ * Wrapper struct for managing preallocating an extent_buffer, its folios and a ++ * btrfs_folio_state if needed. ++ * ++ * Only used to mediate allocation, do not refer to the eb directly if not ++ * returned from a successful eb allocating API. ++ * ++ * The eb folios and bfs should generally not be fully attached, except briefly ++ * before they are NULLed in the struct after successful attachment. ++ */ ++struct btrfs_eb_prealloc { ++ struct extent_buffer *eb; ++ struct btrfs_folio_state *bfs; ++ /* eb alloc may use GFP_NOWAIT; caller can drop locks and retry. */ ++ bool supports_nowait; ++ /* GFP_NOWAIT eb alloc failed; preallocate again and retry. */ ++ bool needs_prealloc; ++}; ++ + struct btrfs_eb_write_context { + struct writeback_control *wbc; + struct extent_buffer *eb; +@@ -271,7 +290,11 @@ int set_folio_extent_mapped(struct folio *folio); + void clear_folio_extent_mapped(struct folio *folio); + + struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 start, u64 owner_root, int level); ++int btrfs_init_eb_prealloc(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, bool nowait); ++void btrfs_free_eb_prealloc(struct btrfs_eb_prealloc *pa); + struct extent_buffer *alloc_dummy_extent_buffer(struct btrfs_fs_info *fs_info, + u64 start); + struct extent_buffer *btrfs_clone_extent_buffer(const struct extent_buffer *src); +@@ -393,6 +416,7 @@ void extent_clear_unlock_delalloc(struct btrfs_inode *inode, u64 start, u64 end, + u32 bits_to_clear, unsigned long page_ops); + void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans, + struct extent_buffer *buf); ++void btrfs_zoned_release_dirty_metadata(struct btrfs_fs_info *fs_info); + + static inline void btrfs_clear_folio_dirty_tag(struct folio *folio) + { +diff --git a/fs/btrfs/fiemap.c b/fs/btrfs/fiemap.c +--- a/fs/btrfs/fiemap.c ++++ b/fs/btrfs/fiemap.c +@@ -641,7 +641,7 @@ static int extent_fiemap(struct btrfs_inode *inode, + u64 prev_extent_end; + u64 range_start; + u64 range_end; +- const u64 sectorsize = inode->root->fs_info->sectorsize; ++ const u32 sectorsize = inode->root->fs_info->sectorsize; + bool stopped = false; + int ret; + +@@ -660,7 +660,7 @@ static int extent_fiemap(struct btrfs_inode *inode, + range_end = round_up(start + len, sectorsize); + prev_extent_end = range_start; + +- btrfs_lock_extent(&inode->io_tree, range_start, range_end, &cached_state); ++ btrfs_lock_extent(&inode->io_tree, range_start, range_end - 1, &cached_state); + + ret = fiemap_find_last_extent_offset(inode, path, &last_extent_end); + if (ret < 0) +@@ -840,7 +840,7 @@ static int extent_fiemap(struct btrfs_inode *inode, + } + + out_unlock: +- btrfs_unlock_extent(&inode->io_tree, range_start, range_end, &cached_state); ++ btrfs_unlock_extent(&inode->io_tree, range_start, range_end - 1, &cached_state); + + if (ret == BTRFS_FIEMAP_FLUSH_CACHE) { + btrfs_release_path(path); +diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c +--- a/fs/btrfs/file.c ++++ b/fs/btrfs/file.c +@@ -875,70 +875,64 @@ static noinline int prepare_one_folio(struct inode *inode, struct folio **folio_ + + /* + * Locks the extent and properly waits for data=ordered extents to finish +- * before allowing the folios to be modified if need. ++ * before allowing the folios to be modified. + * + * Return: +- * 1 - the extent is locked +- * 0 - the extent is not locked, and everything is OK ++ * 0 - the extent is locked + * -EAGAIN - need to prepare the folios again + */ + static noinline int +-lock_and_cleanup_extent_if_need(struct btrfs_inode *inode, struct folio *folio, +- loff_t pos, size_t write_bytes, +- u64 *lockstart, u64 *lockend, bool nowait, +- struct extent_state **cached_state) ++lock_and_cleanup_extent(struct btrfs_inode *inode, struct folio *folio, ++ loff_t pos, size_t write_bytes, ++ u64 *lockstart, u64 *lockend, bool nowait, ++ struct extent_state **cached_state) + { + struct btrfs_fs_info *fs_info = inode->root->fs_info; ++ struct btrfs_ordered_extent *ordered; + u64 start_pos; + u64 last_pos; +- int ret = 0; + + start_pos = round_down(pos, fs_info->sectorsize); + last_pos = round_up(pos + write_bytes, fs_info->sectorsize) - 1; + +- if (start_pos < inode->vfs_inode.i_size) { +- struct btrfs_ordered_extent *ordered; +- +- if (nowait) { +- if (!btrfs_try_lock_extent(&inode->io_tree, start_pos, +- last_pos, cached_state)) { +- folio_unlock(folio); +- folio_put(folio); +- return -EAGAIN; +- } +- } else { +- btrfs_lock_extent(&inode->io_tree, start_pos, last_pos, +- cached_state); +- } +- +- ordered = btrfs_lookup_ordered_range(inode, start_pos, +- last_pos - start_pos + 1); +- if (ordered && +- ordered->file_offset + ordered->num_bytes > start_pos && +- ordered->file_offset <= last_pos) { +- btrfs_unlock_extent(&inode->io_tree, start_pos, last_pos, +- cached_state); ++ if (nowait) { ++ if (!btrfs_try_lock_extent(&inode->io_tree, start_pos, ++ last_pos, cached_state)) { + folio_unlock(folio); + folio_put(folio); +- btrfs_start_ordered_extent(ordered); +- btrfs_put_ordered_extent(ordered); + return -EAGAIN; + } +- if (ordered) +- btrfs_put_ordered_extent(ordered); +- +- *lockstart = start_pos; +- *lockend = last_pos; +- ret = 1; ++ } else { ++ btrfs_lock_extent(&inode->io_tree, start_pos, last_pos, ++ cached_state); + } + ++ ordered = btrfs_lookup_ordered_range(inode, start_pos, ++ last_pos - start_pos + 1); ++ if (ordered && ++ ordered->file_offset + ordered->num_bytes > start_pos && ++ ordered->file_offset <= last_pos) { ++ btrfs_unlock_extent(&inode->io_tree, start_pos, last_pos, ++ cached_state); ++ folio_unlock(folio); ++ folio_put(folio); ++ btrfs_start_ordered_extent(ordered); ++ btrfs_put_ordered_extent(ordered); ++ return -EAGAIN; ++ } ++ if (ordered) ++ btrfs_put_ordered_extent(ordered); ++ ++ *lockstart = start_pos; ++ *lockend = last_pos; ++ + /* + * We should be called after prepare_one_folio() which should have locked + * all pages in the range. + */ + WARN_ON(!folio_test_locked(folio)); + +- return ret; ++ return 0; + } + + /* +@@ -1195,7 +1189,6 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + const u64 reserved_start = round_down(start, fs_info->sectorsize); + u64 reserved_len; + struct folio *folio = NULL; +- int extents_locked; + u64 lockstart; + u64 lockend; + bool only_release_metadata = false; +@@ -1253,18 +1246,16 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + reserved_len = last_block - reserved_start; + } + +- extents_locked = lock_and_cleanup_extent_if_need(inode, folio, start, +- write_bytes, &lockstart, +- &lockend, nowait, +- &cached_state); +- if (extents_locked < 0) { +- if (!nowait && extents_locked == -EAGAIN) ++ ret = lock_and_cleanup_extent(inode, folio, start, write_bytes, ++ &lockstart, &lockend, nowait, &cached_state); ++ if (ret < 0) { ++ if (!nowait) + goto again; + + btrfs_delalloc_release_extents(inode, reserved_len); + release_space(inode, *data_reserved, reserved_start, reserved_len, + only_release_metadata); +- return extents_locked; ++ return ret; + } + + copied = copy_folio_from_iter_atomic(folio, offset_in_folio(folio, start), +@@ -1288,11 +1279,8 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + + /* No copied bytes, unlock, release reserved space and exit. */ + if (copied == 0) { +- if (extents_locked) +- btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, +- &cached_state); +- else +- btrfs_free_extent_state(cached_state); ++ btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, ++ &cached_state); + btrfs_delalloc_release_extents(inode, reserved_len); + release_space(inode, *data_reserved, reserved_start, reserved_len, + only_release_metadata); +@@ -1311,17 +1299,7 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + + ret = btrfs_dirty_folio(inode, folio, start, copied, &cached_state, + only_release_metadata); +- /* +- * If we have not locked the extent range, because the range's start +- * offset is >= i_size, we might still have a non-NULL cached extent +- * state, acquired while marking the extent range as delalloc through +- * btrfs_dirty_page(). Therefore free any possible cached extent state +- * to avoid a memory leak. +- */ +- if (extents_locked) +- btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, &cached_state); +- else +- btrfs_free_extent_state(cached_state); ++ btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, &cached_state); + + btrfs_delalloc_release_extents(inode, reserved_len); + if (ret) { +@@ -1595,8 +1573,6 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) + else + btrfs_inode_lock(inode, BTRFS_ILOCK_MMAP); + +- atomic_inc(&root->log_batch); +- + /* + * Before we acquired the inode's lock and the mmap lock, someone may + * have dirtied more pages in the target range. We need to make sure +@@ -1679,8 +1655,6 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) + if (ret) + goto out_release_extents; + +- atomic_inc(&root->log_batch); +- + if (skip_inode_logging(&ctx)) { + /* + * We've had everything committed since the last time we were +@@ -2683,8 +2657,8 @@ static int btrfs_punch_hole(struct file *file, loff_t offset, loff_t len) + + lockstart = round_up(offset, fs_info->sectorsize); + lockend = round_down(offset + len, fs_info->sectorsize) - 1; +- same_block = (BTRFS_BYTES_TO_BLKS(fs_info, offset)) +- == (BTRFS_BYTES_TO_BLKS(fs_info, offset + len - 1)); ++ same_block = (offset >> fs_info->sectorsize_bits) == ++ ((offset + len - 1) >> fs_info->sectorsize_bits); + /* + * Only do this if we are in the same block and we aren't doing the + * entire block. +@@ -2888,7 +2862,7 @@ enum { + static int btrfs_zero_range_check_range_boundary(struct btrfs_inode *inode, + u64 offset) + { +- const u64 sectorsize = inode->root->fs_info->sectorsize; ++ const u32 sectorsize = inode->root->fs_info->sectorsize; + struct extent_map *em; + int ret; + +@@ -2918,7 +2892,7 @@ static int btrfs_zero_range(struct inode *inode, + struct extent_changeset *data_reserved = NULL; + int ret; + u64 alloc_hint = 0; +- const u64 sectorsize = fs_info->sectorsize; ++ const u32 sectorsize = fs_info->sectorsize; + const u64 orig_start = offset; + const u64 orig_end = offset + len - 1; + u64 alloc_start = round_down(offset, sectorsize); +@@ -2967,8 +2941,8 @@ static int btrfs_zero_range(struct inode *inode, + } + btrfs_free_extent_map(em); + +- if (BTRFS_BYTES_TO_BLKS(fs_info, offset) == +- BTRFS_BYTES_TO_BLKS(fs_info, offset + len - 1)) { ++ if ((offset >> fs_info->sectorsize_bits) == ++ ((offset + len - 1) >> fs_info->sectorsize_bits)) { + em = btrfs_get_extent(BTRFS_I(inode), NULL, alloc_start, sectorsize); + if (IS_ERR(em)) { + ret = PTR_ERR(em); +diff --git a/fs/btrfs/fs.c b/fs/btrfs/fs.c +--- a/fs/btrfs/fs.c ++++ b/fs/btrfs/fs.c +@@ -127,20 +127,9 @@ void btrfs_csum_final(struct btrfs_csum_ctx *ctx, u8 *out) + } + + /* +- * We support the following block sizes for all systems: +- * +- * - 4K +- * This is the most common block size. For PAGE SIZE > 4K cases the subpage +- * mode is used. +- * +- * - PAGE_SIZE +- * The straightforward block size to support. +- * +- * And extra support for the following block sizes based on the kernel config: +- * +- * - MIN_BLOCKSIZE +- * This is either 4K (regular builds) or 2K (debug builds) +- * This allows testing subpage routines on x86_64. ++ * For regular builds, any block size <= page size is supported. ++ * For experimental builds, any block size between BTRFS_MIN_BLOCKSIZE ++ * and BTRFS_MAX_BLOCKSIZE (inclusive) is supported. + */ + bool __attribute_const__ btrfs_supported_blocksize(u32 blocksize) + { +@@ -148,7 +137,7 @@ bool __attribute_const__ btrfs_supported_blocksize(u32 blocksize) + ASSERT(is_power_of_2(blocksize) && blocksize >= BTRFS_MIN_BLOCKSIZE && + blocksize <= BTRFS_MAX_BLOCKSIZE); + +- if (blocksize == PAGE_SIZE || blocksize == SZ_4K || blocksize == BTRFS_MIN_BLOCKSIZE) ++ if (blocksize <= PAGE_SIZE) + return true; + #ifdef CONFIG_BTRFS_EXPERIMENTAL + /* +diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h +--- a/fs/btrfs/fs.h ++++ b/fs/btrfs/fs.h +@@ -289,7 +289,8 @@ enum { + BTRFS_MOUNT_IGNOREBADROOTS | \ + BTRFS_MOUNT_IGNOREDATACSUMS | \ + BTRFS_MOUNT_IGNOREMETACSUMS | \ +- BTRFS_MOUNT_IGNORESUPERFLAGS) ++ BTRFS_MOUNT_IGNORESUPERFLAGS | \ ++ BTRFS_MOUNT_USEBACKUPROOT) + + /* + * Compat flags that we support. If any incompat flags are set other than the +@@ -890,7 +891,6 @@ struct btrfs_fs_info { + u32 sectorsize_bits; + u32 block_min_order; + u32 block_max_order; +- u32 stripesize; + u32 writeback_bio_size; + u32 csum_size; + u32 csums_per_leaf; +@@ -1060,8 +1060,6 @@ static inline u64 btrfs_calc_metadata_size(const struct btrfs_fs_info *fs_info, + #define BTRFS_MAX_EXTENT_ITEM_SIZE(r) ((BTRFS_LEAF_DATA_SIZE(r->fs_info) >> 4) - \ + sizeof(struct btrfs_item)) + +-#define BTRFS_BYTES_TO_BLKS(fs_info, bytes) ((bytes) >> (fs_info)->sectorsize_bits) +- + static inline bool btrfs_is_zoned(const struct btrfs_fs_info *fs_info) + { + return IS_ENABLED(CONFIG_BLK_DEV_ZONED) && fs_info->zone_size > 0; +diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -250,8 +250,8 @@ static void print_data_reloc_error(const struct btrfs_inode *inode, u64 file_off + + ret = extent_from_logical(fs_info, logical, &path, &found_key, &flags); + if (ret < 0) { +- btrfs_err_rl(fs_info, "failed to lookup extent item for logical %llu: %d", +- logical, ret); ++ btrfs_err_rl(fs_info, "failed to lookup extent item for logical %llu: %pe", ++ logical, ERR_PTR(ret)); + return; + } + eb = path.nodes[0]; +@@ -869,7 +869,7 @@ static void compress_file_range(struct btrfs_work *work) + struct btrfs_inode *inode = async_chunk->inode; + struct btrfs_fs_info *fs_info = inode->root->fs_info; + struct compressed_bio *cb = NULL; +- u64 blocksize = fs_info->sectorsize; ++ const u32 blocksize = fs_info->sectorsize; + u64 start = async_chunk->start; + u64 end = async_chunk->end; + u64 actual_end; +@@ -1019,9 +1019,10 @@ static void submit_uncompressed_range(struct btrfs_inode *inode, + btrfs_folio_end_lock(inode->root->fs_info, locked_folio, + start, async_extent->ram_size); + btrfs_err_rl(inode->root->fs_info, +- "%s failed, root=%llu inode=%llu start=%llu len=%llu: %d", ++ "%s failed, root=%llu inode=%llu start=%llu len=%llu: %pe", + __func__, btrfs_root_id(inode->root), +- btrfs_ino(inode), start, async_extent->ram_size, ret); ++ btrfs_ino(inode), start, async_extent->ram_size, ++ ERR_PTR(ret)); + } + } + +@@ -1508,10 +1509,10 @@ static noinline int cow_file_range(struct btrfs_inode *inode, + end - start - cur_alloc_size + 1, NULL); + } + btrfs_err(fs_info, +-"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu cur_alloc_size=%u: %d", ++"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu cur_alloc_size=%u: %pe", + __func__, btrfs_root_id(inode->root), + btrfs_ino(inode), orig_start, end + 1 - orig_start, +- start, cur_alloc_size, ret); ++ start, cur_alloc_size, ERR_PTR(ret)); + return ret; + } + +@@ -1962,9 +1963,9 @@ static int nocow_one_range(struct btrfs_inode *inode, struct folio *locked_folio + PAGE_UNLOCK | PAGE_START_WRITEBACK | + PAGE_END_WRITEBACK); + btrfs_err(inode->root->fs_info, +- "%s failed, root=%lld inode=%llu start=%llu len=%llu: %d", ++ "%s failed, root=%lld inode=%llu start=%llu len=%llu: %pe", + __func__, btrfs_root_id(inode->root), btrfs_ino(inode), +- file_pos, len, ret); ++ file_pos, len, ERR_PTR(ret)); + return ret; + } + +@@ -2285,10 +2286,10 @@ static noinline int run_delalloc_nocow(struct btrfs_inode *inode, + } + btrfs_free_path(path); + btrfs_err(fs_info, +-"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu oe_cleanup=%llu oe_cleanup_len=%llu untouched_start=%llu untouched_len=%llu: %d", ++"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu oe_cleanup=%llu oe_cleanup_len=%llu untouched_start=%llu untouched_len=%llu: %pe", + __func__, btrfs_root_id(inode->root), btrfs_ino(inode), + start, end + 1 - start, cur_offset, oe_cleanup_start, oe_cleanup_len, +- untouched_start, untouched_len, ret); ++ untouched_start, untouched_len, ERR_PTR(ret)); + return ret; + } + +@@ -3050,7 +3051,7 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans, + u64 qgroup_reserved) + { + struct btrfs_root *root = inode->root; +- const u64 sectorsize = root->fs_info->sectorsize; ++ const u32 sectorsize = root->fs_info->sectorsize; + BTRFS_PATH_AUTO_FREE(path); + struct extent_buffer *leaf; + struct btrfs_key ins; +@@ -3907,7 +3908,7 @@ int btrfs_orphan_cleanup(struct btrfs_root *root) + + out: + if (ret) +- btrfs_err(fs_info, "could not do orphan cleanup %d", ret); ++ btrfs_err(fs_info, "could not do orphan cleanup %pe", ERR_PTR(ret)); + return ret; + } + +@@ -4210,8 +4211,8 @@ static int btrfs_read_locked_inode(struct btrfs_inode *inode, struct btrfs_path + ret = btrfs_load_inode_props(inode, path); + if (ret) + btrfs_err(fs_info, +- "error loading props for ino %llu (root %llu): %d", +- btrfs_ino(inode), btrfs_root_id(root), ret); ++ "error loading props for ino %llu (root %llu): %pe", ++ btrfs_ino(inode), btrfs_root_id(root), ERR_PTR(ret)); + } + + /* +@@ -6825,8 +6826,8 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans, + } + if (ret) { + btrfs_err(fs_info, +- "error inheriting props for ino %llu (root %llu): %d", +- btrfs_ino(BTRFS_I(inode)), btrfs_root_id(root), ret); ++ "error inheriting props for ino %llu (root %llu): %pe", ++ btrfs_ino(BTRFS_I(inode)), btrfs_root_id(root), ERR_PTR(ret)); + } + + /* +@@ -10205,6 +10206,8 @@ static void btrfs_free_swapfile_pins(struct inode *inode) + struct btrfs_fs_info *fs_info = BTRFS_I(inode)->root->fs_info; + struct btrfs_swapfile_pin *sp; + struct rb_node *node, *next; ++ u64 bg_bytes_released = 0; ++ u32 bg_nr_released = 0; + + spin_lock(&fs_info->swapfile_pins_lock); + node = rb_first(&fs_info->swapfile_pins); +@@ -10214,15 +10217,24 @@ static void btrfs_free_swapfile_pins(struct inode *inode) + if (sp->inode == inode) { + rb_erase(&sp->node, &fs_info->swapfile_pins); + if (sp->is_block_group) { +- btrfs_dec_block_group_swap_extents(sp->ptr, ++ struct btrfs_block_group *bg = sp->ptr; ++ ++ bg_bytes_released += bg->length; ++ bg_nr_released++; ++ btrfs_dec_block_group_swap_extents(bg, + sp->bg_extent_count); +- btrfs_put_block_group(sp->ptr); ++ btrfs_put_block_group(bg); + } + kfree(sp); + } + node = next; + } + spin_unlock(&fs_info->swapfile_pins_lock); ++ btrfs_info(fs_info, ++"swapfile deactivated on root %llu ino %llu, released %llu bytes from %u block group(s)", ++ btrfs_root_id(BTRFS_I(inode)->root), ++ btrfs_ino(BTRFS_I(inode)), bg_bytes_released, ++ bg_nr_released); + } + + struct btrfs_swap_info { +@@ -10300,8 +10312,10 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, + struct btrfs_backref_share_check_ctx *backref_ctx = NULL; + struct btrfs_path *path = NULL; + int ret = 0; ++ u32 pinned_bg_nr = 0; + u64 isize; + u64 prev_extent_end = 0; ++ u64 pinned_bg_size = 0; + + /* + * Acquire the inode's mmap lock to prevent races with memory mapped +@@ -10551,6 +10565,9 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, + ret = 0; + else + goto out; ++ } else { ++ pinned_bg_size += bg->length; ++ pinned_bg_nr++; + } + + if (bsi.block_len && +@@ -10598,6 +10615,14 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, + if (ret) + return ret; + ++ btrfs_info(fs_info, ++"swapfile activated on root %llu ino %llu, pinned down %llu bytes from %u block group(s)", ++ btrfs_root_id(BTRFS_I(inode)->root), ++ btrfs_ino(BTRFS_I(inode)), ++ pinned_bg_size, pinned_bg_nr); ++ btrfs_warn(fs_info, ++"block groups with swapfile extents will not be scrubbed or balanced"); ++ + if (device) + sis->bdev = device->bdev; + *span = bsi.highest_ppage - bsi.lowest_ppage + 1; +diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c +--- a/fs/btrfs/ioctl.c ++++ b/fs/btrfs/ioctl.c +@@ -400,9 +400,9 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + + /* + * 1 for inode item +- * 2 for properties ++ * 1 for property + */ +- trans = btrfs_start_transaction(root, 3); ++ trans = btrfs_start_transaction(root, 2); + if (IS_ERR(trans)) + return PTR_ERR(trans); + +@@ -1143,13 +1143,13 @@ static noinline int btrfs_ioctl_resize(struct file *file, + } + + static noinline int __btrfs_ioctl_snap_create(struct file *file, +- struct mnt_idmap *idmap, + const char *name, unsigned long fd, bool subvol, + bool readonly, + struct btrfs_qgroup_inherit *inherit) + { + int ret; + struct qstr qname = QSTR(name); ++ struct mnt_idmap *idmap = file_mnt_idmap(file); + + if (!S_ISDIR(file_inode(file)->i_mode)) + return -ENOTDIR; +@@ -1227,8 +1227,7 @@ static noinline int btrfs_ioctl_snap_create(struct file *file, + if (ret < 0) + return ret; + +- return __btrfs_ioctl_snap_create(file, file_mnt_idmap(file), +- vol_args->name, vol_args->fd, subvol, ++ return __btrfs_ioctl_snap_create(file, vol_args->name, vol_args->fd, subvol, + false, NULL); + } + +@@ -1271,8 +1270,7 @@ static noinline int btrfs_ioctl_snap_create_v2(struct file *file, + return ret; + } + +- return __btrfs_ioctl_snap_create(file, file_mnt_idmap(file), +- vol_args->name, vol_args->fd, subvol, ++ return __btrfs_ioctl_snap_create(file, vol_args->name, vol_args->fd, subvol, + readonly, inherit); + } + +@@ -1664,13 +1662,11 @@ static noinline int btrfs_ioctl_tree_search_v2(struct btrfs_root *root, + } + + /* +- * Search INODE_REFs to identify path name of 'dirid' directory +- * in a 'tree_id' tree. and sets path name to 'name'. ++ * Search for an INODE_REF in a 'root' tree which identifies the path name of ++ * 'dirid'. When found, it sets 'name' with the path name. + */ +-static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, +- u64 tree_id, u64 dirid, char *name) ++static noinline int btrfs_search_path_in_tree(struct btrfs_root *root, u64 dirid, char *name) + { +- struct btrfs_root *root; + struct btrfs_key key; + char *ptr; + int ret = -1; +@@ -1692,13 +1688,6 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + + ptr = &name[BTRFS_INO_LOOKUP_PATH_MAX - 1]; + +- root = btrfs_get_fs_root(info, tree_id, true); +- if (IS_ERR(root)) { +- ret = PTR_ERR(root); +- root = NULL; +- goto out; +- } +- + key.objectid = dirid; + key.type = BTRFS_INODE_REF_KEY; + key.offset = (u64)-1; +@@ -1706,11 +1695,9 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + while (1) { + ret = btrfs_search_backwards(root, &key, path); + if (ret < 0) +- goto out; +- else if (ret > 0) { +- ret = -ENOENT; +- goto out; +- } ++ return ret; ++ else if (ret > 0) ++ return -ENOENT; + + l = path->nodes[0]; + slot = path->slots[0]; +@@ -1719,10 +1706,8 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + len = btrfs_inode_ref_name_len(l, iref); + ptr -= len + 1; + total_len += len + 1; +- if (ptr < name) { +- ret = -ENAMETOOLONG; +- goto out; +- } ++ if (ptr < name) ++ return -ENAMETOOLONG; + + *(ptr + len) = '/'; + read_extent_buffer(l, ptr, (unsigned long)(iref + 1), len); +@@ -1737,10 +1722,8 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + } + memmove(name, ptr, total_len); + name[total_len] = '\0'; +- ret = 0; +-out: +- btrfs_put_root(root); +- return ret; ++ ++ return 0; + } + + static int btrfs_search_path_in_tree_user(struct mnt_idmap *idmap, +@@ -1884,6 +1867,7 @@ static int btrfs_search_path_in_tree_user(struct mnt_idmap *idmap, + static noinline int btrfs_ioctl_ino_lookup(struct btrfs_root *root, + void __user *argp) + { ++ bool new_root = false; + struct btrfs_ioctl_ino_lookup_args AUTO_KFREE(args); + int ret = 0; + +@@ -1897,6 +1881,8 @@ static noinline int btrfs_ioctl_ino_lookup(struct btrfs_root *root, + */ + if (args->treeid == 0) + args->treeid = btrfs_root_id(root); ++ else ++ new_root = true; + + if (args->objectid == BTRFS_FIRST_FREE_OBJECTID) { + args->name[0] = 0; +@@ -1908,9 +1894,14 @@ static noinline int btrfs_ioctl_ino_lookup(struct btrfs_root *root, + goto out; + } + +- ret = btrfs_search_path_in_tree(root->fs_info, +- args->treeid, args->objectid, +- args->name); ++ if (new_root) { ++ root = btrfs_get_fs_root(root->fs_info, args->treeid, true); ++ if (IS_ERR(root)) ++ return PTR_ERR(root); ++ } ++ ret = btrfs_search_path_in_tree(root, args->objectid, args->name); ++ if (new_root) ++ btrfs_put_root(root); + + out: + if (ret == 0 && copy_to_user(argp, args, sizeof(*args))) +@@ -2848,8 +2839,8 @@ static long btrfs_ioctl_default_subvol(struct file *file, void __user *argp) + else + ret = -ENOENT; + btrfs_err(fs_info, +- "could not find default diritem for dir %llu: %d", +- dir_id, ret); ++ "could not find default diritem for dir %llu: %pe", ++ dir_id, ERR_PTR(ret)); + goto out_free; + } + +@@ -3620,7 +3611,7 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + { + struct inode *inode = file_inode(file); + struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); +- struct btrfs_root *root = BTRFS_I(inode)->root; ++ struct btrfs_root *quota_root; + struct btrfs_ioctl_qgroup_assign_args AUTO_KFREE(sa); + struct btrfs_qgroup_list AUTO_KFREE(prealloc); + struct btrfs_trans_handle *trans; +@@ -3651,10 +3642,20 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + } + } + ++ mutex_lock(&fs_info->qgroup_ioctl_lock); ++ quota_root = btrfs_grab_root(fs_info->quota_root); ++ mutex_unlock(&fs_info->qgroup_ioctl_lock); ++ ++ if (!quota_root) { ++ ret = -ENOTCONN; ++ goto drop_write; ++ } ++ + /* 2 BTRFS_QGROUP_RELATION_KEY items. */ +- trans = btrfs_start_transaction(root, 2); ++ trans = btrfs_start_transaction(quota_root, 2); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); ++ btrfs_put_root(quota_root); + goto drop_write; + } + +@@ -3678,6 +3679,7 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + "qgroup status update failed after %s relation, marked as inconsistent", + sa->assign ? "adding" : "deleting"); + err = btrfs_end_transaction(trans); ++ btrfs_put_root(quota_root); + if (err && !ret) + ret = err; + +@@ -3689,7 +3691,8 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + { + struct inode *inode = file_inode(file); +- struct btrfs_root *root = BTRFS_I(inode)->root; ++ struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); ++ struct btrfs_root *quota_root; + struct btrfs_ioctl_qgroup_create_args AUTO_KFREE(sa); + struct btrfs_trans_handle *trans; + int ret; +@@ -3698,7 +3701,7 @@ static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + +- if (!btrfs_qgroup_enabled(root->fs_info)) ++ if (!btrfs_qgroup_enabled(fs_info)) + return -ENOTCONN; + + ret = mnt_want_write_file(file); +@@ -3721,13 +3724,23 @@ static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + goto drop_write; + } + ++ mutex_lock(&fs_info->qgroup_ioctl_lock); ++ quota_root = btrfs_grab_root(fs_info->quota_root); ++ mutex_unlock(&fs_info->qgroup_ioctl_lock); ++ ++ if (!quota_root) { ++ ret = -ENOTCONN; ++ goto drop_write; ++ } ++ + /* + * 1 BTRFS_QGROUP_INFO_KEY item. + * 1 BTRFS_QGROUP_LIMIT_KEY item. + */ +- trans = btrfs_start_transaction(root, 2); ++ trans = btrfs_start_transaction(quota_root, 2); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); ++ btrfs_put_root(quota_root); + goto drop_write; + } + +@@ -3738,6 +3751,7 @@ static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + } + + err = btrfs_end_transaction(trans); ++ btrfs_put_root(quota_root); + if (err && !ret) + ret = err; + +@@ -3750,6 +3764,8 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + { + struct inode *inode = file_inode(file); + struct btrfs_root *root = BTRFS_I(inode)->root; ++ struct btrfs_root *quota_root; ++ struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_ioctl_qgroup_limit_args AUTO_KFREE(sa); + struct btrfs_trans_handle *trans; + int ret; +@@ -3759,7 +3775,7 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + +- if (!btrfs_qgroup_enabled(root->fs_info)) ++ if (!btrfs_qgroup_enabled(fs_info)) + return -ENOTCONN; + + ret = mnt_want_write_file(file); +@@ -3772,10 +3788,20 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + goto drop_write; + } + ++ mutex_lock(&fs_info->qgroup_ioctl_lock); ++ quota_root = btrfs_grab_root(fs_info->quota_root); ++ mutex_unlock(&fs_info->qgroup_ioctl_lock); ++ ++ if (!quota_root) { ++ ret = -ENOTCONN; ++ goto drop_write; ++ } ++ + /* 1 BTRFS_QGROUP_LIMIT_KEY item. */ +- trans = btrfs_start_transaction(root, 1); ++ trans = btrfs_start_transaction(quota_root, 1); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); ++ btrfs_put_root(quota_root); + goto drop_write; + } + +@@ -3788,6 +3814,7 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + ret = btrfs_limit_qgroup(trans, qgroupid, &sa->lim); + + err = btrfs_end_transaction(trans); ++ btrfs_put_root(quota_root); + if (err && !ret) + ret = err; + +diff --git a/fs/btrfs/messages.c b/fs/btrfs/messages.c +--- a/fs/btrfs/messages.c ++++ b/fs/btrfs/messages.c +@@ -279,7 +279,6 @@ void __btrfs_panic(const struct btrfs_fs_info *fs_info, const char *function, + unsigned int line, int error, const char *fmt, ...) + { + char *s_id = ""; +- const char *errstr; + struct va_format vaf = { .fmt = fmt }; + va_list args; + +@@ -289,13 +288,12 @@ void __btrfs_panic(const struct btrfs_fs_info *fs_info, const char *function, + va_start(args, fmt); + vaf.va = &args; + +- errstr = btrfs_decode_error(error); + if (fs_info && (btrfs_test_opt(fs_info, PANIC_ON_FATAL_ERROR))) +- panic(KERN_CRIT "BTRFS panic (device %s) in %s:%d: %pV (errno=%d %s)\n", +- s_id, function, line, &vaf, error, errstr); ++ panic(KERN_CRIT "BTRFS panic (device %s) in %s:%d: %pV (errno=%d %pe)\n", ++ s_id, function, line, &vaf, error, ERR_PTR(error)); + +- btrfs_crit(fs_info, "panic in %s:%d: %pV (errno=%d %s)", +- function, line, &vaf, error, errstr); ++ btrfs_crit(fs_info, "panic in %s:%d: %pV (errno=%d %pe)", ++ function, line, &vaf, error, ERR_PTR(error)); + va_end(args); + /* Caller calls BUG() */ + } +diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c +--- a/fs/btrfs/qgroup.c ++++ b/fs/btrfs/qgroup.c +@@ -3915,8 +3915,8 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) + ret = PTR_ERR(trans); + trans = NULL; + btrfs_err(fs_info, +- "fail to start transaction for status update: %d", +- ret); ++ "fail to start transaction for status update: %pe", ++ ERR_PTR(ret)); + } + } else { + trans = NULL; +@@ -3931,7 +3931,7 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) + + if (ret2 < 0) { + ret = ret2; +- btrfs_err(fs_info, "fail to update qgroup status: %d", ret); ++ btrfs_err(fs_info, "fail to update qgroup status: %pe", ERR_PTR(ret)); + } + } + fs_info->qgroup_rescan_running = false; +@@ -3952,7 +3952,7 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) + btrfs_info(fs_info, "qgroup scan completed%s", + ret > 0 ? " (inconsistency flag cleared)" : ""); + } else { +- btrfs_err(fs_info, "qgroup scan failed with %d", ret); ++ btrfs_err(fs_info, "qgroup scan failed with %pe", ERR_PTR(ret)); + } + } + +@@ -4339,12 +4339,13 @@ static int qgroup_free_reserved_data(struct btrfs_inode *inode, + struct ulist_node *unode; + struct ulist_iterator uiter; + struct extent_changeset changeset; ++ const u32 sectorsize = root->fs_info->sectorsize; ++ const u64 aligned_start = round_down(start, sectorsize); ++ const u64 aligned_len = round_up(start + len, sectorsize) - aligned_start; + u64 freed = 0; + int ret; + + extent_changeset_init_bytes_only(&changeset); +- len = round_up(start + len, root->fs_info->sectorsize); +- start = round_down(start, root->fs_info->sectorsize); + + ULIST_ITER_INIT(&uiter); + while ((unode = ulist_next(&reserved->range_changed, &uiter))) { +@@ -4356,12 +4357,15 @@ static int qgroup_free_reserved_data(struct btrfs_inode *inode, + + extent_changeset_release(&changeset); + +- /* Only free range in range [start, start + len) */ +- if (range_start >= start + len || +- range_start + range_len <= start) ++ /* ++ * Only free the range within ++ * [aligned_start, aligned_start + aligned_len). ++ */ ++ if (range_start >= aligned_start + aligned_len || ++ range_start + range_len <= aligned_start) + continue; +- free_start = max(range_start, start); +- free_len = min(start + len, range_start + range_len) - ++ free_start = max(range_start, aligned_start); ++ free_len = min(aligned_start + aligned_len, range_start + range_len) - + free_start; + /* + * TODO: To also modify reserved->ranges_reserved to reflect +diff --git a/fs/btrfs/raid56.c b/fs/btrfs/raid56.c +--- a/fs/btrfs/raid56.c ++++ b/fs/btrfs/raid56.c +@@ -2997,13 +2997,11 @@ void raid56_parity_submit_scrub_rbio(struct btrfs_raid_bio *rbio) + * This is due to the fact rbio has its own page management for its cache. + */ + void raid56_parity_cache_data_folios(struct btrfs_raid_bio *rbio, +- struct folio **data_folios, u64 data_logical) ++ void *vaddr, u64 data_logical) + { + struct btrfs_fs_info *fs_info = rbio->bioc->fs_info; + const u64 offset_in_full_stripe = data_logical - + rbio->bioc->full_stripe_logical; +- unsigned int findex = 0; +- unsigned int foffset = 0; + int ret; + + /* +@@ -3026,18 +3024,10 @@ void raid56_parity_cache_data_folios(struct btrfs_raid_bio *rbio, + cur_off < offset_in_full_stripe + BTRFS_STRIPE_LEN; + cur_off += PAGE_SIZE) { + const unsigned int pindex = cur_off >> PAGE_SHIFT; +- void *kaddr; + +- kaddr = kmap_local_page(rbio->stripe_pages[pindex]); +- memcpy_from_folio(kaddr, data_folios[findex], foffset, PAGE_SIZE); +- kunmap_local(kaddr); +- +- foffset += PAGE_SIZE; +- ASSERT(foffset <= folio_size(data_folios[findex])); +- if (foffset == folio_size(data_folios[findex])) { +- findex++; +- foffset = 0; +- } ++ ASSERT(cur_off - offset_in_full_stripe + PAGE_SIZE <= BTRFS_STRIPE_LEN); ++ memcpy_to_page(rbio->stripe_pages[pindex], 0, ++ vaddr + cur_off - offset_in_full_stripe, PAGE_SIZE); + } + bitmap_set(rbio->stripe_uptodate_bitmap, + offset_in_full_stripe >> fs_info->sectorsize_bits, +diff --git a/fs/btrfs/raid56.h b/fs/btrfs/raid56.h +--- a/fs/btrfs/raid56.h ++++ b/fs/btrfs/raid56.h +@@ -283,7 +283,7 @@ struct btrfs_raid_bio *raid56_parity_alloc_scrub_rbio(struct bio *bio, + void raid56_parity_submit_scrub_rbio(struct btrfs_raid_bio *rbio); + + void raid56_parity_cache_data_folios(struct btrfs_raid_bio *rbio, +- struct folio **data_folios, u64 data_logical); ++ void *vaddr, u64 data_logical); + + int btrfs_alloc_stripe_hash_table(struct btrfs_fs_info *info); + void btrfs_free_stripe_hash_table(struct btrfs_fs_info *info); +diff --git a/fs/btrfs/reflink.c b/fs/btrfs/reflink.c +--- a/fs/btrfs/reflink.c ++++ b/fs/btrfs/reflink.c +@@ -20,30 +20,31 @@ + #define BTRFS_MAX_DEDUPE_LEN SZ_16M + + static int clone_finish_inode_update(struct btrfs_trans_handle *trans, +- struct inode *inode, ++ struct btrfs_inode *inode, + u64 endoff, + const u64 destoff, + const u64 olen, + bool no_time_update) + { ++ struct inode *vfs_inode = &inode->vfs_inode; + int ret; + +- inode_inc_iversion(inode); +- if (!no_time_update) { +- inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); +- } ++ inode_inc_iversion(vfs_inode); ++ if (!no_time_update) ++ inode_set_mtime_to_ts(vfs_inode, inode_set_ctime_current(vfs_inode)); ++ + /* + * We round up to the block size at eof when determining which + * extents to clone above, but shouldn't round up the file size. + */ + if (endoff > destoff + olen) + endoff = destoff + olen; +- if (endoff > inode->i_size) { +- i_size_write(inode, endoff); +- btrfs_inode_safe_disk_i_size_write(BTRFS_I(inode), 0); ++ if (endoff > vfs_inode->i_size) { ++ i_size_write(vfs_inode, endoff); ++ btrfs_inode_safe_disk_i_size_write(inode, 0); + } + +- ret = btrfs_update_inode(trans, BTRFS_I(inode)); ++ ret = btrfs_update_inode(trans, inode); + if (unlikely(ret)) { + btrfs_abort_transaction(trans, ret); + btrfs_end_transaction(trans); +@@ -392,11 +393,11 @@ static int clone_copy_inline_extent(struct btrfs_inode *inode, + * @destoff: Offset within @inode to start clone + * @no_time_update: Whether to update mtime/ctime on the target inode + */ +-static int btrfs_clone(struct inode *src, struct inode *inode, ++static int btrfs_clone(struct btrfs_inode *src, struct btrfs_inode *inode, + const u64 off, const u64 olen, const u64 olen_aligned, + const u64 destoff, bool no_time_update) + { +- struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); ++ struct btrfs_fs_info *fs_info = inode->root->fs_info; + BTRFS_PATH_AUTO_FREE(path); + struct extent_buffer *leaf; + struct btrfs_trans_handle *trans; +@@ -420,7 +421,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + + path->reada = READA_FORWARD; + /* Clone data */ +- key.objectid = btrfs_ino(BTRFS_I(src)); ++ key.objectid = btrfs_ino(src); + key.type = BTRFS_EXTENT_DATA_KEY; + key.offset = off; + +@@ -436,8 +437,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + u64 drop_start; + + /* Note the key will change type as we walk through the tree */ +- ret = btrfs_search_slot(NULL, BTRFS_I(src)->root, &key, path, +- 0, 0); ++ ret = btrfs_search_slot(NULL, src->root, &key, path, 0, 0); + if (ret < 0) + goto out; + /* +@@ -455,7 +455,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + nritems = btrfs_header_nritems(path->nodes[0]); + process_slot: + if (path->slots[0] >= nritems) { +- ret = btrfs_next_leaf(BTRFS_I(src)->root, path); ++ ret = btrfs_next_leaf(src->root, path); + if (ret < 0) + goto out; + if (ret > 0) +@@ -466,8 +466,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + slot = path->slots[0]; + + btrfs_item_key_to_cpu(leaf, &key, slot); +- if (key.type > BTRFS_EXTENT_DATA_KEY || +- key.objectid != btrfs_ino(BTRFS_I(src))) ++ if (key.type > BTRFS_EXTENT_DATA_KEY || key.objectid != btrfs_ino(src)) + break; + + ASSERT(key.type == BTRFS_EXTENT_DATA_KEY, "key.type=%u", key.type); +@@ -514,7 +513,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + btrfs_release_path(path); + + memcpy(&new_key, &key, sizeof(new_key)); +- new_key.objectid = btrfs_ino(BTRFS_I(inode)); ++ new_key.objectid = btrfs_ino(inode); + if (off <= key.offset) + new_key.offset = key.offset + destoff - off; + else +@@ -558,7 +557,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + clone_info.extent_buf = buf; + clone_info.is_new_extent = false; + clone_info.update_times = !no_time_update; +- ret = btrfs_replace_file_extents(BTRFS_I(inode), path, ++ ret = btrfs_replace_file_extents(inode, path, + drop_start, new_key.offset + datal - 1, + &clone_info, &trans); + if (ret) +@@ -582,7 +581,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + goto out; + } + +- ret = clone_copy_inline_extent(BTRFS_I(inode), path, &new_key, ++ ret = clone_copy_inline_extent(inode, path, &new_key, + drop_start, datal, size, + comp, buf, &trans); + if (ret) +@@ -605,9 +604,9 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + * the checksums problem on fsync. + */ + if (extent_gen == trans->transid && disko > 0) +- BTRFS_I(src)->last_reflink_trans = trans->transid; ++ src->last_reflink_trans = trans->transid; + +- BTRFS_I(inode)->last_reflink_trans = trans->transid; ++ inode->last_reflink_trans = trans->transid; + + last_dest_end = ALIGN(new_key.offset + datal, + fs_info->sectorsize); +@@ -653,10 +652,10 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + * set by previous calls to btrfs_replace_file_extents() that + * replaced file extent items. + */ +- if (last_dest_end >= i_size_read(inode)) +- btrfs_set_inode_full_sync(BTRFS_I(inode)); ++ if (last_dest_end >= i_size_read(&inode->vfs_inode)) ++ btrfs_set_inode_full_sync(inode); + +- ret = btrfs_replace_file_extents(BTRFS_I(inode), path, ++ ret = btrfs_replace_file_extents(inode, path, + last_dest_end, destoff + len - 1, NULL, &trans); + if (ret) + goto out; +@@ -666,7 +665,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + } + + out: +- clear_bit(BTRFS_INODE_NO_DELALLOC_FLUSH, &BTRFS_I(inode)->runtime_flags); ++ clear_bit(BTRFS_INODE_NO_DELALLOC_FLUSH, &inode->runtime_flags); + + return ret; + } +@@ -688,10 +687,10 @@ static void btrfs_double_mmap_unlock(struct btrfs_inode *inode1, struct btrfs_in + static int btrfs_extent_same_range(struct btrfs_inode *src, u64 loff, u64 len, + struct btrfs_inode *dst, u64 dst_loff) + { +- const u64 end = dst_loff + len - 1; + struct extent_state *cached_state = NULL; + struct btrfs_fs_info *fs_info = src->root->fs_info; +- const u64 bs = fs_info->sectorsize; ++ const u32 bs = fs_info->sectorsize; ++ const u64 end = round_up(dst_loff + len, bs) - 1; + int ret; + + /* +@@ -701,8 +700,7 @@ static int btrfs_extent_same_range(struct btrfs_inode *src, u64 loff, u64 len, + * mode. + */ + btrfs_lock_extent(&dst->io_tree, dst_loff, end, &cached_state); +- ret = btrfs_clone(&src->vfs_inode, &dst->vfs_inode, loff, len, +- ALIGN(len, bs), dst_loff, true); ++ ret = btrfs_clone(src, dst, loff, len, ALIGN(len, bs), dst_loff, true); + btrfs_unlock_extent(&dst->io_tree, dst_loff, end, &cached_state); + + btrfs_btree_balance_dirty(fs_info); +@@ -710,12 +708,12 @@ static int btrfs_extent_same_range(struct btrfs_inode *src, u64 loff, u64 len, + return ret; + } + +-static int btrfs_extent_same(struct inode *src, u64 loff, u64 olen, +- struct inode *dst, u64 dst_loff) ++static int btrfs_extent_same(struct btrfs_inode *src, u64 loff, u64 olen, ++ struct btrfs_inode *dst, u64 dst_loff) + { + int ret = 0; + u64 i, tail_len, chunk_count; +- struct btrfs_root *root_dst = BTRFS_I(dst)->root; ++ struct btrfs_root *root_dst = dst->root; + + spin_lock(&root_dst->root_item_lock); + if (root_dst->send_in_progress) { +@@ -733,8 +731,8 @@ static int btrfs_extent_same(struct inode *src, u64 loff, u64 olen, + chunk_count = div_u64(olen, BTRFS_MAX_DEDUPE_LEN); + + for (i = 0; i < chunk_count; i++) { +- ret = btrfs_extent_same_range(BTRFS_I(src), loff, BTRFS_MAX_DEDUPE_LEN, +- BTRFS_I(dst), dst_loff); ++ ret = btrfs_extent_same_range(src, loff, BTRFS_MAX_DEDUPE_LEN, ++ dst, dst_loff); + if (ret) + goto out; + +@@ -743,8 +741,7 @@ static int btrfs_extent_same(struct inode *src, u64 loff, u64 olen, + } + + if (tail_len > 0) +- ret = btrfs_extent_same_range(BTRFS_I(src), loff, tail_len, +- BTRFS_I(dst), dst_loff); ++ ret = btrfs_extent_same_range(src, loff, tail_len, dst, dst_loff); + out: + spin_lock(&root_dst->root_item_lock); + root_dst->dedupe_in_progress--; +@@ -757,12 +754,14 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + u64 off, u64 olen, u64 destoff) + { + struct extent_state *cached_state = NULL; +- struct inode *inode = file_inode(file); +- struct inode *src = file_inode(file_src); +- struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); ++ struct btrfs_inode *inode = BTRFS_I(file_inode(file)); ++ struct btrfs_inode *src = BTRFS_I(file_inode(file_src)); ++ struct btrfs_fs_info *fs_info = inode->root->fs_info; ++ const u64 src_isize = src->vfs_inode.i_size; ++ const u64 inode_isize = inode->vfs_inode.i_size; + int ret; + u64 len = olen; +- u64 bs = fs_info->sectorsize; ++ const u32 bs = fs_info->sectorsize; + u64 end; + + /* +@@ -771,13 +770,13 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * if the file size is not blocksize aligned. So we don't need to check + * for that case here. + */ +- if (off + len == src->i_size) +- len = ALIGN(src->i_size, bs) - off; ++ if (off + len == src_isize) ++ len = ALIGN(src_isize, bs) - off; + +- if (destoff > inode->i_size) { +- const u64 wb_start = ALIGN_DOWN(inode->i_size, bs); ++ if (destoff > inode_isize) { ++ const u64 wb_start = ALIGN_DOWN(inode_isize, bs); + +- ret = btrfs_cont_expand(BTRFS_I(inode), inode->i_size, destoff); ++ ret = btrfs_cont_expand(inode, inode_isize, destoff); + if (ret) + return ret; + /* +@@ -789,8 +788,7 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * we found the previous extent covering eof and before we + * attempted to increment its reference count). + */ +- ret = btrfs_wait_ordered_range(BTRFS_I(inode), wb_start, +- destoff - wb_start); ++ ret = btrfs_wait_ordered_range(inode, wb_start, destoff - wb_start); + if (ret) + return ret; + } +@@ -801,10 +799,10 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * because we have already locked the inode's i_mmap_lock in exclusive + * mode. + */ +- end = destoff + len - 1; +- btrfs_lock_extent(&BTRFS_I(inode)->io_tree, destoff, end, &cached_state); ++ end = round_up(destoff + len, bs) - 1; ++ btrfs_lock_extent(&inode->io_tree, destoff, end, &cached_state); + ret = btrfs_clone(src, inode, off, olen, len, destoff, false); +- btrfs_unlock_extent(&BTRFS_I(inode)->io_tree, destoff, end, &cached_state); ++ btrfs_unlock_extent(&inode->io_tree, destoff, end, &cached_state); + if (ret < 0) + return ret; + +@@ -818,7 +816,7 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * could come from some range other than the copied inline extent's + * destination range and we have no way to know that. + */ +- ret = btrfs_wait_ordered_range(BTRFS_I(inode), destoff, len); ++ ret = btrfs_wait_ordered_range(inode, destoff, len); + if (ret < 0) + return ret; + +@@ -826,7 +824,7 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * Invalidate page cache so that future reads will see the cloned data + * immediately and not the previous data. + */ +- ret = filemap_invalidate_inode(inode, false, destoff, end); ++ ret = filemap_invalidate_inode(&inode->vfs_inode, false, destoff, end); + if (ret < 0) + return ret; + +@@ -841,7 +839,7 @@ static int btrfs_remap_file_range_prep(struct file *file_in, loff_t pos_in, + { + struct btrfs_inode *inode_in = BTRFS_I(file_inode(file_in)); + struct btrfs_inode *inode_out = BTRFS_I(file_inode(file_out)); +- u64 bs = inode_out->root->fs_info->sectorsize; ++ const u32 bs = inode_out->root->fs_info->sectorsize; + u64 wb_len; + int ret; + +@@ -934,7 +932,7 @@ loff_t btrfs_remap_file_range(struct file *src_file, loff_t off, + bool same_inode = dst_inode == src_inode; + int ret; + +- if (btrfs_is_shutdown(inode_to_fs_info(file_inode(src_file)))) ++ if (btrfs_is_shutdown(src_inode->root->fs_info)) + return -EIO; + + if (remap_flags & ~(REMAP_FILE_DEDUP | REMAP_FILE_ADVISORY)) +@@ -953,8 +951,7 @@ loff_t btrfs_remap_file_range(struct file *src_file, loff_t off, + goto out_unlock; + + if (remap_flags & REMAP_FILE_DEDUP) +- ret = btrfs_extent_same(&src_inode->vfs_inode, off, len, +- &dst_inode->vfs_inode, destoff); ++ ret = btrfs_extent_same(src_inode, off, len, dst_inode, destoff); + else + ret = btrfs_clone_files(dst_file, src_file, off, len, destoff); + +diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c +--- a/fs/btrfs/relocation.c ++++ b/fs/btrfs/relocation.c +@@ -339,14 +339,15 @@ static struct btrfs_backref_node *walk_down_backref( + + static bool reloc_root_is_dead(const struct btrfs_root *root) + { +- /* +- * Pair with set_bit/clear_bit in clean_dirty_subvols and +- * btrfs_update_reloc_root. We need to see the updated bit before +- * trying to access reloc_root +- */ +- smp_rmb(); + if (test_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state)) + return true; ++ /* ++ * Pairs with set_bit/clear_bit in clear_reloc_root() and ++ * btrfs_update_reloc_root(). We need to see the updated bit before ++ * trying to access root->reloc_root in our callers. ++ */ ++ smp_rmb(); ++ + return false; + } + +@@ -1537,6 +1538,33 @@ static void clear_reloc_root(struct btrfs_root *root) + clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state); + } + ++/* Drop the reloc trees of a relocation that is being deferred and retried. */ ++static void abort_reloc_roots(struct reloc_control *rc, struct list_head *list) ++{ ++ struct btrfs_fs_info *fs_info = rc->extent_root->fs_info; ++ struct btrfs_root *reloc_root, *tmp; ++ ++ list_for_each_entry_safe(reloc_root, tmp, list, root_list) { ++ struct btrfs_root *root; ++ ++ root = btrfs_get_fs_root(fs_info, reloc_root->root_key.offset, false); ++ if (!IS_ERR(root)) { ++ if (root->reloc_root == reloc_root) { ++ clear_reloc_root(root); ++ btrfs_put_root(reloc_root); ++ } ++ btrfs_put_root(root); ++ } ++ ++ btrfs_set_root_refs(&reloc_root->root_item, 0); ++ memset(&reloc_root->root_item.drop_progress, 0, sizeof(struct btrfs_disk_key)); ++ btrfs_set_root_drop_level(&reloc_root->root_item, 0); ++ ++ list_del_init(&reloc_root->root_list); ++ list_add_tail(&reloc_root->reloc_dirty_list, &rc->dirty_subvol_roots); ++ } ++} ++ + static int clean_dirty_subvols(struct reloc_control *rc) + { + struct btrfs_root *root; +@@ -1876,8 +1904,7 @@ int prepare_to_merge(struct reloc_control *rc, int err) + return err; + } + +-static noinline_for_stack +-void merge_reloc_roots(struct reloc_control *rc) ++static noinline_for_stack int merge_reloc_roots(struct reloc_control *rc) + { + struct btrfs_fs_info *fs_info = rc->extent_root->fs_info; + struct btrfs_root *root; +@@ -1975,7 +2002,15 @@ void merge_reloc_roots(struct reloc_control *rc) + goto again; + } + out: +- if (ret) { ++ if (btrfs_is_zoned(fs_info) && ret == -EAGAIN) { ++ abort_reloc_roots(rc, &reloc_roots); ++ ++ /* New reloc root may be added. */ ++ mutex_lock(&fs_info->reloc_mutex); ++ list_splice_init(&rc->reloc_roots, &reloc_roots); ++ mutex_unlock(&fs_info->reloc_mutex); ++ abort_reloc_roots(rc, &reloc_roots); ++ } else if (ret) { + btrfs_handle_fs_error(fs_info, ret, NULL); + free_reloc_roots(&reloc_roots); + +@@ -2001,6 +2036,7 @@ void merge_reloc_roots(struct reloc_control *rc) + * + * The remaining nodes will be cleaned up by put_reloc_control(). + */ ++ return ret; + } + + static void free_block_list(struct rb_root *blocks) +@@ -3730,7 +3766,9 @@ static noinline_for_stack int relocate_block_group(struct reloc_control *rc) + */ + err = prepare_to_merge(rc, err); + +- merge_reloc_roots(rc); ++ ret = merge_reloc_roots(rc); ++ if (ret && !err) ++ err = ret; + + rc->merge_reloc_tree = false; + unset_reloc_control(rc); +@@ -4114,10 +4152,10 @@ static int copy_remapped_data(struct btrfs_fs_info *fs_info, u64 old_addr, + u64 new_addr, u64 length) + { + int ret; +- u64 copy_len = min_t(u64, length, SZ_1M); ++ const u64 copy_len = min_t(u64, length, SZ_1M); + struct page **pages; + struct reloc_io_private priv; +- unsigned int nr_pages = DIV_ROUND_UP(length, PAGE_SIZE); ++ const unsigned int nr_pages = DIV_ROUND_UP(copy_len, PAGE_SIZE); + + pages = kzalloc_objs(struct page *, nr_pages, GFP_NOFS); + if (!pages) +@@ -5720,7 +5758,9 @@ int btrfs_recover_relocation(struct btrfs_fs_info *fs_info) + goto out_drop_reloc_refs; + release_recovered_fs_roots(&recovered_roots, false); + +- merge_reloc_roots(rc); ++ ret = merge_reloc_roots(rc); ++ if (ret) ++ goto out_unset; + + unset_reloc_control(rc); + +diff --git a/fs/btrfs/root-tree.c b/fs/btrfs/root-tree.c +--- a/fs/btrfs/root-tree.c ++++ b/fs/btrfs/root-tree.c +@@ -265,15 +265,15 @@ int btrfs_find_orphan_roots(struct btrfs_fs_info *fs_info) + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + btrfs_err(fs_info, +- "failed to join transaction to delete orphan item: %d", +- ret); ++ "failed to join transaction to delete orphan item: %pe", ++ ERR_PTR(ret)); + return ret; + } + ret = btrfs_del_orphan_item(trans, tree_root, root_objectid); + btrfs_end_transaction(trans); + if (ret) { + btrfs_err(fs_info, +- "failed to delete root orphan item: %d", ret); ++ "failed to delete root orphan item: %pe", ERR_PTR(ret)); + return ret; + } + continue; +diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c +--- a/fs/btrfs/scrub.c ++++ b/fs/btrfs/scrub.c +@@ -57,12 +57,6 @@ struct scrub_ctx; + + #define SCRUB_TOTAL_STRIPES (SCRUB_GROUPS_PER_SCTX * SCRUB_STRIPES_PER_GROUP) + +-/* +- * The following value times PAGE_SIZE needs to be large enough to match the +- * largest node/leaf/sector size that shall be supported. +- */ +-#define SCRUB_MAX_SECTORS_PER_BLOCK (BTRFS_MAX_METADATA_BLOCKSIZE / SZ_4K) +- + /* Represent one sector and its needed info to verify the content. */ + struct scrub_sector_verification { + union { +@@ -129,19 +123,17 @@ enum { + scrub_bitmap_nr_last, + }; + +-#define SCRUB_STRIPE_MAX_FOLIOS (BTRFS_STRIPE_LEN / PAGE_SIZE) +- + /* + * Represent one contiguous range with a length of BTRFS_STRIPE_LEN. + */ + struct scrub_stripe { + struct scrub_ctx *sctx; + struct btrfs_block_group *bg; +- +- struct folio *folios[SCRUB_STRIPE_MAX_FOLIOS]; + struct scrub_sector_verification *sectors; +- + struct btrfs_device *dev; ++ ++ void *buffer; ++ + u64 logical; + u64 physical; + +@@ -227,6 +219,9 @@ struct scrub_ctx { + refcount_t refs; + }; + ++static_assert(BTRFS_STRIPE_LEN >= PAGE_SIZE); ++static_assert(IS_ALIGNED(BTRFS_STRIPE_LEN, PAGE_SIZE)); ++ + #define scrub_calc_start_bit(stripe, name, block_nr) \ + ({ \ + unsigned int __start_bit; \ +@@ -338,13 +333,10 @@ static void release_scrub_stripe(struct scrub_stripe *stripe) + if (!stripe) + return; + +- for (int i = 0; i < SCRUB_STRIPE_MAX_FOLIOS; i++) { +- if (stripe->folios[i]) +- folio_put(stripe->folios[i]); +- stripe->folios[i] = NULL; +- } ++ kvfree(stripe->buffer); + kfree(stripe->sectors); + kfree(stripe->csums); ++ stripe->buffer = NULL; + stripe->sectors = NULL; + stripe->csums = NULL; + stripe->sctx = NULL; +@@ -354,9 +346,6 @@ static void release_scrub_stripe(struct scrub_stripe *stripe) + static int init_scrub_stripe(struct btrfs_fs_info *fs_info, + struct scrub_stripe *stripe) + { +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; +- int ret; +- + memset(stripe, 0, sizeof(*stripe)); + + stripe->nr_sectors = BTRFS_STRIPE_LEN >> fs_info->sectorsize_bits; +@@ -367,11 +356,8 @@ static int init_scrub_stripe(struct btrfs_fs_info *fs_info, + atomic_set(&stripe->pending_io, 0); + spin_lock_init(&stripe->write_error_lock); + +- ASSERT(BTRFS_STRIPE_LEN >> min_folio_shift <= SCRUB_STRIPE_MAX_FOLIOS); +- ret = btrfs_alloc_folio_array(BTRFS_STRIPE_LEN >> min_folio_shift, +- fs_info->block_min_order, stripe->folios, +- GFP_NOFS); +- if (ret < 0) ++ stripe->buffer = kvmalloc(BTRFS_STRIPE_LEN, GFP_NOFS); ++ if (!stripe->buffer) + goto error; + + stripe->sectors = kzalloc_objs(struct scrub_sector_verification, +@@ -682,32 +668,18 @@ static int fill_writer_pointer_gap(struct scrub_ctx *sctx, u64 physical) + return ret; + } + +-static void *scrub_stripe_get_kaddr(struct scrub_stripe *stripe, int sector_nr) ++/* ++ * Unlike the existing csum which is based on paddr, this version is fully on ++ * vaddr, so no extra per-page iteration needed. ++ */ ++static void scrub_calc_vaddr_csum(struct btrfs_fs_info *fs_info, ++ void *vaddr, unsigned int len, u8 *dest) + { +- struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; +- u32 offset = (sector_nr << fs_info->sectorsize_bits); +- const struct folio *folio = stripe->folios[offset >> min_folio_shift]; ++ struct btrfs_csum_ctx csum; + +- /* stripe->folios[] is allocated by us and no highmem is allowed. */ +- ASSERT(folio); +- ASSERT(!folio_test_highmem(folio)); +- return folio_address(folio) + offset_in_folio(folio, offset); +-} +- +-static phys_addr_t scrub_stripe_get_paddr(struct scrub_stripe *stripe, int sector_nr) +-{ +- struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; +- u32 offset = (sector_nr << fs_info->sectorsize_bits); +- const struct folio *folio = stripe->folios[offset >> min_folio_shift]; +- +- /* stripe->folios[] is allocated by us and no highmem is allowed. */ +- ASSERT(folio); +- ASSERT(!folio_test_highmem(folio)); +- /* And the range must be contained inside the folio. */ +- ASSERT(offset_in_folio(folio, offset) + fs_info->sectorsize <= folio_size(folio)); +- return page_to_phys(folio_page(folio, 0)) + offset_in_folio(folio, offset); ++ btrfs_csum_init(&csum, fs_info->csum_type); ++ btrfs_csum_update(&csum, vaddr, len); ++ btrfs_csum_final(&csum, dest); + } + + static void scrub_verify_one_metadata(struct scrub_stripe *stripe, int sector_nr) +@@ -715,19 +687,10 @@ static void scrub_verify_one_metadata(struct scrub_stripe *stripe, int sector_nr + struct btrfs_fs_info *fs_info = stripe->bg->fs_info; + const u32 sectors_per_tree = fs_info->nodesize >> fs_info->sectorsize_bits; + const u64 logical = stripe->logical + (sector_nr << fs_info->sectorsize_bits); +- void *first_kaddr = scrub_stripe_get_kaddr(stripe, sector_nr); +- struct btrfs_header *header = first_kaddr; +- struct btrfs_csum_ctx csum; +- u8 on_disk_csum[BTRFS_CSUM_SIZE]; ++ void *first_vaddr = stripe->buffer + (sector_nr << fs_info->sectorsize_bits); ++ struct btrfs_header *header = first_vaddr; + u8 calculated_csum[BTRFS_CSUM_SIZE]; + +- /* +- * Here we don't have a good way to attach the pages (and subpages) +- * to a dummy extent buffer, thus we have to directly grab the members +- * from pages. +- */ +- memcpy(on_disk_csum, header->csum, fs_info->csum_size); +- + if (logical != btrfs_stack_header_bytenr(header)) { + scrub_bitmap_set_meta_error(stripe, sector_nr, sectors_per_tree); + scrub_bitmap_set_error(stripe, sector_nr, sectors_per_tree); +@@ -759,23 +722,15 @@ static void scrub_verify_one_metadata(struct scrub_stripe *stripe, int sector_nr + } + + /* Now check tree block csum. */ +- btrfs_csum_init(&csum, fs_info->csum_type); +- btrfs_csum_update(&csum, first_kaddr + BTRFS_CSUM_SIZE, +- fs_info->sectorsize - BTRFS_CSUM_SIZE); +- +- for (int i = sector_nr + 1; i < sector_nr + sectors_per_tree; i++) { +- btrfs_csum_update(&csum, scrub_stripe_get_kaddr(stripe, i), +- fs_info->sectorsize); +- } +- +- btrfs_csum_final(&csum, calculated_csum); +- if (memcmp(calculated_csum, on_disk_csum, fs_info->csum_size) != 0) { ++ scrub_calc_vaddr_csum(fs_info, first_vaddr + BTRFS_CSUM_SIZE, ++ fs_info->nodesize - BTRFS_CSUM_SIZE, calculated_csum); ++ if (memcmp(calculated_csum, header->csum, fs_info->csum_size) != 0) { + scrub_bitmap_set_meta_error(stripe, sector_nr, sectors_per_tree); + scrub_bitmap_set_error(stripe, sector_nr, sectors_per_tree); + btrfs_warn_rl(fs_info, + "scrub: tree block %llu mirror %u has bad csum, has " BTRFS_CSUM_FMT " want " BTRFS_CSUM_FMT, + logical, stripe->mirror_num, +- BTRFS_CSUM_FMT_VALUE(fs_info->csum_size, on_disk_csum), ++ BTRFS_CSUM_FMT_VALUE(fs_info->csum_size, header->csum), + BTRFS_CSUM_FMT_VALUE(fs_info->csum_size, calculated_csum)); + return; + } +@@ -801,9 +756,7 @@ static void scrub_verify_one_sector(struct scrub_stripe *stripe, int sector_nr) + struct btrfs_fs_info *fs_info = stripe->bg->fs_info; + struct scrub_sector_verification *sector = &stripe->sectors[sector_nr]; + const u32 sectors_per_tree = fs_info->nodesize >> fs_info->sectorsize_bits; +- phys_addr_t paddr = scrub_stripe_get_paddr(stripe, sector_nr); + u8 csum_buf[BTRFS_CSUM_SIZE]; +- int ret; + + ASSERT(sector_nr >= 0 && sector_nr < stripe->nr_sectors); + +@@ -846,8 +799,10 @@ static void scrub_verify_one_sector(struct scrub_stripe *stripe, int sector_nr) + return; + } + +- ret = btrfs_check_block_csum(fs_info, paddr, csum_buf, sector->csum); +- if (ret < 0) { ++ scrub_calc_vaddr_csum(fs_info, ++ stripe->buffer + (sector_nr << fs_info->sectorsize_bits), ++ fs_info->sectorsize, csum_buf); ++ if (memcmp(csum_buf, sector->csum, fs_info->csum_size)) { + scrub_bitmap_set_bit_csum_error(stripe, sector_nr); + scrub_bitmap_set_bit_error(stripe, sector_nr); + } else { +@@ -870,16 +825,51 @@ static void scrub_verify_one_stripe(struct scrub_stripe *stripe, unsigned long b + } + } + +-static int calc_sector_number(struct scrub_stripe *stripe, struct bio_vec *first_bvec) ++static unsigned int calc_sector_number(const struct btrfs_bio *bbio) + { +- int i; ++ const struct scrub_stripe *stripe = bbio->private; ++ const struct btrfs_fs_info *fs_info = stripe->bg->fs_info; + +- for (i = 0; i < stripe->nr_sectors; i++) { +- if (scrub_stripe_get_kaddr(stripe, i) == bvec_virt(first_bvec)) +- break; ++ /* Scrub bbios all have their @file_offset set to the logical bytenr. */ ++ ASSERT(bbio->file_offset >= stripe->logical && ++ bbio->file_offset < stripe->logical + (stripe->nr_sectors << ++ fs_info->sectorsize_bits), ++ "scrub bio logical=%llu stripe logical=%llu stripe len=%u", ++ bbio->file_offset, stripe->logical, ++ stripe->nr_sectors << fs_info->sectorsize_bits); ++ return (bbio->file_offset - stripe->logical) >> fs_info->sectorsize_bits; ++} ++ ++/* ++ * Common handling of read endio. ++ * ++ * The bbio will be released, so no more access to @bbio after this function. ++ */ ++static void scrub_read_endio_common(struct btrfs_bio *bbio) ++{ ++ struct scrub_stripe *stripe = bbio->private; ++ struct btrfs_fs_info *fs_info = stripe->bg->fs_info; ++ unsigned int sector_nr = calc_sector_number(bbio); ++ const u32 bio_size = bio_get_size(&bbio->bio); ++ const u32 sectors = bio_size >> fs_info->sectorsize_bits; ++ ++ ++ /* ++ * For vmallocated space, readers need to call invalidate_kernel_vmap_range() ++ * to manage the coherency between kernel mapping and devie space mapping. ++ */ ++ if (is_vmalloc_addr(stripe->buffer)) ++ invalidate_kernel_vmap_range( ++ stripe->buffer + (sector_nr << fs_info->sectorsize_bits), ++ bio_size); ++ ++ if (bbio->bio.bi_status) { ++ scrub_bitmap_set_io_error(stripe, sector_nr, sectors); ++ scrub_bitmap_set_error(stripe, sector_nr, sectors); ++ } else { ++ scrub_bitmap_clear_io_error(stripe, sector_nr, sectors); + } +- ASSERT(i < stripe->nr_sectors); +- return i; ++ bio_put(&bbio->bio); + } + + /* +@@ -891,22 +881,9 @@ static int calc_sector_number(struct scrub_stripe *stripe, struct bio_vec *first + static void scrub_repair_read_endio(struct btrfs_bio *bbio) + { + struct scrub_stripe *stripe = bbio->private; +- struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- int sector_nr = calc_sector_number(stripe, bio_first_bvec_all(&bbio->bio)); +- const u32 bio_size = bio_get_size(&bbio->bio); + +- ASSERT(sector_nr < stripe->nr_sectors); ++ scrub_read_endio_common(bbio); + +- if (bbio->bio.bi_status) { +- scrub_bitmap_set_io_error(stripe, sector_nr, +- bio_size >> fs_info->sectorsize_bits); +- scrub_bitmap_set_error(stripe, sector_nr, +- bio_size >> fs_info->sectorsize_bits); +- } else { +- scrub_bitmap_clear_io_error(stripe, sector_nr, +- bio_size >> fs_info->sectorsize_bits); +- } +- bio_put(&bbio->bio); + if (atomic_dec_and_test(&stripe->pending_io)) + wake_up(&stripe->io_wait); + } +@@ -921,30 +898,35 @@ static void scrub_bio_add_sector(struct btrfs_bio *bbio, struct scrub_stripe *st + int sector_nr) + { + struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info; +- void *kaddr = scrub_stripe_get_kaddr(stripe, sector_nr); ++ const u32 offset = sector_nr << fs_info->sectorsize_bits; + int ret; + +- ret = bio_add_page(&bbio->bio, virt_to_page(kaddr), fs_info->sectorsize, +- offset_in_page(kaddr)); +- /* +- * Caller should ensure the bbio has enough size. +- * And we cannot use __bio_add_page(), which doesn't do any merge. +- * +- * Meanwhile for scrub_submit_initial_read() we fully rely on the merge +- * to create the minimal amount of bio vectors, for fs block size < page +- * size cases. +- */ ++ ASSERT(offset + fs_info->sectorsize <= BTRFS_STRIPE_LEN); ++ ++ if (is_vmalloc_addr(stripe->buffer)) { ++ ret = bio_add_vmalloc(&bbio->bio, stripe->buffer + offset, fs_info->sectorsize); ++ ASSERT(ret == true); ++ return; ++ } ++ ret = bio_add_page(&bbio->bio, virt_to_page(stripe->buffer + offset), ++ fs_info->sectorsize, offset_in_page(stripe->buffer + offset)); + ASSERT(ret == fs_info->sectorsize); + } + + static struct btrfs_bio *alloc_scrub_bbio(struct btrfs_fs_info *fs_info, +- unsigned int nr_vecs, blk_opf_t opf, ++ blk_opf_t opf, + u64 logical, + btrfs_bio_end_io_t end_io, void *private) + { + struct btrfs_bio *bbio; + +- bbio = btrfs_bio_alloc(nr_vecs, opf, BTRFS_I(fs_info->btree_inode), ++ /* ++ * Stripe->buffer is allocated by kvmalloc(), which can be pages at ++ * different physical addresses, we have to ensure the bbio is large ++ * enough to contain the full stripe. ++ */ ++ bbio = btrfs_bio_alloc(BTRFS_STRIPE_LEN >> PAGE_SHIFT, opf, ++ BTRFS_I(fs_info->btree_inode), + logical, end_io, private); + bbio->is_scrub = true; + bbio->bio.bi_iter.bi_sector = logical >> SECTOR_SHIFT; +@@ -976,7 +958,7 @@ static void scrub_stripe_submit_repair_read(struct scrub_stripe *stripe, + } + + if (!bbio) +- bbio = alloc_scrub_bbio(fs_info, stripe->nr_sectors, REQ_OP_READ, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_READ, + stripe->logical + (i << fs_info->sectorsize_bits), + scrub_repair_read_endio, stripe); + +@@ -1245,20 +1227,9 @@ static void scrub_stripe_read_repair_worker(struct work_struct *work) + static void scrub_read_endio(struct btrfs_bio *bbio) + { + struct scrub_stripe *stripe = bbio->private; +- int sector_nr = calc_sector_number(stripe, bio_first_bvec_all(&bbio->bio)); +- int num_sectors; +- const u32 bio_size = bio_get_size(&bbio->bio); + +- ASSERT(sector_nr < stripe->nr_sectors); +- num_sectors = bio_size >> stripe->bg->fs_info->sectorsize_bits; ++ scrub_read_endio_common(bbio); + +- if (bbio->bio.bi_status) { +- scrub_bitmap_set_io_error(stripe, sector_nr, num_sectors); +- scrub_bitmap_set_error(stripe, sector_nr, num_sectors); +- } else { +- scrub_bitmap_clear_io_error(stripe, sector_nr, num_sectors); +- } +- bio_put(&bbio->bio); + if (atomic_dec_and_test(&stripe->pending_io)) { + wake_up(&stripe->io_wait); + INIT_WORK(&stripe->work, scrub_stripe_read_repair_worker); +@@ -1270,7 +1241,7 @@ static void scrub_write_endio(struct btrfs_bio *bbio) + { + struct scrub_stripe *stripe = bbio->private; + struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- int sector_nr = calc_sector_number(stripe, bio_first_bvec_all(&bbio->bio)); ++ unsigned int sector_nr = calc_sector_number(bbio); + const u32 bio_size = bio_get_size(&bbio->bio); + + if (bbio->bio.bi_status) { +@@ -1349,7 +1320,7 @@ static void scrub_write_sectors(struct scrub_ctx *sctx, struct scrub_stripe *str + bbio = NULL; + } + if (!bbio) +- bbio = alloc_scrub_bbio(fs_info, stripe->nr_sectors, REQ_OP_WRITE, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_WRITE, + stripe->logical + (sector_nr << fs_info->sectorsize_bits), + scrub_write_endio, stripe); + scrub_bio_add_sector(bbio, stripe, sector_nr); +@@ -1844,7 +1815,7 @@ static void scrub_submit_extent_sector_read(struct scrub_stripe *stripe) + continue; + } + +- bbio = alloc_scrub_bbio(fs_info, stripe->nr_sectors, REQ_OP_READ, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_READ, + logical, scrub_read_endio, stripe); + } + +@@ -1869,7 +1840,6 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx, + { + struct btrfs_fs_info *fs_info = sctx->fs_info; + struct btrfs_bio *bbio; +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; + unsigned int nr_sectors = stripe_length(stripe) >> fs_info->sectorsize_bits; + int mirror = stripe->mirror_num; + +@@ -1882,7 +1852,7 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx, + return; + } + +- bbio = alloc_scrub_bbio(fs_info, BTRFS_STRIPE_LEN >> min_folio_shift, REQ_OP_READ, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_READ, + stripe->logical, scrub_read_endio, stripe); + /* Read the whole range inside the chunk boundary. */ + for (unsigned int cur = 0; cur < nr_sectors; cur++) +@@ -2138,7 +2108,7 @@ static int scrub_raid56_cached_parity(struct scrub_ctx *sctx, + for (int i = 0; i < data_stripes; i++) { + struct scrub_stripe *stripe = &sctx->raid56_data_stripes[i]; + +- raid56_parity_cache_data_folios(rbio, stripe->folios, ++ raid56_parity_cache_data_folios(rbio, stripe->buffer, + full_stripe_start + (i << BTRFS_STRIPE_LEN_SHIFT)); + } + raid56_parity_submit_scrub_rbio(rbio); +@@ -3091,14 +3061,6 @@ int btrfs_scrub_dev(struct btrfs_fs_info *fs_info, u64 devid, u64 start, + /* At mount time we have ensured nodesize is in the range of [4K, 64K]. */ + ASSERT(fs_info->nodesize <= BTRFS_STRIPE_LEN); + +- /* +- * SCRUB_MAX_SECTORS_PER_BLOCK is calculated using the largest possible +- * value (max nodesize / min sectorsize), thus nodesize should always +- * be fine. +- */ +- ASSERT(fs_info->nodesize <= +- SCRUB_MAX_SECTORS_PER_BLOCK << fs_info->sectorsize_bits); +- + /* Allocate outside of device_list_mutex */ + sctx = scrub_setup_ctx(fs_info, is_dev_replace); + if (IS_ERR(sctx)) +diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c +--- a/fs/btrfs/send.c ++++ b/fs/btrfs/send.c +@@ -130,10 +130,10 @@ static_assert(offsetof(struct backref_cache_entry, entry) == 0); + #define SEND_MAX_DIR_CREATED_CACHE_SIZE 64 + + /* +- * Max number of entries in the cache that stores directories that were already +- * created. The cache uses raw struct btrfs_lru_cache_entry entries, so it uses +- * at most 4096 bytes - sizeof(struct btrfs_lru_cache_entry) is 48 bytes, but +- * the kmalloc-64 slab is used, so we get 4096 bytes (64 bytes * 64). ++ * Maximum number of entries in the cache that stores utimes values for directories. ++ * The cache uses raw struct btrfs_lru_cache_entry entries, so it uses at most ++ * 4096 bytes - sizeof(struct btrfs_lru_cache_entry) is 48 bytes, but the ++ * kmalloc-64 slab is used, so we get 4096 bytes (64 bytes * 64). + */ + #define SEND_MAX_DIR_UTIMES_CACHE_SIZE 64 + +@@ -625,9 +625,8 @@ static void fs_path_unreverse(struct fs_path *p) + static inline bool is_current_inode_path(const struct send_ctx *sctx, + const struct fs_path *path) + { +- const struct fs_path *cur = &sctx->cur_inode_path; +- +- return (strncmp(path->start, cur->start, fs_path_len(cur)) == 0); ++ /* Paths are always nul terminated. */ ++ return (strcmp(path->start, sctx->cur_inode_path.start) == 0); + } + + static struct btrfs_path *alloc_path_for_send(void) +@@ -6033,7 +6032,7 @@ static int send_write_or_clone(struct send_ctx *sctx, + int ret = 0; + u64 offset = key->offset; + u64 end; +- u64 bs = sctx->send_root->fs_info->sectorsize; ++ const u32 bs = sctx->send_root->fs_info->sectorsize; + struct btrfs_file_extent_item *ei; + u64 disk_byte; + u64 data_offset; +@@ -8251,7 +8250,7 @@ long btrfs_ioctl_send(struct btrfs_root *send_root, const struct btrfs_ioctl_sen + } + + if (sort_clone_roots) { +- for (i = 0; i < sctx->clone_roots_cnt; i++) { ++ for (i = 0; sctx && i < sctx->clone_roots_cnt; i++) { + btrfs_root_dec_send_in_progress( + sctx->clone_roots[i].root); + btrfs_put_root(sctx->clone_roots[i].root); +diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c +--- a/fs/btrfs/space-info.c ++++ b/fs/btrfs/space-info.c +@@ -2156,7 +2156,7 @@ static bool do_reclaim_sweep(struct btrfs_space_info *space_info, int raid) + will_reclaim = true; + reclaim = true; + } +- bg->reclaim_mark++; ++ bg->reclaim_mark = true; + spin_unlock(&bg->lock); + if (reclaim) + btrfs_mark_bg_to_reclaim(bg); +diff --git a/fs/btrfs/subpage.c b/fs/btrfs/subpage.c +--- a/fs/btrfs/subpage.c ++++ b/fs/btrfs/subpage.c +@@ -59,7 +59,7 @@ int btrfs_attach_folio_state(const struct btrfs_fs_info *fs_info, + if (type == BTRFS_SUBPAGE_DATA && !btrfs_is_subpage(fs_info, folio)) + return 0; + +- bfs = btrfs_alloc_folio_state(fs_info, folio_size(folio), type); ++ bfs = btrfs_alloc_folio_state(fs_info, folio_size(folio), type, GFP_NOFS); + if (IS_ERR(bfs)) + return PTR_ERR(bfs); + +@@ -86,7 +86,8 @@ void btrfs_detach_folio_state(const struct btrfs_fs_info *fs_info, struct folio + } + + struct btrfs_folio_state *btrfs_alloc_folio_state(const struct btrfs_fs_info *fs_info, +- size_t fsize, enum btrfs_folio_type type) ++ size_t fsize, enum btrfs_folio_type type, ++ gfp_t gfp) + { + struct btrfs_folio_state *ret; + unsigned int real_size; +@@ -96,7 +97,7 @@ struct btrfs_folio_state *btrfs_alloc_folio_state(const struct btrfs_fs_info *fs + real_size = struct_size(ret, bitmaps, + BITS_TO_LONGS(btrfs_bitmap_nr_max * + (fsize >> fs_info->sectorsize_bits))); +- ret = kzalloc(real_size, GFP_NOFS); ++ ret = kzalloc(real_size, gfp); + if (!ret) + return ERR_PTR(-ENOMEM); + +diff --git a/fs/btrfs/subpage.h b/fs/btrfs/subpage.h +--- a/fs/btrfs/subpage.h ++++ b/fs/btrfs/subpage.h +@@ -110,7 +110,8 @@ void btrfs_detach_folio_state(const struct btrfs_fs_info *fs_info, struct folio + + /* Allocate additional data where page represents more than one sector */ + struct btrfs_folio_state *btrfs_alloc_folio_state(const struct btrfs_fs_info *fs_info, +- size_t fsize, enum btrfs_folio_type type); ++ size_t fsize, enum btrfs_folio_type type, ++ gfp_t gfp); + static inline void btrfs_free_folio_state(struct btrfs_folio_state *bfs) + { + kfree(bfs); +diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c +--- a/fs/btrfs/super.c ++++ b/fs/btrfs/super.c +@@ -129,7 +129,6 @@ enum { + + /* Rescue options */ + Opt_rescue, +- Opt_usebackuproot, + + /* Debugging options */ + Opt_enospc_debug, +@@ -249,8 +248,6 @@ static const struct fs_parameter_spec btrfs_fs_parameters[] = { + + /* Rescue options. */ + fsparam_enum("rescue", Opt_rescue, btrfs_parameter_rescue), +- /* Deprecated, with alias rescue=usebackuproot */ +- __fsparam(NULL, "usebackuproot", Opt_usebackuproot, fs_param_deprecated, NULL), + /* For compatibility only, alias for "rescue=nologreplay". */ + fsparam_flag("norecovery", Opt_norecovery), + +@@ -514,19 +511,20 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + btrfs_clear_opt(ctx->mount_opt, NODISCARD); + break; + case Opt_space_cache: +- if (result.negated) { +- btrfs_set_opt(ctx->mount_opt, NOSPACECACHE); +- btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE); +- btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); +- } else { +- btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); +- btrfs_set_opt(ctx->mount_opt, SPACE_CACHE); +- } ++ if (!result.negated) ++ btrfs_warn(NULL, ++ "v1 space cache is deprecated, falling back to no space cache"); ++ btrfs_set_opt(ctx->mount_opt, NOSPACECACHE); ++ btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE); ++ btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); + break; + case Opt_space_cache_version: + switch (result.uint_32) { + case Opt_space_cache_v1: +- btrfs_set_opt(ctx->mount_opt, SPACE_CACHE); ++ btrfs_warn(NULL, ++ "v1 space cache is deprecated, falling back to no space cache"); ++ btrfs_set_opt(ctx->mount_opt, NOSPACECACHE); ++ btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE); + btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); + break; + case Opt_space_cache_v2: +@@ -560,14 +558,6 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + else + btrfs_set_opt(ctx->mount_opt, AUTO_DEFRAG); + break; +- case Opt_usebackuproot: +- btrfs_warn(NULL, +- "'usebackuproot' is deprecated, use 'rescue=usebackuproot' instead"); +- btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT); +- +- /* If we're loading the backup roots we can't trust the space cache. */ +- btrfs_set_opt(ctx->mount_opt, CLEAR_CACHE); +- break; + case Opt_skip_balance: + btrfs_set_opt(ctx->mount_opt, SKIP_BALANCE); + break; +@@ -620,6 +610,7 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + btrfs_set_opt(ctx->mount_opt, IGNORESUPERFLAGS); + btrfs_set_opt(ctx->mount_opt, IGNOREBADROOTS); + btrfs_set_opt(ctx->mount_opt, NOLOGREPLAY); ++ btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT); + break; + default: + btrfs_info(NULL, "unrecognized rescue option '%s'", +@@ -668,7 +659,6 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + */ + static void btrfs_clear_oneshot_options(struct btrfs_fs_info *fs_info) + { +- btrfs_clear_opt(fs_info->mount_opt, USEBACKUPROOT); + btrfs_clear_opt(fs_info->mount_opt, CLEAR_CACHE); + btrfs_clear_opt(fs_info->mount_opt, NOSPACECACHE); + } +@@ -692,7 +682,8 @@ bool btrfs_check_options(const struct btrfs_fs_info *info, + bool ret = true; + + if (!(flags & SB_RDONLY) && +- (check_ro_option(info, *mount_opt, BTRFS_MOUNT_NOLOGREPLAY, "nologreplay") || ++ (check_ro_option(info, *mount_opt, BTRFS_MOUNT_USEBACKUPROOT, "usebackuproot") || ++ check_ro_option(info, *mount_opt, BTRFS_MOUNT_NOLOGREPLAY, "nologreplay") || + check_ro_option(info, *mount_opt, BTRFS_MOUNT_IGNOREBADROOTS, "ignorebadroots") || + check_ro_option(info, *mount_opt, BTRFS_MOUNT_IGNOREDATACSUMS, "ignoredatacsums") || + check_ro_option(info, *mount_opt, BTRFS_MOUNT_IGNOREMETACSUMS, "ignoremetacsums") || +@@ -982,7 +973,7 @@ static int btrfs_fill_super(struct super_block *sb, + + ret = open_ctree(sb, fs_devices); + if (ret) { +- btrfs_err(fs_info, "open_ctree failed: %d", ret); ++ btrfs_err(fs_info, "open_ctree failed: %pe", ERR_PTR(ret)); + return ret; + } + +diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c +--- a/fs/btrfs/sysfs.c ++++ b/fs/btrfs/sysfs.c +@@ -1336,7 +1336,7 @@ char *btrfs_get_mod_read_policy(void) + return read_policy; + } + +-/* Set perms to 0, disable /sys/module/btrfs/parameter/read_policy interface. */ ++/* Set perms to 0, disable /sys/module/btrfs/parameters/read_policy interface. */ + module_param(read_policy, charp, 0); + MODULE_PARM_DESC(read_policy, + "Global read policy: pid (default), round-robin[:], devid[:]"); +diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c +--- a/fs/btrfs/transaction.c ++++ b/fs/btrfs/transaction.c +@@ -698,8 +698,6 @@ start_transaction(struct btrfs_root *root, unsigned int num_items, + goto alloc_fail; + } + +- xa_init(&h->writeback_inhibited_ebs); +- + /* + * If we are JOIN_NOLOCK we're already committing a transaction and + * waiting on this guy, so we don't need to do the sb_start_intwrite +@@ -1519,12 +1517,8 @@ static noinline int commit_fs_roots(struct btrfs_trans_handle *trans) + ASSERT(atomic_read(&root->log_writers) == 0, + "atomic_read(&root->log_writers)=%d", + atomic_read(&root->log_writers)); +- ASSERT(atomic_read(&root->log_commit[0]) == 0, +- "atomic_read(&root->log_commit[0])=%d", +- atomic_read(&root->log_commit[0])); +- ASSERT(atomic_read(&root->log_commit[1]) == 0, +- "atomic_read(&root->log_commit[1])=%d", +- atomic_read(&root->log_commit[1])); ++ ASSERT(!root->log_commit[0]); ++ ASSERT(!root->log_commit[1]); + + radix_tree_tag_clear(&fs_info->fs_roots_radix, + (unsigned long)btrfs_root_id(root), +@@ -1642,7 +1636,7 @@ static int qgroup_account_snapshot(struct btrfs_trans_handle *trans, + ret = btrfs_write_and_wait_transaction(trans); + if (unlikely(ret)) { + btrfs_err(fs_info, +-"error while writing out transaction during qgroup snapshot accounting: %d", ret); ++"error while writing out transaction during qgroup snapshot accounting: %pe", ERR_PTR(ret)); + return ret; + } + +@@ -2588,7 +2582,7 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans) + + ret = btrfs_write_and_wait_transaction(trans); + if (unlikely(ret)) { +- btrfs_err(fs_info, "error while writing out transaction: %d", ret); ++ btrfs_err(fs_info, "error while writing out transaction: %pe", ERR_PTR(ret)); + mutex_unlock(&fs_info->tree_log_mutex); + goto scrub_continue; + } +@@ -2749,8 +2743,8 @@ void __cold __btrfs_abort_transaction(struct btrfs_trans_handle *trans, + WRITE_ONCE(trans->transaction->aborted, error); + trace_btrfs_transaction_abort(trans); + if (first_hit) { +- btrfs_err(fs_info, "Transaction %llu aborted (error %d)", +- trans->transid, error); ++ btrfs_err(fs_info, "Transaction %llu aborted (%pe)", ++ trans->transid, ERR_PTR(error)); + if (error == -ENOSPC) + btrfs_dump_space_info_for_trans_abort(fs_info); + } +diff --git a/fs/btrfs/transaction.h b/fs/btrfs/transaction.h +--- a/fs/btrfs/transaction.h ++++ b/fs/btrfs/transaction.h +@@ -7,12 +7,12 @@ + #define BTRFS_TRANSACTION_H + + #include ++#include + #include + #include + #include + #include + #include +-#include + #include "btrfs_inode.h" + #include "delayed-ref.h" + +@@ -23,6 +23,7 @@ struct btrfs_fs_info; + struct btrfs_root_item; + struct btrfs_root; + struct btrfs_path; ++struct extent_buffer; + + /* + * Signal that a direct IO write is in progress, to avoid deadlock for sync +@@ -136,6 +137,18 @@ enum { + + #define TRANS_EXTWRITERS (__TRANS_START | __TRANS_ATTACH) + ++/* ++ * Number of extent buffers a transaction handle tracks for writeback ++ * inhibition. The CLOCK reference bits pack into a u32 so this must not exceed ++ * 32, and keeping it a power of two lets the compiler reduce the CLOCK hand ++ * modulo to a mask. ++ */ ++#define BTRFS_INHIBITED_EBS_SLOTS 8 ++ ++static_assert(BTRFS_INHIBITED_EBS_SLOTS <= 32); ++static_assert(BTRFS_INHIBITED_EBS_SLOTS != 0 && ++ (BTRFS_INHIBITED_EBS_SLOTS & (BTRFS_INHIBITED_EBS_SLOTS - 1)) == 0); ++ + struct btrfs_trans_handle { + u64 transid; + u64 bytes_reserved; +@@ -163,8 +176,14 @@ struct btrfs_trans_handle { + struct btrfs_fs_info *fs_info; + struct list_head new_bgs; + struct btrfs_block_rsv delayed_rsv; +- /* Extent buffers with writeback inhibited by this handle. */ +- struct xarray writeback_inhibited_ebs; ++ ++ /* Extent buffers this handle has inhibited writeback on. */ ++ struct extent_buffer *inhibited_ebs[BTRFS_INHIBITED_EBS_SLOTS]; ++ /* CLOCK reference bit per slot. */ ++ u32 inhibited_ebs_referenced; ++ u32 nr_inhibited_ebs; ++ /* CLOCK hand. */ ++ u32 inhibited_ebs_hand; + }; + + /* +diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c +--- a/fs/btrfs/tree-log.c ++++ b/fs/btrfs/tree-log.c +@@ -221,7 +221,7 @@ static int btrfs_log_inode(struct btrfs_trans_handle *trans, + static int link_to_fixup_dir(struct walk_control *wc, u64 objectid); + static noinline int replay_dir_deletes(struct walk_control *wc, + u64 dirid, bool del_all); +-static void wait_log_commit(struct btrfs_root *root, int transid); ++static bool wait_log_commit(struct btrfs_root *root, int transid); + + /* + * tree logging is a special write ahead log used to make sure that +@@ -305,24 +305,13 @@ static int start_log_trans(struct btrfs_trans_handle *trans, + + again: + if (root->log_root) { +- int index = (root->log_transid + 1) % 2; +- + if (btrfs_need_log_full_commit(trans)) { + ret = BTRFS_LOG_FORCE_COMMIT; + goto out; + } + +- if (zoned && atomic_read(&root->log_commit[index])) { +- wait_log_commit(root, root->log_transid - 1); ++ if (zoned && wait_log_commit(root, root->log_transid - 1)) + goto again; +- } +- +- if (!root->log_start_pid) { +- clear_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state); +- root->log_start_pid = current->pid; +- } else if (root->log_start_pid != current->pid) { +- set_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state); +- } + } else { + /* + * This means fs_info->log_root_tree was already created +@@ -340,8 +329,6 @@ static int start_log_trans(struct btrfs_trans_handle *trans, + goto out; + + set_bit(BTRFS_ROOT_HAS_LOG_TREE, &root->state); +- clear_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state); +- root->log_start_pid = current->pid; + } + + atomic_inc(&root->log_writers); +@@ -372,13 +359,9 @@ static int join_running_log_trans(struct btrfs_root *root) + mutex_lock(&root->log_mutex); + again: + if (root->log_root) { +- int index = (root->log_transid + 1) % 2; +- + ret = 0; +- if (zoned && atomic_read(&root->log_commit[index])) { +- wait_log_commit(root, root->log_transid - 1); ++ if (zoned && wait_log_commit(root, root->log_transid - 1)) + goto again; +- } + atomic_inc(&root->log_writers); + } + mutex_unlock(&root->log_mutex); +@@ -2986,6 +2969,7 @@ static noinline int walk_down_log_tree(struct btrfs_path *path, int *level, + { + struct btrfs_trans_handle *trans = wc->trans; + struct btrfs_fs_info *fs_info = wc->log->fs_info; ++ struct btrfs_eb_prealloc pa = { 0 }; + u64 bytenr; + u64 ptr_gen; + struct extent_buffer *next; +@@ -3010,7 +2994,7 @@ static noinline int walk_down_log_tree(struct btrfs_path *path, int *level, + check.has_first_key = true; + btrfs_node_key_to_cpu(cur, &check.first_key, path->slots[*level]); + +- next = btrfs_find_create_tree_block(fs_info, bytenr, ++ next = btrfs_find_create_tree_block(fs_info, &pa, bytenr, + btrfs_header_owner(cur), + *level - 1); + if (IS_ERR(next)) { +@@ -3181,10 +3165,14 @@ static int update_log_root(struct btrfs_trans_handle *trans, + return ret; + } + +-static void wait_log_commit(struct btrfs_root *root, int transid) ++/* Returns true if we had to wait, false otherwise. */ ++static bool wait_log_commit(struct btrfs_root *root, int transid) + { + DEFINE_WAIT(wait); +- int index = transid % 2; ++ const int index = (transid >= 0 ? transid % 2 : -transid % 2); ++ ++ if (!root->log_commit[index]) ++ return false; + + /* + * we only allow two pending log transactions at a time, +@@ -3195,15 +3183,17 @@ static void wait_log_commit(struct btrfs_root *root, int transid) + prepare_to_wait(&root->log_commit_wait[index], + &wait, TASK_UNINTERRUPTIBLE); + +- if (!(root->log_transid_committed < transid && +- atomic_read(&root->log_commit[index]))) +- break; +- + mutex_unlock(&root->log_mutex); + schedule(); + mutex_lock(&root->log_mutex); ++ ++ if (!(root->log_transid_committed < transid && ++ root->log_commit[index])) ++ break; + } + finish_wait(&root->log_commit_wait[index], &wait); ++ ++ return true; + } + + static void wait_for_writer(struct btrfs_root *root) +@@ -3307,15 +3297,15 @@ static inline void btrfs_remove_all_log_ctxs(struct btrfs_root *root, + int btrfs_sync_log(struct btrfs_trans_handle *trans, + struct btrfs_root *root, struct btrfs_log_ctx *ctx) + { +- int index1; +- int index2; + int mark; + int ret; + struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_root *log = root->log_root; + struct btrfs_root *log_root_tree = fs_info->log_root_tree; + struct btrfs_root_item new_root_item; +- int log_transid = 0; ++ int log_transid = ctx->log_transid; ++ int index1 = log_transid % 2; ++ int index2; + struct btrfs_log_ctx root_log_ctx; + struct blk_plug plug; + u64 log_root_start; +@@ -3323,41 +3313,25 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + + mutex_lock(&root->log_mutex); + trace_btrfs_sync_log_enter(trans, root, ctx); +- log_transid = ctx->log_transid; + if (root->log_transid_committed >= log_transid) { + trace_btrfs_sync_log_exit(trans, root, ctx, ctx->log_ret); + mutex_unlock(&root->log_mutex); + return ctx->log_ret; + } + +- index1 = log_transid % 2; +- if (atomic_read(&root->log_commit[index1])) { +- wait_log_commit(root, log_transid); ++ if (wait_log_commit(root, log_transid)) { + trace_btrfs_sync_log_exit(trans, root, ctx, ctx->log_ret); + mutex_unlock(&root->log_mutex); + return ctx->log_ret; + } + ASSERT(log_transid == root->log_transid, + "log_transid=%d root->log_transid=%d", log_transid, root->log_transid); +- atomic_set(&root->log_commit[index1], 1); ++ root->log_commit[index1] = true; + + /* wait for previous tree log sync to complete */ +- if (atomic_read(&root->log_commit[(index1 + 1) % 2])) +- wait_log_commit(root, log_transid - 1); ++ wait_log_commit(root, log_transid - 1); + +- while (1) { +- int batch = atomic_read(&root->log_batch); +- /* when we're on an ssd, just kick the log commit out */ +- if (!btrfs_test_opt(fs_info, SSD) && +- test_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state)) { +- mutex_unlock(&root->log_mutex); +- schedule_timeout_uninterruptible(1); +- mutex_lock(&root->log_mutex); +- } +- wait_for_writer(root); +- if (batch == atomic_read(&root->log_batch)) +- break; +- } ++ wait_for_writer(root); + + /* bail out if we need to do a full commit */ + if (btrfs_need_log_full_commit(trans)) { +@@ -3414,7 +3388,6 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + + btrfs_set_root_log_transid(root, root->log_transid + 1); + log->log_transid = root->log_transid; +- root->log_start_pid = 0; + /* + * IO has been started, blocks of the log tree have WRITTEN flag set + * in their headers. new modifications of the log will be written to +@@ -3473,7 +3446,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + goto out; + } + +- if (atomic_read(&log_root_tree->log_commit[index2])) { ++ if (log_root_tree->log_commit[index2]) { + blk_finish_plug(&plug); + ret = btrfs_wait_tree_log_extents(log, mark); + wait_log_commit(log_root_tree, +@@ -3487,12 +3460,9 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + ASSERT(root_log_ctx.log_transid == log_root_tree->log_transid, + "root_log_ctx.log_transid=%d log_root_tree->log_transid=%d", + root_log_ctx.log_transid, log_root_tree->log_transid); +- atomic_set(&log_root_tree->log_commit[index2], 1); ++ log_root_tree->log_commit[index2] = true; + +- if (atomic_read(&log_root_tree->log_commit[(index2 + 1) % 2])) { +- wait_log_commit(log_root_tree, +- root_log_ctx.log_transid - 1); +- } ++ wait_log_commit(log_root_tree, root_log_ctx.log_transid - 1); + + /* + * now that we've moved on to the tree of log tree roots, +@@ -3590,7 +3560,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + + /* + * We know there can only be one task here, since we have not yet set +- * root->log_commit[index1] to 0 and any task attempting to sync the ++ * root->log_commit[index1] to false and any task attempting to sync the + * log must wait for the previous log transaction to commit if it's + * still in progress or wait for the current log transaction commit if + * someone else already started it. We use <= and not < because the +@@ -3606,7 +3576,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + btrfs_remove_all_log_ctxs(log_root_tree, index2, ret); + + log_root_tree->log_transid_committed++; +- atomic_set(&log_root_tree->log_commit[index2], 0); ++ log_root_tree->log_commit[index2] = false; + mutex_unlock(&log_root_tree->log_mutex); + + /* +@@ -3619,7 +3589,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + mutex_lock(&root->log_mutex); + btrfs_remove_all_log_ctxs(root, index1, ret); + root->log_transid_committed++; +- atomic_set(&root->log_commit[index1], 0); ++ root->log_commit[index1] = false; + mutex_unlock(&root->log_mutex); + + /* +@@ -5622,6 +5592,15 @@ static int btrfs_log_holes(struct btrfs_trans_handle *trans, + if (!btrfs_fs_incompat(fs_info, NO_HOLES) || i_size == 0) + return 0; + ++ /* ++ * If there are no prealloc extents (which can be located past i_size), ++ * and disk space used is greater than or equals to i_size, then there ++ * are no holes. ++ */ ++ if (!(inode->flags & BTRFS_INODE_PREALLOC) && ++ i_size <= inode_get_bytes(&inode->vfs_inode)) ++ return 0; ++ + key.objectid = ino; + key.type = BTRFS_EXTENT_DATA_KEY; + key.offset = 0; +diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c +--- a/fs/btrfs/verity.c ++++ b/fs/btrfs/verity.c +@@ -638,7 +638,7 @@ static int btrfs_end_enable_verity(struct file *filp, const void *desc, + rollback_ret = rollback_verity(inode); + if (rollback_ret) + btrfs_err(inode->root->fs_info, +- "failed to rollback verity items: %d", rollback_ret); ++ "failed to rollback verity items: %pe", ERR_PTR(rollback_ret)); + return ret; + } + +@@ -720,14 +720,18 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, + goto out; + + folio_lock(folio); +- /* If it's not uptodate after we have the lock, we got a read error. */ +- if (!folio_test_uptodate(folio)) { ++ /* Folio was truncated from mapping. */ ++ if (!folio->mapping) { + folio_unlock(folio); + folio_put(folio); +- return ERR_PTR(-EIO); ++ goto again; + } +- folio_unlock(folio); +- goto out; ++ /* Another reader may have filled the folio while we waited. */ ++ if (folio_test_uptodate(folio)) { ++ folio_unlock(folio); ++ goto out; ++ } ++ goto read_folio; + } + + folio = filemap_alloc_folio(mapping_gfp_constraint(inode->i_mapping, ~__GFP_FS), +@@ -744,6 +748,7 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, + return ERR_PTR(ret); + } + ++read_folio: + /* + * Merkle item keys are indexed from byte 0 in the merkle tree. + * They have the form: +@@ -753,6 +758,7 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, + ret = read_key_bytes(BTRFS_I(inode), BTRFS_VERITY_MERKLE_ITEM_KEY, off, + folio_address(folio), PAGE_SIZE, folio); + if (ret < 0) { ++ folio_unlock(folio); + folio_put(folio); + return ERR_PTR(ret); + } +diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c +--- a/fs/btrfs/volumes.c ++++ b/fs/btrfs/volumes.c +@@ -740,41 +740,6 @@ const u8 *btrfs_sb_fsid_ptr(const struct btrfs_super_block *sb) + return has_metadata_uuid ? sb->metadata_uuid : sb->fsid; + } + +-static bool is_same_device(struct btrfs_device *device, const char *new_path) +-{ +- struct path old = { .mnt = NULL, .dentry = NULL }; +- struct path new = { .mnt = NULL, .dentry = NULL }; +- char AUTO_KFREE(old_path); +- bool is_same = false; +- int ret; +- +- if (!device->name) +- goto out; +- +- old_path = kzalloc(PATH_MAX, GFP_NOFS); +- if (!old_path) +- goto out; +- +- rcu_read_lock(); +- ret = strscpy(old_path, rcu_dereference(device->name), PATH_MAX); +- rcu_read_unlock(); +- if (ret < 0) +- goto out; +- +- ret = kern_path(old_path, LOOKUP_FOLLOW, &old); +- if (ret) +- goto out; +- ret = kern_path(new_path, LOOKUP_FOLLOW, &new); +- if (ret) +- goto out; +- if (path_equal(&old, &new)) +- is_same = true; +-out: +- path_put(&old); +- path_put(&new); +- return is_same; +-} +- + /* + * Add new device to list of registered devices + * +@@ -895,7 +860,7 @@ static noinline struct btrfs_device *device_list_add(const char *path, + MAJOR(path_devt), MINOR(path_devt), + current->comm, task_pid_nr(current)); + +- } else if (!device->name || !is_same_device(device, path)) { ++ } else if (!device->name || device->devt != path_devt) { + const char *old_name; + + /* +@@ -4588,7 +4553,7 @@ static int __btrfs_balance(struct btrfs_fs_info *fs_info) + if (ret == -ENOSPC) { + enospc_errors++; + } else if (ret == -ETXTBSY) { +- btrfs_info(fs_info, ++ btrfs_warn(fs_info, + "skipping relocation of block group %llu due to active swapfile", + found_key.offset); + ret = 0; +@@ -6049,6 +6014,19 @@ struct btrfs_chunk_map *btrfs_alloc_chunk_map(int num_stripes, gfp_t gfp) + return map; + } + ++static void set_real_chunk_type(struct btrfs_chunk_map *map) ++{ ++ map->type = map->on_disk_type; ++ if (likely((map->on_disk_type & BTRFS_BLOCK_GROUP_RAID56_MASK) == 0 || ++ nr_data_stripes(map) > 1)) ++ return; ++ if (map->on_disk_type & BTRFS_BLOCK_GROUP_RAID5) ++ map->type |= BTRFS_BLOCK_GROUP_RAID1; ++ else ++ map->type |= BTRFS_BLOCK_GROUP_RAID1C3; ++ map->type &= ~BTRFS_BLOCK_GROUP_RAID56_MASK; ++} ++ + static struct btrfs_block_group *create_chunk(struct btrfs_trans_handle *trans, + struct alloc_chunk_ctl *ctl, + struct btrfs_device_info *devices_info) +@@ -6067,11 +6045,10 @@ static struct btrfs_block_group *create_chunk(struct btrfs_trans_handle *trans, + map->start = start; + map->chunk_len = ctl->chunk_size; + map->stripe_size = ctl->stripe_size; +- map->type = type; +- map->io_align = BTRFS_STRIPE_LEN; +- map->io_width = BTRFS_STRIPE_LEN; ++ map->on_disk_type = type; + map->sub_stripes = ctl->sub_stripes; + map->num_stripes = ctl->num_stripes; ++ set_real_chunk_type(map); + + for (int i = 0; i < ctl->ndevs; i++) { + for (int j = 0; j < ctl->dev_stripes; j++) { +@@ -6250,7 +6227,7 @@ int btrfs_chunk_alloc_add_chunk_item(struct btrfs_trans_handle *trans, + btrfs_set_stack_chunk_length(chunk, bg->length); + btrfs_set_stack_chunk_owner(chunk, BTRFS_EXTENT_TREE_OBJECTID); + btrfs_set_stack_chunk_stripe_len(chunk, BTRFS_STRIPE_LEN); +- btrfs_set_stack_chunk_type(chunk, map->type); ++ btrfs_set_stack_chunk_type(chunk, map->on_disk_type); + btrfs_set_stack_chunk_num_stripes(chunk, map->num_stripes); + btrfs_set_stack_chunk_io_align(chunk, BTRFS_STRIPE_LEN); + btrfs_set_stack_chunk_io_width(chunk, BTRFS_STRIPE_LEN); +@@ -7632,9 +7609,7 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf, + map->start = logical; + map->chunk_len = length; + map->num_stripes = num_stripes; +- map->io_width = btrfs_chunk_io_width(leaf, chunk); +- map->io_align = btrfs_chunk_io_align(leaf, chunk); +- map->type = type; ++ map->on_disk_type = type; + /* + * We can't use the sub_stripes value, as for profiles other than + * RAID10, they may have 0 as sub_stripes for filesystems created by +@@ -7645,6 +7620,7 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf, + */ + map->sub_stripes = btrfs_raid_array[index].sub_stripes; + map->verified_stripes = 0; ++ set_real_chunk_type(map); + + if (num_stripes > 0) + map->stripe_size = btrfs_calc_stripe_length(map); +diff --git a/fs/btrfs/volumes.h b/fs/btrfs/volumes.h +--- a/fs/btrfs/volumes.h ++++ b/fs/btrfs/volumes.h +@@ -632,9 +632,15 @@ struct btrfs_chunk_map { + u64 start; + u64 chunk_len; + u64 stripe_size; ++ /* ++ * The real type that is utilized during logical address mapping. ++ * ++ * For most profiles it matches @on_disk_type, but for single-data-RAID56, ++ * the real type will be set to RAID1/RAID1C3, to avoid unsupported ++ * operations from raid56 lib. ++ */ + u64 type; +- int io_align; +- int io_width; ++ u64 on_disk_type; + int num_stripes; + int sub_stripes; + struct btrfs_io_stripe stripes[]; +diff --git a/include/trace/events/btrfs.h b/include/trace/events/btrfs.h +--- a/include/trace/events/btrfs.h ++++ b/include/trace/events/btrfs.h +@@ -1613,9 +1613,9 @@ TRACE_EVENT(btrfs_sync_log_enter, + __entry->log_transid_committed = + data_race(root->log_transid_committed); + __entry->log_committing = +- atomic_read(&root->log_commit[ctx->log_transid % 2]); ++ data_race(root->log_commit[ctx->log_transid % 2]); + __entry->log_committing_prev = +- atomic_read(&root->log_commit[(ctx->log_transid + 1) % 2]); ++ data_race(root->log_commit[(ctx->log_transid + 1) % 2]); + __entry->log_writers = atomic_read(&root->log_writers); + ), + diff --git a/pkgbuilds/linux-omarchy/0301-btrfs-fixes.patch b/pkgbuilds/linux-omarchy/0301-btrfs-fixes.patch new file mode 100644 index 0000000..c5a4318 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0301-btrfs-fixes.patch @@ -0,0 +1,386 @@ +diff --git a/fs/btrfs/dev-replace.c b/fs/btrfs/dev-replace.c +--- a/fs/btrfs/dev-replace.c ++++ b/fs/btrfs/dev-replace.c +@@ -626,7 +626,7 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info, + + ret = mark_block_group_to_copy(fs_info, src_device); + if (ret) +- return ret; ++ goto leave; + + down_write(&dev_replace->rwsem); + dev_replace->replace_task = current; +diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -3436,6 +3436,9 @@ int btrfs_finish_one_ordered(struct btrfs_ordered_extent *ordered_extent) + */ + btrfs_remove_ordered_extent(ordered_extent); + ++ /* Cleanup any remaining biocs attached to the OE. */ ++ btrfs_cleanup_ordered_bioc_list(ordered_extent); ++ + /* once for us */ + btrfs_put_ordered_extent(ordered_extent); + /* once for the tree */ +diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c +--- a/fs/btrfs/ioctl.c ++++ b/fs/btrfs/ioctl.c +@@ -384,6 +384,7 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + inode_flags &= ~BTRFS_INODE_COMPRESS; + inode_flags |= BTRFS_INODE_NOCOMPRESS; + } else if (fsflags & FS_COMPR_FL) { ++ enum btrfs_compression_type comp_type; + + if (IS_SWAPFILE(&inode->vfs_inode)) + return -ETXTBSY; +@@ -391,9 +392,23 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + inode_flags |= BTRFS_INODE_COMPRESS; + inode_flags &= ~BTRFS_INODE_NOCOMPRESS; + +- comp = btrfs_compress_type2str(fs_info->compress_type); +- if (!comp || comp[0] == 0) +- comp = btrfs_compress_type2str(BTRFS_COMPRESS_ZLIB); ++ /* ++ * Keep the algorithm recorded in the compression property, ++ * otherwise changing an unrelated attribute would reset it to ++ * the mount default, since FS_IOC_SETFLAGS callers write back ++ * the whole flag set they got from FS_IOC_GETFLAGS and that ++ * includes FS_COMPR_FL for any inode carrying the property. ++ * ++ * Inodes with the compress flag set but no property keep using ++ * the mount default, so they behave as before. ++ */ ++ if (inode->prop_compress) ++ comp_type = inode->prop_compress; ++ else if (fs_info->compress_type) ++ comp_type = fs_info->compress_type; ++ else ++ comp_type = BTRFS_COMPRESS_ZLIB; ++ comp = btrfs_compress_type2str(comp_type); + } else { + inode_flags &= ~(BTRFS_INODE_COMPRESS | BTRFS_INODE_NOCOMPRESS); + } +diff --git a/fs/btrfs/raid-stripe-tree.c b/fs/btrfs/raid-stripe-tree.c +--- a/fs/btrfs/raid-stripe-tree.c ++++ b/fs/btrfs/raid-stripe-tree.c +@@ -310,8 +310,10 @@ static int update_raid_extent_item(struct btrfs_trans_handle *trans, + + ret = btrfs_search_slot(trans, trans->fs_info->stripe_root, key, path, + 0, 1); +- if (ret) +- return (ret == 1 ? ret : -EINVAL); ++ if (ret > 0) ++ ret = -ENOENT; ++ if (ret < 0) ++ return ret; + + leaf = path->nodes[0]; + slot = path->slots[0]; +@@ -337,7 +339,6 @@ int btrfs_insert_one_raid_extent(struct btrfs_trans_handle *trans, + stripe_extent = kzalloc(item_size, GFP_NOFS); + if (unlikely(!stripe_extent)) { + btrfs_abort_transaction(trans, -ENOMEM); +- btrfs_end_transaction(trans); + return -ENOMEM; + } + +@@ -374,7 +375,7 @@ int btrfs_insert_raid_extent(struct btrfs_trans_handle *trans, + struct btrfs_ordered_extent *ordered_extent) + { + struct btrfs_io_context *bioc; +- int ret; ++ int ret = 0; + + if (!btrfs_fs_incompat(trans->fs_info, RAID_STRIPE_TREE)) + return 0; +@@ -382,17 +383,23 @@ int btrfs_insert_raid_extent(struct btrfs_trans_handle *trans, + list_for_each_entry(bioc, &ordered_extent->bioc_list, rst_ordered_entry) { + ret = btrfs_insert_one_raid_extent(trans, bioc); + if (ret) +- return ret; ++ break; + } + +- while (!list_empty(&ordered_extent->bioc_list)) { +- bioc = list_first_entry(&ordered_extent->bioc_list, ++ btrfs_cleanup_ordered_bioc_list(ordered_extent); ++ return ret; ++} ++ ++void btrfs_cleanup_ordered_bioc_list(struct btrfs_ordered_extent *ordered) ++{ ++ while (!list_empty(&ordered->bioc_list)) { ++ struct btrfs_io_context *bioc; ++ ++ bioc = list_first_entry(&ordered->bioc_list, + typeof(*bioc), rst_ordered_entry); + list_del(&bioc->rst_ordered_entry); + btrfs_put_bioc(bioc); + } +- +- return 0; + } + + int btrfs_get_raid_extent_offset(struct btrfs_fs_info *fs_info, +diff --git a/fs/btrfs/raid-stripe-tree.h b/fs/btrfs/raid-stripe-tree.h +--- a/fs/btrfs/raid-stripe-tree.h ++++ b/fs/btrfs/raid-stripe-tree.h +@@ -28,6 +28,7 @@ int btrfs_get_raid_extent_offset(struct btrfs_fs_info *fs_info, + u32 stripe_index, struct btrfs_io_stripe *stripe); + int btrfs_insert_raid_extent(struct btrfs_trans_handle *trans, + struct btrfs_ordered_extent *ordered_extent); ++void btrfs_cleanup_ordered_bioc_list(struct btrfs_ordered_extent *ordered); + + #ifdef CONFIG_BTRFS_FS_RUN_SANITY_TESTS + int btrfs_insert_one_raid_extent(struct btrfs_trans_handle *trans, +diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c +--- a/fs/btrfs/scrub.c ++++ b/fs/btrfs/scrub.c +@@ -1023,6 +1023,10 @@ static void scrub_stripe_report_errors(struct scrub_ctx *sctx, + + skip: + for_each_set_bit(sector_nr, &extent_bitmap, stripe->nr_sectors) { ++ const u64 sector_logical = stripe->logical + ++ ((u64)sector_nr << fs_info->sectorsize_bits); ++ const u64 sector_physical = physical + ++ ((u64)sector_nr << fs_info->sectorsize_bits); + bool repaired = false; + + if (scrub_bitmap_test_bit_is_metadata(stripe, sector_nr)) { +@@ -1051,12 +1055,12 @@ static void scrub_stripe_report_errors(struct scrub_ctx *sctx, + if (dev) { + btrfs_err_rl(fs_info, + "scrub: fixed up error at logical %llu on dev %s physical %llu", +- stripe->logical, btrfs_dev_name(dev), +- physical); ++ sector_logical, btrfs_dev_name(dev), ++ sector_physical); + } else { + btrfs_err_rl(fs_info, + "scrub: fixed up error at logical %llu on mirror %u", +- stripe->logical, stripe->mirror_num); ++ sector_logical, stripe->mirror_num); + } + continue; + } +@@ -1065,30 +1069,30 @@ static void scrub_stripe_report_errors(struct scrub_ctx *sctx, + if (dev) { + btrfs_err_rl(fs_info, + "scrub: unable to fixup (regular) error at logical %llu on dev %s physical %llu", +- stripe->logical, btrfs_dev_name(dev), +- physical); ++ sector_logical, btrfs_dev_name(dev), ++ sector_physical); + } else { + btrfs_err_rl(fs_info, + "scrub: unable to fixup (regular) error at logical %llu on mirror %u", +- stripe->logical, stripe->mirror_num); ++ sector_logical, stripe->mirror_num); + } + + if (scrub_bitmap_test_bit_io_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("i/o error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + if (scrub_bitmap_test_bit_csum_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("checksum error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + if (scrub_bitmap_test_bit_meta_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("header error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + if (scrub_bitmap_test_bit_meta_gen_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("generation error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + } + + /* Update the device stats. */ +diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c +--- a/fs/btrfs/send.c ++++ b/fs/btrfs/send.c +@@ -2065,7 +2065,7 @@ static int will_overwrite_ref(struct send_ctx *sctx, u64 dir, u64 dir_gen, + + ret = is_inode_existent(sctx, dir, dir_gen, NULL, &parent_root_dir_gen); + if (ret <= 0) +- return 0; ++ return ret; + + /* + * If we have a parent root we need to verify that the parent dir was +@@ -6417,6 +6417,13 @@ static int process_extent(struct send_ctx *sctx, + + if (S_ISLNK(sctx->cur_inode_mode)) + return 0; ++ if (unlikely(!S_ISREG(sctx->cur_inode_mode))) { ++ btrfs_crit(sctx->send_root->fs_info, ++ "send: extent for non-regular inode %llu root %llu mode 0%llo", ++ key->objectid, btrfs_root_id(sctx->send_root), ++ sctx->cur_inode_mode & S_IFMT); ++ return -EUCLEAN; ++ } + + if (sctx->parent_root && !sctx->cur_inode_new) { + ret = is_extent_unchanged(sctx, path, key); +diff --git a/fs/btrfs/tests/extent-io-tests.c b/fs/btrfs/tests/extent-io-tests.c +--- a/fs/btrfs/tests/extent-io-tests.c ++++ b/fs/btrfs/tests/extent-io-tests.c +@@ -133,14 +133,14 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) + if (IS_ERR(root)) { + test_std_err(TEST_ALLOC_ROOT); + ret = PTR_ERR(root); +- goto out; ++ goto out_root_info; + } + + inode = btrfs_new_test_inode(); + if (!inode) { + test_std_err(TEST_ALLOC_INODE); + ret = -ENOMEM; +- goto out; ++ goto out_root_info; + } + tmp = &BTRFS_I(inode)->io_tree; + BTRFS_I(inode)->root = root; +@@ -333,6 +333,7 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) + process_page_range(inode, 0, total_dirty - 1, + PROCESS_UNLOCK | PROCESS_RELEASE); + iput(inode); ++out_root_info: + btrfs_free_dummy_root(root); + btrfs_free_dummy_fs_info(fs_info); + return ret; +diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c +--- a/fs/btrfs/transaction.c ++++ b/fs/btrfs/transaction.c +@@ -458,8 +458,19 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans, + * through btrfs_record_root_in_trans without having to take the + * lock. smp_wmb() makes sure that all the writes above are + * done before we pop in the zero below ++ * ++ * If @force is true, it means the call is from ++ * qgroup_account_snapshot(), which only requires radix tree ++ * tracking. ++ * We should not force reloc root creation here, as the root ++ * may have already been modified, and in that case ++ * root->commit_root has already been dropped. ++ * ++ * Using that commit root will cause the reloc root to refer ++ * to a deleted extent, causing extent tree corruption. + */ +- ret = btrfs_init_reloc_root(trans, root); ++ if (!force) ++ ret = btrfs_init_reloc_root(trans, root); + smp_mb__before_atomic(); + clear_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state); + } +@@ -2583,6 +2594,12 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans) + ret = btrfs_write_and_wait_transaction(trans); + if (unlikely(ret)) { + btrfs_err(fs_info, "error while writing out transaction: %pe", ERR_PTR(ret)); ++ /* ++ * Abort before releasing tree_log_mutex, so a log sync waiting ++ * on it sees the fs error and skips writing super_for_commit ++ * for this failed transaction. See btrfs_sync_log(). ++ */ ++ btrfs_abort_transaction(trans, ret); + mutex_unlock(&fs_info->tree_log_mutex); + goto scrub_continue; + } +diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c +--- a/fs/btrfs/volumes.c ++++ b/fs/btrfs/volumes.c +@@ -3035,7 +3035,11 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path + error_sysfs: + btrfs_sysfs_remove_device(device); + mutex_lock(&fs_info->fs_devices->device_list_mutex); ++ if (seeding_dev) ++ btrfs_assign_next_active_device(device, seed_devices->latest_dev); + mutex_lock(&fs_info->chunk_mutex); ++ if (!list_empty(&device->post_commit_list)) ++ list_del_init(&device->post_commit_list); + list_del_rcu(&device->dev_list); + list_del(&device->dev_alloc_list); + fs_info->fs_devices->num_devices--; +diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c +--- a/fs/btrfs/zoned.c ++++ b/fs/btrfs/zoned.c +@@ -2626,16 +2626,13 @@ static int do_zone_finish(struct btrfs_block_group *block_group, bool fully_writ + down_read(&dev_replace->rwsem); + map = block_group->physical_map; + for (i = 0; i < map->num_stripes; i++) { +- + ret = call_zone_finish(block_group, &map->stripes[i]); +- if (ret) { +- up_read(&dev_replace->rwsem); +- return ret; +- } ++ if (ret) ++ break; + } + up_read(&dev_replace->rwsem); + +- if (!fully_written) ++ if (!ret && !fully_written) + btrfs_dec_block_group_ro(block_group); + + spin_lock(&fs_info->zone_active_bgs_lock); +@@ -2648,7 +2645,7 @@ static int do_zone_finish(struct btrfs_block_group *block_group, bool fully_writ + + clear_and_wake_up_bit(BTRFS_FS_NEED_ZONE_FINISH, &fs_info->flags); + +- return 0; ++ return ret; + } + + int btrfs_zone_finish(struct btrfs_block_group *block_group) +@@ -2713,6 +2710,7 @@ int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 leng + { + struct btrfs_block_group *block_group; + u64 min_alloc_bytes; ++ int ret = 0; + + if (!btrfs_is_zoned(fs_info)) + return 0; +@@ -2732,11 +2730,11 @@ int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 leng + block_group->start + block_group->zone_capacity) + goto out; + +- do_zone_finish(block_group, true); ++ ret = do_zone_finish(block_group, true); + + out: + btrfs_put_block_group(block_group); +- return 0; ++ return ret; + } + + static void btrfs_zone_finish_endio_workfn(struct work_struct *work) +diff --git a/fs/btrfs/zstd.c b/fs/btrfs/zstd.c +--- a/fs/btrfs/zstd.c ++++ b/fs/btrfs/zstd.c +@@ -307,8 +307,17 @@ struct list_head *zstd_get_workspace(struct btrfs_fs_info *fs_info, int level) + DEFINE_WAIT(wait); + + prepare_to_wait(&zwsm->wait, &wait, TASK_UNINTERRUPTIBLE); +- schedule(); ++ /* ++ * Re-check after being queued: zstd_put_workspace() only wakes ++ * a queue that already has a sleeper, so a workspace returned ++ * since the failed allocation woke nobody. ++ */ ++ ws = zstd_find_workspace(fs_info, level); ++ if (!ws) ++ schedule(); + finish_wait(&zwsm->wait, &wait); ++ if (ws) ++ return ws; + + goto again; + } diff --git a/pkgbuilds/linux-omarchy/0302-btrfs-zstd-decompress-direct-to-page.patch b/pkgbuilds/linux-omarchy/0302-btrfs-zstd-decompress-direct-to-page.patch new file mode 100644 index 0000000..d949f95 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0302-btrfs-zstd-decompress-direct-to-page.patch @@ -0,0 +1,109 @@ +diff --git c/fs/btrfs/zstd.c i/fs/btrfs/zstd.c +--- c/fs/btrfs/zstd.c ++++ i/fs/btrfs/zstd.c +@@ -589,10 +589,48 @@ int zstd_compress_bio(struct list_head *ws, struct compressed_bio *cb) + return ret; + } + ++/* ++ * Map the destination for the next chunk of output. ++ * ++ * @decompressed is the offset of the next output byte inside the fully ++ * decompressed extent. If that offset has reached the current destination ++ * segment, its page-bounded bio_vec is kmapped so that zstd can write into the ++ * page cache directly, and the number of bytes writable there is returned. ++ * Otherwise @kaddr_ret is set to NULL and the number of bytes to skip before ++ * that segment is returned. This covers both the initial prefix and gaps in ++ * the destination bio. ++ */ ++static u32 zstd_map_dest(struct compressed_bio *cb, u32 decompressed, ++ void **kaddr_ret) ++{ ++ struct bio *orig_bio = &cb->orig_bbio->bio; ++ struct bio_vec bvec; ++ u32 bvec_offset; ++ u32 off; ++ ++ bvec = bio_iter_iovec(orig_bio, orig_bio->bi_iter); ++ /* ++ * cb->start may underflow, but subtracting that value can still give us ++ * the correct offset inside the full decompressed extent. ++ */ ++ bvec_offset = page_offset(bvec.bv_page) + bvec.bv_offset - cb->start; ++ ++ if (decompressed < bvec_offset) { ++ *kaddr_ret = NULL; ++ return bvec_offset - decompressed; ++ } ++ ++ off = decompressed - bvec_offset; ++ ASSERT(off < bvec.bv_len); ++ *kaddr_ret = bvec_kmap_local(&bvec) + off; ++ return bvec.bv_len - off; ++} ++ + int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + { + struct btrfs_fs_info *fs_info = cb_to_fs_info(cb); + struct workspace *workspace = list_entry(ws, struct workspace, list); ++ struct bio *orig_bio = &cb->orig_bbio->bio; + struct folio_iter fi; + size_t srclen = bio_get_size(&cb->bbio.bio); + zstd_dstream *stream; +@@ -600,7 +638,6 @@ int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + const unsigned int min_folio_size = btrfs_min_folio_size(fs_info); + unsigned long folio_in_index = 0; + unsigned long total_folios_in = DIV_ROUND_UP(srclen, min_folio_size); +- unsigned long buf_start; + unsigned long total_out = 0; + + bio_first_folio(&fi, &cb->bbio.bio, 0); +@@ -624,15 +661,26 @@ int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + workspace->in_buf.pos = 0; + workspace->in_buf.size = min_t(size_t, srclen, min_folio_size); + +- workspace->out_buf.dst = workspace->buf; +- workspace->out_buf.pos = 0; +- workspace->out_buf.size = fs_info->sectorsize; +- +- while (1) { ++ while (orig_bio->bi_iter.bi_size) { + size_t ret2; ++ void *kaddr; ++ u32 dstlen; ++ ++ dstlen = zstd_map_dest(cb, total_out, &kaddr); ++ if (kaddr) { ++ workspace->out_buf.dst = kaddr; ++ workspace->out_buf.size = dstlen; ++ } else { ++ workspace->out_buf.dst = workspace->buf; ++ workspace->out_buf.size = min_t(u32, dstlen, ++ fs_info->sectorsize); ++ } ++ workspace->out_buf.pos = 0; + + ret2 = zstd_decompress_stream(stream, &workspace->out_buf, + &workspace->in_buf); ++ if (kaddr) ++ kunmap_local(kaddr); + if (unlikely(zstd_is_error(ret2))) { + struct btrfs_inode *inode = cb->bbio.inode; + +@@ -643,14 +691,9 @@ int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + ret = -EIO; + goto done; + } +- buf_start = total_out; + total_out += workspace->out_buf.pos; +- workspace->out_buf.pos = 0; +- +- ret = btrfs_decompress_buf2page(workspace->out_buf.dst, +- total_out - buf_start, cb, buf_start); +- if (ret == 0) +- break; ++ if (kaddr) ++ bio_advance(orig_bio, workspace->out_buf.pos); + + if (workspace->in_buf.pos >= srclen) + break; diff --git a/pkgbuilds/linux-omarchy/0310-fuse-eof-zeroing.patch b/pkgbuilds/linux-omarchy/0310-fuse-eof-zeroing.patch new file mode 100644 index 0000000..b34a909 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0310-fuse-eof-zeroing.patch @@ -0,0 +1,42 @@ +diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c +--- a/fs/fuse/dir.c ++++ b/fs/fuse/dir.c +@@ -2280,6 +2280,9 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + */ + if ((is_truncate || !is_wb) && + S_ISREG(inode->i_mode) && oldsize != outarg.attr.size) { ++ if (outarg.attr.size > oldsize) ++ truncate_pagecache_range(inode, oldsize, ++ outarg.attr.size - 1); + truncate_pagecache(inode, outarg.attr.size); + invalidate_inode_pages2(mapping); + } +diff --git a/fs/fuse/file.c b/fs/fuse/file.c +--- a/fs/fuse/file.c ++++ b/fs/fuse/file.c +@@ -1360,9 +1360,13 @@ static ssize_t fuse_perform_write(struct kiocb *iocb, struct iov_iter *ii) + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_inode *fi = get_fuse_inode(inode); + loff_t pos = iocb->ki_pos; ++ loff_t old_size = i_size_read(inode); + int err = 0; + ssize_t res = 0; + ++ if (pos > old_size) ++ truncate_pagecache_range(inode, old_size, pos - 1); ++ + if (inode->i_size < pos + iov_iter_count(ii)) + set_bit(FUSE_I_SIZE_UNSTABLE, &fi->state); + +@@ -2908,6 +2912,11 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset, + + /* we could have extended the file */ + if (!(mode & FALLOC_FL_KEEP_SIZE)) { ++ loff_t oldsize = i_size_read(inode); ++ ++ if (offset + length > oldsize) ++ truncate_pagecache_range(inode, oldsize, ++ offset + length - 1); + if (fuse_write_update_attr(inode, offset + length, length)) + file_update_time(file); + } diff --git a/pkgbuilds/linux-omarchy/0311-fuse-perf.patch b/pkgbuilds/linux-omarchy/0311-fuse-perf.patch new file mode 100644 index 0000000..5162077 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0311-fuse-perf.patch @@ -0,0 +1,104 @@ +diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c +--- a/fs/fuse/dev.c ++++ b/fs/fuse/dev.c +@@ -216,10 +216,13 @@ EXPORT_SYMBOL_GPL(fuse_req_hash); + /* + * A new request is available, wake fiq->waitq + */ +-static void fuse_dev_wake_and_unlock(struct fuse_iqueue *fiq) ++static void fuse_dev_wake_and_unlock(struct fuse_iqueue *fiq, bool sync) + __releases(fiq->lock) + { +- wake_up(&fiq->waitq); ++ if (sync) ++ wake_up_sync(&fiq->waitq); ++ else ++ wake_up(&fiq->waitq); + kill_fasync(&fiq->fasync, SIGIO, POLL_IN); + spin_unlock(&fiq->lock); + } +@@ -236,7 +239,7 @@ void fuse_dev_queue_forget(struct fuse_iqueue *fiq, + if (fiq->connected) { + fiq->forget_list_tail->next = forget; + fiq->forget_list_tail = forget; +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, false); + } else { + kfree(forget); + spin_unlock(&fiq->lock); +@@ -258,7 +261,7 @@ void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req) + list_del_init(&req->intr_entry); + spin_unlock(&fiq->lock); + } else { +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, false); + } + } else { + spin_unlock(&fiq->lock); +@@ -288,11 +291,13 @@ EXPORT_SYMBOL_GPL(fuse_request_assign_unique); + + static void fuse_dev_queue_req(struct fuse_iqueue *fiq, struct fuse_req *req) + { ++ bool sync = test_and_clear_bit(FR_SYNC_WAKEUP, &req->flags); ++ + spin_lock(&fiq->lock); + if (fiq->connected) { + fuse_request_assign_unique_locked(fiq, req); + list_add_tail(&req->list, &fiq->pending); +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, sync); + } else { + spin_unlock(&fiq->lock); + req->out.h.error = -ENOTCONN; +@@ -755,6 +760,11 @@ static void __fuse_request_send(struct fuse_req *req) + /* acquire extra reference, since request is still needed after + fuse_request_end() */ + __fuse_get_request(req); ++ /* ++ * This is a synchronous request: the caller will block waiting for ++ * the answer. Hint the scheduler via wake_up_sync(). ++ */ ++ set_bit(FR_SYNC_WAKEUP, &req->flags); + fuse_send_one(fiq, req); + + request_wait_answer(req); +@@ -1809,7 +1819,7 @@ void fuse_chan_resend(struct fuse_chan *fch) + } + /* iq and pq requests are both oldest to newest */ + list_splice(&to_queue, &fiq->pending); +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, false); + } + + /* Look up request on processing list by unique ID */ +diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h +--- a/fs/fuse/fuse_dev_i.h ++++ b/fs/fuse/fuse_dev_i.h +@@ -38,6 +38,8 @@ struct fuse_iqueue; + * @FR_PRIVATE: request is on private list + * @FR_ASYNC: request is asynchronous + * @FR_URING: request is handled through fuse-io-uring ++ * @FR_SYNC_WAKEUP: use synchronous wakeup when queueing this request to ++ * give the scheduler a hint about the waker task + */ + enum fuse_req_flag { + FR_ISREPLY, +@@ -53,6 +55,7 @@ enum fuse_req_flag { + FR_PRIVATE, + FR_ASYNC, + FR_URING, ++ FR_SYNC_WAKEUP, + }; + + /** +diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c +--- a/fs/fuse/inode.c ++++ b/fs/fuse/inode.c +@@ -1417,6 +1417,7 @@ static void process_init_reply(struct fuse_args *args, int error) + + fm->sb->s_bdi->ra_pages = + min(fm->sb->s_bdi->ra_pages, ra_pages); ++ fm->sb->s_bdi->io_pages = fc->max_pages; + fc->minor = arg->minor; + fc->max_write = arg->minor < 5 ? 4096 : arg->max_write; + fc->max_write = max_t(unsigned, 4096, fc->max_write); diff --git a/pkgbuilds/linux-omarchy/0312-fuse-writethrough-uptodate.patch b/pkgbuilds/linux-omarchy/0312-fuse-writethrough-uptodate.patch new file mode 100644 index 0000000..8aaf4c6 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0312-fuse-writethrough-uptodate.patch @@ -0,0 +1,38 @@ +diff --git a/fs/fuse/file.c b/fs/fuse/file.c +--- a/fs/fuse/file.c ++++ b/fs/fuse/file.c +@@ -1219,8 +1219,7 @@ static ssize_t fuse_send_write_pages(struct fuse_io_args *ia, + struct file *file = iocb->ki_filp; + struct fuse_file *ff = file->private_data; + struct fuse_mount *fm = ff->fm; +- unsigned int offset, i; +- bool short_write; ++ unsigned int i; + int err; + + for (i = 0; i < ap->num_folios; i++) +@@ -1235,24 +1234,9 @@ static ssize_t fuse_send_write_pages(struct fuse_io_args *ia, + if (!err && ia->write.out.size > count) + err = -EIO; + +- short_write = ia->write.out.size < count; +- offset = ap->descs[0].offset; +- count = ia->write.out.size; + for (i = 0; i < ap->num_folios; i++) { + struct folio *folio = ap->folios[i]; + +- if (err) { +- folio_clear_uptodate(folio); +- } else { +- if (count >= folio_size(folio) - offset) +- count -= folio_size(folio) - offset; +- else { +- if (short_write) +- folio_clear_uptodate(folio); +- count = 0; +- } +- offset = 0; +- } + if (ia->write.folio_locked && (i == ap->num_folios - 1)) + folio_unlock(folio); + folio_put(folio); diff --git a/pkgbuilds/linux-omarchy/0313-fuse-background-wakeup.patch b/pkgbuilds/linux-omarchy/0313-fuse-background-wakeup.patch new file mode 100644 index 0000000..77e750a --- /dev/null +++ b/pkgbuilds/linux-omarchy/0313-fuse-background-wakeup.patch @@ -0,0 +1,13 @@ +diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c +--- a/fs/fuse/dev.c ++++ b/fs/fuse/dev.c +@@ -409,7 +409,8 @@ void fuse_chan_max_background_set(struct fuse_chan *fch, unsigned int val) + fch->max_background = val; + fch->blocked = fch->num_background >= fch->max_background; + if (!fch->blocked) +- wake_up(&fch->blocked_waitq); ++ wake_up_nr(&fch->blocked_waitq, ++ fch->max_background - fch->num_background); + spin_unlock(&fch->bg_lock); + } + diff --git a/pkgbuilds/linux-omarchy/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch b/pkgbuilds/linux-omarchy/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch new file mode 100644 index 0000000..9a8f97f --- /dev/null +++ b/pkgbuilds/linux-omarchy/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch @@ -0,0 +1,123 @@ +diff --git a/drivers/gpu/drm/drm_displayid_internal.h b/drivers/gpu/drm/drm_displayid_internal.h +index 4590d6a3d821..dbdc2053332d 100644 +--- a/drivers/gpu/drm/drm_displayid_internal.h ++++ b/drivers/gpu/drm/drm_displayid_internal.h +@@ -67,6 +67,7 @@ struct drm_edid; + #define DATA_BLOCK_2_TILED_DISPLAY_TOPOLOGY 0x28 + #define DATA_BLOCK_2_CONTAINER_ID 0x29 + #define DATA_BLOCK_2_TYPE_10_FORMULA_TIMING 0x2a ++#define DATA_BLOCK_2_ADAPTIVE_SYNC 0x2b + #define DATA_BLOCK_2_VENDOR_SPECIFIC 0x7e + #define DATA_BLOCK_2_CTA_DISPLAY_ID 0x81 + +diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c +index df3c25bac761..354d4ff1aff7 100644 +--- a/drivers/gpu/drm/drm_edid.c ++++ b/drivers/gpu/drm/drm_edid.c +@@ -6598,6 +6598,49 @@ static void drm_get_monitor_range(struct drm_connector *connector, + info->monitor_range.min_vfreq, info->monitor_range.max_vfreq); + } + ++#define DISPLAYID_ADAPTIVE_SYNC_DESC_SIZE 6 ++ ++static bool drm_update_displayid_adaptive_sync_range(struct drm_connector *connector, ++ const struct displayid_block *block) ++{ ++ struct drm_monitor_range_info *range = &connector->display_info.monitor_range; ++ const u8 *data = (const u8 *)(block + 1); ++ u16 best_min = 0, best_max = 0; ++ unsigned int best_span = 0; ++ int i; ++ ++ if (block->rev != 0 || block->num_bytes < DISPLAYID_ADAPTIVE_SYNC_DESC_SIZE || ++ block->num_bytes % DISPLAYID_ADAPTIVE_SYNC_DESC_SIZE) ++ return false; ++ ++ for (i = 0; i < block->num_bytes; i += DISPLAYID_ADAPTIVE_SYNC_DESC_SIZE) { ++ const u8 *desc = &data[i]; ++ u16 min_vfreq = desc[2]; ++ u16 max_vfreq = ((((u16)desc[4]) & 0x3) << 8) | desc[3]; ++ unsigned int span; ++ ++ max_vfreq += 1; ++ if (!min_vfreq || max_vfreq <= min_vfreq) ++ continue; ++ ++ span = max_vfreq - min_vfreq; ++ if (span <= best_span) ++ continue; ++ ++ best_min = min_vfreq; ++ best_max = max_vfreq; ++ best_span = span; ++ } ++ ++ if (best_span) { ++ range->min_vfreq = best_min; ++ range->max_vfreq = best_max; ++ return true; ++ } ++ ++ return false; ++} ++ + static void drm_parse_vesa_mso_data(struct drm_connector *connector, + const struct displayid_block *block) + { +@@ -6720,26 +6763,43 @@ static void update_displayid_info(struct drm_connector *connector, + { + struct drm_display_info *info = &connector->display_info; + const struct displayid_block *block; ++ bool adaptive_sync_range = false; ++ bool displayid_base_logged = false; + struct displayid_iter iter; + + displayid_iter_edid_begin(drm_edid, &iter); + displayid_iter_for_each(block, &iter) { +- drm_dbg_kms(connector->dev, +- "[CONNECTOR:%d:%s] DisplayID extension version 0x%02x, primary use 0x%02x\n", +- connector->base.id, connector->name, +- displayid_version(&iter), +- displayid_primary_use(&iter)); +- if (displayid_version(&iter) == DISPLAY_ID_STRUCTURE_VER_20 && +- (displayid_primary_use(&iter) == PRIMARY_USE_HEAD_MOUNTED_VR || +- displayid_primary_use(&iter) == PRIMARY_USE_HEAD_MOUNTED_AR)) +- info->non_desktop = true; +- + /* +- * We're only interested in the base section here, no need to +- * iterate further. ++ * Primary use is a DisplayID base section property, but later ++ * blocks may still carry useful metadata like adaptive sync ranges. + */ +- break; ++ if (!displayid_base_logged) { ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] DisplayID extension version 0x%02x, primary use 0x%02x\n", ++ connector->base.id, connector->name, ++ displayid_version(&iter), ++ displayid_primary_use(&iter)); ++ if (displayid_version(&iter) == DISPLAY_ID_STRUCTURE_VER_20 && ++ (displayid_primary_use(&iter) == PRIMARY_USE_HEAD_MOUNTED_VR || ++ displayid_primary_use(&iter) == PRIMARY_USE_HEAD_MOUNTED_AR)) ++ info->non_desktop = true; ++ ++ displayid_base_logged = true; ++ } ++ ++ if (!info->monitor_range.min_vfreq && !info->monitor_range.max_vfreq && ++ block->tag == DATA_BLOCK_2_ADAPTIVE_SYNC) ++ adaptive_sync_range = ++ drm_update_displayid_adaptive_sync_range(connector, block); + } ++ ++ if (adaptive_sync_range) ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] DisplayID adaptive sync refresh rate range is %d Hz - %d Hz\n", ++ connector->base.id, connector->name, ++ info->monitor_range.min_vfreq, ++ info->monitor_range.max_vfreq); ++ + displayid_iter_end(&iter); + } + diff --git a/pkgbuilds/linux-omarchy/0360-gpu-mem-cgroup.patch b/pkgbuilds/linux-omarchy/0360-gpu-mem-cgroup.patch new file mode 100644 index 0000000..278fc0c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0360-gpu-mem-cgroup.patch @@ -0,0 +1,601 @@ +diff --git a/drivers/gpu/drm/nouveau/nouveau_ttm.c b/drivers/gpu/drm/nouveau/nouveau_ttm.c +--- a/drivers/gpu/drm/nouveau/nouveau_ttm.c ++++ b/drivers/gpu/drm/nouveau/nouveau_ttm.c +@@ -188,6 +188,11 @@ nouveau_ttm_init_vram(struct nouveau_drm *drm) + + man->func = &nouveau_vram_manager; + ++ man->cg = drmm_cgroup_register_region(drm->dev, "vram", ++ drm->gem.vram_available); ++ if (IS_ERR(man->cg)) ++ return PTR_ERR(man->cg); ++ + ttm_resource_manager_init(man, &drm->ttm.bdev, + drm->gem.vram_available >> PAGE_SHIFT); + ttm_set_driver_manager(&drm->ttm.bdev, TTM_PL_VRAM, man); +diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c +--- a/drivers/gpu/drm/ttm/ttm_bo.c ++++ b/drivers/gpu/drm/ttm/ttm_bo.c +@@ -488,6 +488,118 @@ int ttm_bo_evict_first(struct ttm_device *bdev, struct ttm_resource_manager *man + return ret; + } + ++struct ttm_bo_alloc_state { ++ /** @charge_pool: The memory pool the resource is charged to */ ++ struct dmem_cgroup_pool_state *charge_pool; ++ /** @limit_pool: Which pool limit we should test against */ ++ struct dmem_cgroup_pool_state *limit_pool; ++ /** @in_evict: Whether we are currently evicting buffers */ ++ bool in_evict; ++ /** @may_try_low: If only unprotected BOs, i.e. BOs whose cgroup ++ * is exceeding its dmem low/min protection, should be considered for eviction ++ */ ++ bool may_try_low; ++}; ++ ++/** ++ * ttm_bo_alloc_at_place - Attempt allocating a BO's backing store in a place ++ * ++ * @bo: The buffer to allocate the backing store of ++ * @place: The place to attempt allocation in ++ * @ctx: ttm_operation_ctx associated with this allocation ++ * @force_space: If we should evict buffers to force space ++ * @res: On allocation success, the resulting struct ttm_resource. ++ * @alloc_state: Object holding allocation state such as charged cgroups. ++ * ++ * Returns: ++ * -EBUSY: No space available, but allocation should be retried with ttm_bo_evict_alloc. ++ * -ENOSPC: No space available, allocation should not be retried. ++ * -ERESTARTSYS: An interruptible sleep was interrupted by a signal. ++ * ++ */ ++static int ttm_bo_alloc_at_place(struct ttm_buffer_object *bo, ++ const struct ttm_place *place, ++ bool force_space, ++ struct ttm_resource **res, ++ struct ttm_bo_alloc_state *alloc_state) ++{ ++ bool may_evict; ++ int ret; ++ ++ may_evict = !alloc_state->in_evict && force_space && ++ place->mem_type != TTM_PL_SYSTEM; ++ if (!alloc_state->charge_pool) { ++ ret = ttm_resource_try_charge(bo, place, &alloc_state->charge_pool, ++ force_space ? &alloc_state->limit_pool ++ : NULL); ++ if (ret) { ++ /* ++ * -EAGAIN means the charge failed, which we treat ++ * like an allocation failure. Therefore, return an ++ * error code indicating the allocation failed - ++ * either -EBUSY if the allocation should be ++ * retried with eviction, or -ENOSPC if there should ++ * be no second attempt. ++ */ ++ if (!alloc_state->in_evict) ++ alloc_state->may_try_low = may_evict; ++ if (ret == -EAGAIN) ++ ret = may_evict ? -EBUSY : -ENOSPC; ++ return ret; ++ } ++ } ++ ++ /* ++ * cgroup protection plays a special role in eviction. ++ * Conceptually, protection of memory via the dmem cgroup controller ++ * entitles the protected cgroup to use a certain amount of memory. ++ * There are two types of protection - the 'low' limit is a ++ * "best-effort" protection, whereas the 'min' limit provides a hard ++ * guarantee that memory within the cgroup's allowance will not be ++ * evicted under any circumstance. ++ * ++ * To faithfully model this concept in TTM, we also need to take cgroup ++ * protection into account when allocating. When allocation in one ++ * place fails, TTM will default to trying other places first before ++ * evicting. ++ * If the allocation is covered by dmem cgroup protection, however, ++ * this prevents the allocation from using the memory it is "entitled" ++ * to. To make sure unprotected allocations cannot push new protected ++ * allocations out of places they are "entitled" to use, we should ++ * evict buffers not covered by any cgroup protection, if this ++ * allocation is covered by cgroup protection. ++ * ++ * Buffers covered by 'min' protection are a special case - the 'min' ++ * limit is a stronger guarantee than 'low', and thus buffers protected ++ * by 'low' but not 'min' should also be considered for eviction. ++ * Buffers protected by 'min' will never be considered for eviction ++ * anyway, so the regular eviction path should be triggered here. ++ * Buffers protected by 'low' but not 'min' will take a special ++ * eviction path that only evicts buffers covered by neither 'low' or ++ * 'min' protections. ++ */ ++ if (!alloc_state->in_evict) { ++ may_evict |= dmem_cgroup_below_min(NULL, alloc_state->charge_pool); ++ alloc_state->may_try_low = may_evict; ++ ++ may_evict |= dmem_cgroup_below_low(NULL, alloc_state->charge_pool); ++ } ++ ++ ret = ttm_resource_alloc(bo, place, res, alloc_state->charge_pool); ++ if (ret) { ++ if (ret == -ENOSPC && may_evict) ++ ret = -EBUSY; ++ return ret; ++ } ++ ++ /* ++ * Ownership of charge_pool has been transferred to the TTM resource, ++ * don't make the caller think we still hold a reference to it. ++ */ ++ alloc_state->charge_pool = NULL; ++ return 0; ++} ++ + /** + * struct ttm_bo_evict_walk - Parameters for the evict walk. + */ +@@ -503,22 +615,61 @@ struct ttm_bo_evict_walk { + /** @evicted: Number of successful evictions. */ + unsigned long evicted; + +- /** @limit_pool: Which pool limit we should test against */ +- struct dmem_cgroup_pool_state *limit_pool; + /** @try_low: Whether we should attempt to evict BO's with low watermark threshold */ + bool try_low; + /** @hit_low: If we cannot evict a bo when @try_low is false (first pass) */ + bool hit_low; ++ ++ /** @alloc_state: State associated with the allocation attempt. */ ++ struct ttm_bo_alloc_state *alloc_state; + }; + + static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object *bo) + { + struct ttm_bo_evict_walk *evict_walk = + container_of(walk, typeof(*evict_walk), walk); ++ struct dmem_cgroup_pool_state *limit_pool, *ancestor = NULL; ++ bool evict_valuable; + s64 lret; + +- if (!dmem_cgroup_state_evict_valuable(evict_walk->limit_pool, bo->resource->css, +- evict_walk->try_low, &evict_walk->hit_low)) ++ /* ++ * If may_try_low is not set, then we're trying to evict unprotected ++ * buffers in favor of a protected allocation for charge_pool. Explicitly skip ++ * buffers belonging to the same cgroup here - that cgroup is definitely protected, ++ * even though dmem_cgroup_state_evict_valuable would allow the eviction because a ++ * cgroup is always allowed to evict from itself even if it is protected. ++ */ ++ if (!evict_walk->alloc_state->may_try_low && ++ bo->resource->css == evict_walk->alloc_state->charge_pool) ++ return 0; ++ ++ limit_pool = evict_walk->alloc_state->limit_pool; ++ /* ++ * If there is no explicit limit pool, find the root of the shared subtree between ++ * evictor and evictee. This is important so that recursive protection rules can ++ * apply properly: Recursive protection distributes cgroup protection afforded ++ * to a parent cgroup but not used explicitly by a child cgroup between all child ++ * cgroups (see docs of effective_protection in mm/page_counter.c). However, when ++ * direct siblings compete for memory, siblings that were explicitly protected ++ * should get prioritized over siblings that weren't. This only happens correctly ++ * when the root of the shared subtree is passed to ++ * dmem_cgroup_state_evict_valuable. Otherwise, the effective-protection ++ * calculation cannot distinguish direct siblings from unrelated subtrees and the ++ * calculated protection ends up wrong. ++ */ ++ if (!limit_pool) { ++ ancestor = dmem_cgroup_get_common_ancestor(bo->resource->css, ++ evict_walk->alloc_state->charge_pool); ++ limit_pool = ancestor; ++ } ++ ++ evict_valuable = dmem_cgroup_state_evict_valuable(limit_pool, bo->resource->css, ++ evict_walk->try_low, ++ &evict_walk->hit_low); ++ if (ancestor) ++ dmem_cgroup_pool_state_put(ancestor); ++ ++ if (!evict_valuable) + return 0; + + if (bo->pin_count || !bo->bdev->funcs->eviction_valuable(bo, evict_walk->place)) +@@ -537,8 +688,10 @@ static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object * + + evict_walk->evicted++; + if (evict_walk->res) +- lret = ttm_resource_alloc(evict_walk->evictor, evict_walk->place, +- evict_walk->res, NULL); ++ lret = ttm_bo_alloc_at_place(evict_walk->evictor, ++ evict_walk->place, false, ++ evict_walk->res, ++ evict_walk->alloc_state); + if (lret == 0) + return 1; + out: +@@ -560,7 +713,7 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, + struct ttm_operation_ctx *ctx, + struct ww_acquire_ctx *ticket, + struct ttm_resource **res, +- struct dmem_cgroup_pool_state *limit_pool) ++ struct ttm_bo_alloc_state *state) + { + struct ttm_bo_evict_walk evict_walk = { + .walk = { +@@ -573,15 +726,21 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, + .place = place, + .evictor = evictor, + .res = res, +- .limit_pool = limit_pool, ++ .alloc_state = state, + }; + s64 lret; + ++ state->in_evict = true; ++ + evict_walk.walk.arg.trylock_only = true; + lret = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, 1); + +- /* One more attempt if we hit low limit? */ +- if (!lret && evict_walk.hit_low) { ++ /* If we failed to find enough BOs to evict, but we skipped over ++ * some BOs because they were covered by dmem low protection, retry ++ * evicting these protected BOs too, except if we're told not to ++ * consider protected BOs at all. ++ */ ++ if (!lret && evict_walk.hit_low && state->may_try_low) { + evict_walk.try_low = true; + lret = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, 1); + } +@@ -602,11 +761,13 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, + } while (!lret && evict_walk.evicted); + + /* We hit the low limit? Try once more */ +- if (!lret && evict_walk.hit_low && !evict_walk.try_low) { ++ if (!lret && evict_walk.hit_low && !evict_walk.try_low && ++ state->may_try_low) { + evict_walk.try_low = true; + goto retry; + } + out: ++ state->in_evict = false; + if (lret < 0) + return lret; + if (lret == 0) +@@ -724,9 +885,8 @@ static int ttm_bo_alloc_resource(struct ttm_buffer_object *bo, + + for (i = 0; i < placement->num_placement; ++i) { + const struct ttm_place *place = &placement->placement[i]; +- struct dmem_cgroup_pool_state *limit_pool = NULL; ++ struct ttm_bo_alloc_state alloc_state = {}; + struct ttm_resource_manager *man; +- bool may_evict; + + man = ttm_manager_type(bdev, place->mem_type); + if (!man || !ttm_resource_manager_used(man)) +@@ -736,25 +896,30 @@ static int ttm_bo_alloc_resource(struct ttm_buffer_object *bo, + TTM_PL_FLAG_FALLBACK)) + continue; + +- may_evict = (force_space && place->mem_type != TTM_PL_SYSTEM); +- ret = ttm_resource_alloc(bo, place, res, force_space ? &limit_pool : NULL); +- if (ret) { +- if (ret != -ENOSPC) { +- dmem_cgroup_pool_state_put(limit_pool); +- return ret; +- } +- if (!may_evict) { +- dmem_cgroup_pool_state_put(limit_pool); +- continue; +- } ++ ret = ttm_bo_alloc_at_place(bo, place, force_space, res, ++ &alloc_state); + ++ if (ret == -ENOSPC) { ++ dmem_cgroup_uncharge(alloc_state.charge_pool, bo->base.size); ++ dmem_cgroup_pool_state_put(alloc_state.limit_pool); ++ continue; ++ } else if (ret == -EBUSY) { + ret = ttm_bo_evict_alloc(bdev, man, place, bo, ctx, +- ticket, res, limit_pool); +- dmem_cgroup_pool_state_put(limit_pool); +- if (ret == -EBUSY) +- continue; +- if (ret) ++ ticket, res, &alloc_state); ++ ++ dmem_cgroup_pool_state_put(alloc_state.limit_pool); ++ ++ if (ret) { ++ dmem_cgroup_uncharge(alloc_state.charge_pool, ++ bo->base.size); ++ if (ret == -EBUSY) ++ continue; + return ret; ++ } ++ } else if (ret) { ++ dmem_cgroup_uncharge(alloc_state.charge_pool, bo->base.size); ++ dmem_cgroup_pool_state_put(alloc_state.limit_pool); ++ return ret; + } + + ret = ttm_bo_add_pipelined_eviction_fences(bo, man, ctx->no_wait_gpu); +diff --git a/drivers/gpu/drm/ttm/ttm_resource.c b/drivers/gpu/drm/ttm/ttm_resource.c +--- a/drivers/gpu/drm/ttm/ttm_resource.c ++++ b/drivers/gpu/drm/ttm/ttm_resource.c +@@ -386,33 +386,52 @@ void ttm_resource_fini(struct ttm_resource_manager *man, + } + EXPORT_SYMBOL(ttm_resource_fini); + ++/** ++ * ttm_resource_try_charge - charge a resource manager's cgroup pool ++ * @bo: buffer for which an allocation should be charged ++ * @place: where the allocation is attempted to be placed ++ * @ret_pool: on charge success, the pool that was charged ++ * @ret_limit_pool: on charge failure, the pool responsible for the failure ++ * ++ * Should be used to charge cgroups before attempting resource allocation. ++ * When charging succeeds, the value of ret_pool should be passed to ++ * ttm_resource_alloc. ++ * ++ * Returns: 0 on charge success, negative errno on failure. ++ */ ++int ttm_resource_try_charge(struct ttm_buffer_object *bo, ++ const struct ttm_place *place, ++ struct dmem_cgroup_pool_state **ret_pool, ++ struct dmem_cgroup_pool_state **ret_limit_pool) ++{ ++ struct ttm_resource_manager *man = ++ ttm_manager_type(bo->bdev, place->mem_type); ++ ++ if (!man->cg) { ++ *ret_pool = NULL; ++ if (ret_limit_pool) ++ *ret_limit_pool = NULL; ++ return 0; ++ } ++ ++ return dmem_cgroup_try_charge(man->cg, bo->base.size, ret_pool, ++ ret_limit_pool); ++} ++ + int ttm_resource_alloc(struct ttm_buffer_object *bo, + const struct ttm_place *place, + struct ttm_resource **res_ptr, +- struct dmem_cgroup_pool_state **ret_limit_pool) ++ struct dmem_cgroup_pool_state *charge_pool) + { + struct ttm_resource_manager *man = + ttm_manager_type(bo->bdev, place->mem_type); +- struct dmem_cgroup_pool_state *pool = NULL; + int ret; + +- if (man->cg) { +- ret = dmem_cgroup_try_charge(man->cg, bo->base.size, &pool, ret_limit_pool); +- if (ret) { +- if (ret == -EAGAIN) +- ret = -ENOSPC; +- return ret; +- } +- } +- + ret = man->func->alloc(man, bo, place, res_ptr); +- if (ret) { +- if (pool) +- dmem_cgroup_uncharge(pool, bo->base.size); ++ if (ret) + return ret; +- } + +- (*res_ptr)->css = pool; ++ (*res_ptr)->css = charge_pool; + + spin_lock(&bo->bdev->lru_lock); + ttm_resource_add_bulk_move(*res_ptr, bo); +diff --git a/include/drm/ttm/ttm_resource.h b/include/drm/ttm/ttm_resource.h +--- a/include/drm/ttm/ttm_resource.h ++++ b/include/drm/ttm/ttm_resource.h +@@ -458,10 +458,14 @@ void ttm_resource_init(struct ttm_buffer_object *bo, + void ttm_resource_fini(struct ttm_resource_manager *man, + struct ttm_resource *res); + ++int ttm_resource_try_charge(struct ttm_buffer_object *bo, ++ const struct ttm_place *place, ++ struct dmem_cgroup_pool_state **ret_pool, ++ struct dmem_cgroup_pool_state **ret_limit_pool); + int ttm_resource_alloc(struct ttm_buffer_object *bo, + const struct ttm_place *place, + struct ttm_resource **res, +- struct dmem_cgroup_pool_state **ret_limit_pool); ++ struct dmem_cgroup_pool_state *charge_pool); + void ttm_resource_free(struct ttm_buffer_object *bo, struct ttm_resource **res); + bool ttm_resource_intersects(struct ttm_device *bdev, + struct ttm_resource *res, +diff --git a/include/linux/cgroup.h b/include/linux/cgroup.h +--- a/include/linux/cgroup.h ++++ b/include/linux/cgroup.h +@@ -623,6 +623,27 @@ static inline struct cgroup *cgroup_ancestor(struct cgroup *cgrp, + return cgrp->ancestors[ancestor_level]; + } + ++/** ++ * cgroup_common_ancestor - find common ancestor of two cgroups ++ * @a: first cgroup to find common ancestor of ++ * @b: second cgroup to find common ancestor of ++ * ++ * Find the first cgroup that is an ancestor of both @a and @b, if it exists ++ * and return a pointer to it. If such a cgroup doesn't exist, return NULL. ++ * ++ * This function is safe to call as long as both @a and @b are accessible. ++ */ ++static inline struct cgroup *cgroup_common_ancestor(struct cgroup *a, ++ struct cgroup *b) ++{ ++ int level; ++ ++ for (level = min(a->level, b->level); level >= 0; level--) ++ if (a->ancestors[level] == b->ancestors[level]) ++ return a->ancestors[level]; ++ return NULL; ++} ++ + /** + * task_under_cgroup_hierarchy - test task's membership of cgroup ancestry + * @task: the task to be tested +diff --git a/include/linux/cgroup_dmem.h b/include/linux/cgroup_dmem.h +--- a/include/linux/cgroup_dmem.h ++++ b/include/linux/cgroup_dmem.h +@@ -24,6 +24,12 @@ void dmem_cgroup_uncharge(struct dmem_cgroup_pool_state *pool, u64 size); + bool dmem_cgroup_state_evict_valuable(struct dmem_cgroup_pool_state *limit_pool, + struct dmem_cgroup_pool_state *test_pool, + bool ignore_low, bool *ret_hit_low); ++bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test); ++bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test); ++struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgroup_pool_state *a, ++ struct dmem_cgroup_pool_state *b); + + void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool); + #else +@@ -59,6 +65,25 @@ bool dmem_cgroup_state_evict_valuable(struct dmem_cgroup_pool_state *limit_pool, + return true; + } + ++static inline bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test) ++{ ++ return false; ++} ++ ++static inline bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test) ++{ ++ return false; ++} ++ ++static inline ++struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgroup_pool_state *a, ++ struct dmem_cgroup_pool_state *b) ++{ ++ return NULL; ++} ++ + static inline void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool) + { } + +diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c +--- a/kernel/cgroup/dmem.c ++++ b/kernel/cgroup/dmem.c +@@ -695,6 +695,110 @@ int dmem_cgroup_try_charge(struct dmem_cgroup_region *region, u64 size, + } + EXPORT_SYMBOL_GPL(dmem_cgroup_try_charge); + ++/** ++ * dmem_cgroup_below_min() - Tests whether current usage is within min limit. ++ * ++ * @root: Root of the subtree to calculate protection for, or NULL to calculate global protection. ++ * @test: The pool to test the usage/min limit of. ++ * ++ * Return: true if usage is below min and the cgroup is protected, false otherwise. ++ */ ++bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test) ++{ ++ if (root == test || !pool_parent(test)) ++ return false; ++ ++ if (!root) { ++ for (root = test; pool_parent(root); root = pool_parent(root)) ++ {} ++ } ++ ++ /* ++ * In mem_cgroup_below_min(), the memcg pendant, this call is missing. ++ * mem_cgroup_below_min() gets called during traversal of the cgroup tree, where ++ * protection is already calculated as part of the traversal. dmem cgroup eviction ++ * does not traverse the cgroup tree, so we need to recalculate effective protection ++ * here. ++ */ ++ dmem_cgroup_calculate_protection(root, test); ++ return page_counter_read(&test->cnt) <= READ_ONCE(test->cnt.emin); ++} ++EXPORT_SYMBOL_GPL(dmem_cgroup_below_min); ++ ++/** ++ * dmem_cgroup_below_low() - Tests whether current usage is within low limit. ++ * ++ * @root: Root of the subtree to calculate protection for, or NULL to calculate global protection. ++ * @test: The pool to test the usage/low limit of. ++ * ++ * Return: true if usage is below low and the cgroup is protected, false otherwise. ++ */ ++bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test) ++{ ++ if (root == test || !pool_parent(test)) ++ return false; ++ ++ if (!root) { ++ for (root = test; pool_parent(root); root = pool_parent(root)) ++ {} ++ } ++ ++ /* ++ * In mem_cgroup_below_low(), the memcg pendant, this call is missing. ++ * mem_cgroup_below_low() gets called during traversal of the cgroup tree, where ++ * protection is already calculated as part of the traversal. dmem cgroup eviction ++ * does not traverse the cgroup tree, so we need to recalculate effective protection ++ * here. ++ */ ++ dmem_cgroup_calculate_protection(root, test); ++ return page_counter_read(&test->cnt) <= READ_ONCE(test->cnt.elow); ++} ++EXPORT_SYMBOL_GPL(dmem_cgroup_below_low); ++ ++/** ++ * dmem_cgroup_get_common_ancestor(): Find the first common ancestor of two pools. ++ * @a: First pool to find the common ancestor of. ++ * @b: First pool to find the common ancestor of. ++ * ++ * Return: The first pool that is a parent of both @a and @b, or NULL if either @a or @b are NULL, ++ * or if such a pool does not exist. A reference to the returned pool is grabbed and must be ++ * released by the caller when it is done using the pool. ++ */ ++struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgroup_pool_state *a, ++ struct dmem_cgroup_pool_state *b) ++{ ++ struct cgroup *ancestor_cgroup; ++ struct cgroup_subsys_state *ancestor_css; ++ struct dmemcg_state *ancestor_dmemcs = NULL; ++ struct dmem_cgroup_pool_state *pool = NULL; ++ ++ if (!a || !b) ++ return NULL; ++ ++ ancestor_cgroup = cgroup_common_ancestor(a->cs->css.cgroup, b->cs->css.cgroup); ++ if (!ancestor_cgroup) ++ return NULL; ++ ++ rcu_read_lock(); ++ ancestor_css = cgroup_e_css(ancestor_cgroup, &dmem_cgrp_subsys); ++ if (css_tryget(ancestor_css)) ++ ancestor_dmemcs = css_to_dmemcs(ancestor_css); ++ rcu_read_unlock(); ++ ++ if (ancestor_dmemcs) { ++ pool = get_cg_pool_unlocked(css_to_dmemcs(ancestor_css), ++ a->region); ++ if (WARN_ON(IS_ERR(pool))) { ++ pool = NULL; ++ css_put(ancestor_css); ++ } ++ } ++ return pool; ++} ++EXPORT_SYMBOL_GPL(dmem_cgroup_get_common_ancestor); ++ + static int dmem_cgroup_region_capacity_show(struct seq_file *sf, void *v) + { + struct dmem_cgroup_region *region; diff --git a/pkgbuilds/linux-omarchy/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch b/pkgbuilds/linux-omarchy/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch new file mode 100644 index 0000000..32f3010 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch @@ -0,0 +1,13 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_alpm.c b/drivers/gpu/drm/i915/display/intel_alpm.c +index c6963ea420cc..c38d613f15d6 100644 +--- a/drivers/gpu/drm/i915/display/intel_alpm.c ++++ b/drivers/gpu/drm/i915/display/intel_alpm.c +@@ -399,7 +399,7 @@ static void lnl_alpm_configure(struct intel_dp *intel_dp, + ALPM_CTL_AUX_LESS_SLEEP_HOLD_TIME_50_SYMBOLS | + ALPM_CTL_AUX_LESS_WAKE_TIME(crtc_state->alpm_state.aux_less_wake_lines); + +- if (intel_dp->as_sdp_supported) { ++ if (intel_dp->as_sdp_supported && crtc_state->has_panel_replay) { + u32 pr_alpm_ctl = get_pr_alpm_as_sdp_transmission_time(crtc_state); + + if (crtc_state->link_off_after_as_sdp_when_pr_active) diff --git a/pkgbuilds/linux-omarchy/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch b/pkgbuilds/linux-omarchy/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch new file mode 100644 index 0000000..28a6330 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch @@ -0,0 +1,196 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h +index c21e0c0ef0b1..e62788328bfd 100644 +--- a/drivers/gpu/drm/i915/display/intel_display_types.h ++++ b/drivers/gpu/drm/i915/display/intel_display_types.h +@@ -1780,6 +1780,7 @@ struct intel_psr { + u32 dc3co_exitline; + u32 dc3co_exit_delay; + struct delayed_work dc3co_work; ++ struct delayed_work panel_replay_reenable_work; + u8 entry_setup_frames; + + u8 io_wake_lines; +diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c +index beaa1d62613d..644ab9b1bee9 100644 +--- a/drivers/gpu/drm/i915/display/intel_psr.c ++++ b/drivers/gpu/drm/i915/display/intel_psr.c +@@ -2475,6 +2475,7 @@ void intel_psr_disable(struct intel_dp *intel_dp, + mutex_unlock(&intel_dp->psr.lock); + cancel_work_sync(&intel_dp->psr.work); + cancel_delayed_work_sync(&intel_dp->psr.dc3co_work); ++ cancel_delayed_work_sync(&intel_dp->psr.panel_replay_reenable_work); + } + + /** +@@ -2506,6 +2507,7 @@ void intel_psr_pause(struct intel_dp *intel_dp) + + cancel_work_sync(&psr->work); + cancel_delayed_work_sync(&psr->dc3co_work); ++ cancel_delayed_work_sync(&psr->panel_replay_reenable_work); + } + + /** +@@ -3539,6 +3541,17 @@ static void intel_psr_handle_irq(struct intel_dp *intel_dp) + drm_dp_dpcd_writeb(&intel_dp->aux, DP_SET_POWER, DP_SET_POWER_D0); + } + ++#define PANEL_REPLAY_REENABLE_DELAY_MS 50 ++ ++static bool panel_replay_alpm_cursor_lag_workaround_enabled(struct intel_dp *intel_dp) ++{ ++ return intel_dp_is_edp(intel_dp) && ++ intel_dp->psr.panel_replay_enabled && ++ intel_dp->psr.sel_update_enabled && ++ intel_has_dpcd_quirk(intel_dp, ++ QUIRK_PANEL_REPLAY_ALPM_CURSOR_LAG); ++} ++ + static void intel_psr_work(struct work_struct *work) + { + struct intel_dp *intel_dp = +@@ -3557,6 +3570,11 @@ static void intel_psr_work(struct work_struct *work) + if (intel_dp->psr.pause_counter) + goto unlock; + ++ /* The dedicated delayed work owns re-entry while the workaround is armed. */ ++ if (panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp) && ++ delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work)) ++ goto unlock; ++ + /* + * We have to make sure PSR is ready for re-enable + * otherwise it keeps disabled until next full enable/disable cycle. +@@ -3566,6 +3584,10 @@ static void intel_psr_work(struct work_struct *work) + if (!__psr_wait_for_idle_locked(intel_dp)) + goto unlock; + ++ if (panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp) && ++ delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work)) ++ goto unlock; ++ + /* + * The delayed work can race with an invalidate hence we need to + * recheck. Since psr_flush first clears this and then reschedules we +@@ -3579,6 +3601,33 @@ static void intel_psr_work(struct work_struct *work) + mutex_unlock(&intel_dp->psr.lock); + } + ++static void panel_replay_reenable_work(struct work_struct *work) ++{ ++ struct intel_dp *intel_dp = ++ container_of(work, typeof(*intel_dp), ++ psr.panel_replay_reenable_work.work); ++ ++ mutex_lock(&intel_dp->psr.lock); ++ ++ if (!intel_dp->psr.enabled || ++ !panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp) || ++ intel_dp->psr.pause_counter || ++ READ_ONCE(intel_dp->psr.irq_aux_error)) ++ goto unlock; ++ ++ if (!__psr_wait_for_idle_locked(intel_dp)) ++ goto unlock; ++ ++ /* Recheck for activity or a newer deadline after the unlocked wait. */ ++ if (delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work) || ++ intel_dp->psr.busy_frontbuffer_bits || intel_dp->psr.active) ++ goto unlock; ++ ++ intel_psr_activate(intel_dp); ++unlock: ++ mutex_unlock(&intel_dp->psr.lock); ++} ++ + static void intel_psr_configure_full_frame_update(struct intel_dp *intel_dp) + { + struct intel_display *display = to_intel_display(intel_dp); +@@ -3650,8 +3699,11 @@ void intel_psr_invalidate(struct intel_display *display, + INTEL_FRONTBUFFER_ALL_MASK(intel_dp->psr.pipe); + intel_dp->psr.busy_frontbuffer_bits |= pipe_frontbuffer_bits; + +- if (pipe_frontbuffer_bits) ++ if (pipe_frontbuffer_bits) { ++ if (panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp)) ++ cancel_delayed_work(&intel_dp->psr.panel_replay_reenable_work); + _psr_invalidate_handle(intel_dp); ++ } + + mutex_unlock(&intel_dp->psr.lock); + } +@@ -3767,6 +3819,15 @@ void intel_psr_flush(struct intel_display *display, + if (intel_dp->psr.pause_counter) + goto unlock; + ++ if (pipe_frontbuffer_bits && ++ panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp)) { ++ intel_psr_exit(intel_dp); ++ mod_delayed_work(display->wq.unordered, ++ &intel_dp->psr.panel_replay_reenable_work, ++ msecs_to_jiffies(PANEL_REPLAY_REENABLE_DELAY_MS)); ++ goto unlock; ++ } ++ + if (origin == ORIGIN_FLIP || + (origin == ORIGIN_CURSOR_UPDATE && + !intel_dp->psr.psr2_sel_fetch_enabled)) { +@@ -3830,6 +3891,8 @@ void intel_psr_init(struct intel_dp *intel_dp) + + INIT_WORK(&intel_dp->psr.work, intel_psr_work); + INIT_DELAYED_WORK(&intel_dp->psr.dc3co_work, tgl_dc3co_disable_work); ++ INIT_DELAYED_WORK(&intel_dp->psr.panel_replay_reenable_work, ++ panel_replay_reenable_work); + mutex_init(&intel_dp->psr.lock); + } + +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +index 33245f44c0d5..dc6576016400 100644 +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -86,12 +86,13 @@ static void quirk_edp_limit_rate_hbr2(struct intel_display *display) + drm_info(display->drm, "Applying eDP Limit rate to HBR2 quirk\n"); + } + +-static void quirk_disable_edp_panel_replay(struct intel_dp *intel_dp) ++static void quirk_panel_replay_alpm_cursor_lag(struct intel_dp *intel_dp) + { + struct intel_display *display = to_intel_display(intel_dp); + +- intel_set_dpcd_quirk(intel_dp, QUIRK_DISABLE_EDP_PANEL_REPLAY); +- drm_info(display->drm, "Applying disable Panel Replay quirk\n"); ++ intel_set_dpcd_quirk(intel_dp, QUIRK_PANEL_REPLAY_ALPM_CURSOR_LAG); ++ drm_info(display->drm, ++ "Applying Panel Replay ALPM cursor lag workaround\n"); + } + + static void quirk_disable_psr2(struct intel_display *display) +@@ -276,7 +277,7 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .subsystem_vendor = 0x1028, + .subsystem_device = 0x0db9, + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), +- .hook = quirk_disable_edp_panel_replay, ++ .hook = quirk_panel_replay_alpm_cursor_lag, + }, + /* Dell XPS 16 DA16260 */ + { +@@ -284,7 +285,7 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .subsystem_vendor = 0x1028, + .subsystem_device = 0x0dba, + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), +- .hook = quirk_disable_edp_panel_replay, ++ .hook = quirk_panel_replay_alpm_cursor_lag, + }, + }; + +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.h b/drivers/gpu/drm/i915/display/intel_quirks.h +index 970a4fe52faf..50ef333b87a9 100644 +--- a/drivers/gpu/drm/i915/display/intel_quirks.h ++++ b/drivers/gpu/drm/i915/display/intel_quirks.h +@@ -23,6 +23,7 @@ enum intel_quirk_id { + QUIRK_EDP_LIMIT_RATE_HBR2, + QUIRK_DISABLE_EDP_PANEL_REPLAY, + QUIRK_DISABLE_PSR2, ++ QUIRK_PANEL_REPLAY_ALPM_CURSOR_LAG, + }; + + void intel_init_quirks(struct intel_display *display); diff --git a/pkgbuilds/linux-omarchy/0402-psr2-early-transport-panels.patch b/pkgbuilds/linux-omarchy/0402-psr2-early-transport-panels.patch new file mode 100644 index 0000000..3e88cea --- /dev/null +++ b/pkgbuilds/linux-omarchy/0402-psr2-early-transport-panels.patch @@ -0,0 +1,20 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c +index beaa1d626..5fdf4594c 100644 +--- a/drivers/gpu/drm/i915/display/intel_psr.c ++++ b/drivers/gpu/drm/i915/display/intel_psr.c +@@ -714,8 +714,14 @@ static void _psr_init_dpcd(struct intel_dp *intel_dp, struct intel_connector *co + * To support PSR version 02h and PSR version 03h without + * Y-coordinate requirement panels we would need to enable + * GTC first. ++ * ++ * Early Transport (version 04h) implies Y-coordinate ++ * support. Accept it without explicit Y-coordinate ++ * requirement bit. + */ +- connector->dp.psr_caps.su_support = y_req && ++ connector->dp.psr_caps.su_support = ++ (y_req || connector->dp.psr_caps.dpcd[0] == ++ DP_PSR2_WITH_Y_COORD_ET_SUPPORTED) && + intel_alpm_aux_wake_supported(intel_dp); + drm_dbg_kms(display->drm, "PSR2 %ssupported\n", + connector->dp.psr_caps.su_support ? "" : "not "); diff --git a/pkgbuilds/linux-omarchy/0411-drm-xe-display-no-stolen-framebuffers.patch b/pkgbuilds/linux-omarchy/0411-drm-xe-display-no-stolen-framebuffers.patch new file mode 100644 index 0000000..c1f3ad5 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0411-drm-xe-display-no-stolen-framebuffers.patch @@ -0,0 +1,43 @@ +diff --git a/drivers/gpu/drm/xe/display/xe_display_bo.c b/drivers/gpu/drm/xe/display/xe_display_bo.c +--- a/drivers/gpu/drm/xe/display/xe_display_bo.c ++++ b/drivers/gpu/drm/xe/display/xe_display_bo.c +@@ -147,33 +147,12 @@ static struct drm_gem_object *xe_display_bo_fbdev_create(struct drm_device *drm, + struct xe_device *xe = to_xe_device(drm); + struct xe_bo *obj; + +- obj = ERR_PTR(-ENODEV); +- +- if (xe_display_bo_fbdev_prefer_stolen(xe, size)) { +- obj = xe_bo_create_pin_map_novm(xe, xe_device_get_root_tile(xe), +- size, +- ttm_bo_type_kernel, +- XE_BO_FLAG_FORCE_WC | +- XE_BO_FLAG_STOLEN | +- XE_BO_FLAG_GGTT, +- false); +- if (!IS_ERR(obj)) +- drm_info(&xe->drm, "Allocated fbdev into stolen\n"); +- else +- drm_info(&xe->drm, "Allocated fbdev into stolen failed: %li\n", PTR_ERR(obj)); +- } else { +- drm_info(&xe->drm, "Allocating fbdev: Stolen memory not preferred.\n"); +- } +- +- if (IS_ERR(obj)) { +- obj = xe_bo_create_pin_map_novm(xe, xe_device_get_root_tile(xe), size, +- ttm_bo_type_kernel, +- XE_BO_FLAG_FORCE_WC | +- XE_BO_FLAG_VRAM_IF_DGFX(xe_device_get_root_tile(xe)) | +- XE_BO_FLAG_GGTT, +- false); +- } +- ++ obj = xe_bo_create_pin_map_novm(xe, xe_device_get_root_tile(xe), size, ++ ttm_bo_type_kernel, ++ XE_BO_FLAG_FORCE_WC | ++ XE_BO_FLAG_VRAM_IF_DGFX(xe_device_get_root_tile(xe)) | ++ XE_BO_FLAG_GGTT, ++ false); + if (IS_ERR(obj)) { + drm_err(&xe->drm, "failed to allocate framebuffer (%pe)\n", obj); + return ERR_PTR(-ENOMEM); diff --git a/pkgbuilds/linux-omarchy/0420-safe-window.patch b/pkgbuilds/linux-omarchy/0420-safe-window.patch new file mode 100644 index 0000000..21017c7 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0420-safe-window.patch @@ -0,0 +1,23 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c +--- a/drivers/gpu/drm/i915/display/intel_display.c ++++ b/drivers/gpu/drm/i915/display/intel_display.c +@@ -2452,6 +2452,19 @@ static int intel_crtc_set_context_latency(struct intel_crtc_state *crtc_state) + set_context_latency = max(set_context_latency, + intel_psr_min_set_context_latency(crtc_state)); + ++ /* ++ * From PTL onwards, the set context latency can be in the vactive ++ * region, letting the safe window start some lines before the vblank ++ * start. With modes that have a smaller vblank region, the computed ++ * guardband is clamped to the vblank length, making the undelayed and ++ * delayed vblank coincide. If the SCL is also 0, the 'safe window' ++ * becomes effectively 0, and the DSB configured to wait for it gets ++ * stalled, since the hardware never signals the safe window. Keep the ++ * set context latency at a minimum of 1 to avoid this. ++ */ ++ if (DISPLAY_VER(display) >= 30) ++ set_context_latency = max(1, set_context_latency); ++ + return set_context_latency; + } + diff --git a/pkgbuilds/linux-omarchy/0430-fbc.patch b/pkgbuilds/linux-omarchy/0430-fbc.patch new file mode 100644 index 0000000..4548866 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0430-fbc.patch @@ -0,0 +1,112 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_fbc.c b/drivers/gpu/drm/i915/display/intel_fbc.c +--- a/drivers/gpu/drm/i915/display/intel_fbc.c ++++ b/drivers/gpu/drm/i915/display/intel_fbc.c +@@ -59,6 +59,7 @@ + #include "intel_fbc_regs.h" + #include "intel_frontbuffer.h" + #include "intel_parent.h" ++#include "skl_universal_plane.h" + + #define for_each_fbc_id(__display, __fbc_id) \ + for ((__fbc_id) = INTEL_FBC_A; (__fbc_id) < I915_MAX_FBCS; (__fbc_id)++) \ +@@ -1305,6 +1306,9 @@ static bool intel_fbc_surface_size_ok(const struct intel_plane_state *plane_stat + struct intel_display *display = to_intel_display(plane_state); + unsigned int effective_w, effective_h, max_w, max_h; + ++ if (DISPLAY_VER(display) >= 20) ++ return true; ++ + intel_fbc_max_surface_size(display, &max_w, &max_h); + + effective_w = plane_state->view.color_plane[0].x + +@@ -1315,10 +1319,18 @@ static bool intel_fbc_surface_size_ok(const struct intel_plane_state *plane_stat + return effective_w <= max_w && effective_h <= max_h; + } + +-static void intel_fbc_max_plane_size(struct intel_display *display, ++static void intel_fbc_max_plane_size(const struct intel_plane_state *plane_state, + unsigned int *w, unsigned int *h) + { +- if (DISPLAY_VER(display) >= 10) { ++ struct intel_display *display = to_intel_display(plane_state); ++ struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); ++ const struct drm_framebuffer *fb = plane_state->hw.fb; ++ unsigned int rotation = plane_state->hw.rotation; ++ ++ if (DISPLAY_VER(display) >= 20) { ++ *w = intel_plane_max_width(plane, fb, 0, rotation); ++ *h = 4096; ++ } else if (DISPLAY_VER(display) >= 10) { + *w = 5120; + *h = 4096; + } else if (DISPLAY_VER(display) >= 8 || display->platform.haswell) { +@@ -1335,10 +1347,9 @@ static void intel_fbc_max_plane_size(struct intel_display *display, + + static bool intel_fbc_plane_size_valid(const struct intel_plane_state *plane_state) + { +- struct intel_display *display = to_intel_display(plane_state); + unsigned int w, h, max_w, max_h; + +- intel_fbc_max_plane_size(display, &max_w, &max_h); ++ intel_fbc_max_plane_size(plane_state, &max_w, &max_h); + + w = drm_rect_width(&plane_state->uapi.src) >> 16; + h = drm_rect_height(&plane_state->uapi.src) >> 16; +diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c +--- a/drivers/gpu/drm/i915/display/skl_universal_plane.c ++++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c +@@ -1937,10 +1937,10 @@ static int intel_plane_min_height(struct intel_plane *plane, + return 1; + } + +-static int intel_plane_max_width(struct intel_plane *plane, +- const struct drm_framebuffer *fb, +- int color_plane, +- unsigned int rotation) ++int intel_plane_max_width(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation) + { + if (plane->max_width) + return plane->max_width(fb, color_plane, rotation); +@@ -1948,10 +1948,10 @@ static int intel_plane_max_width(struct intel_plane *plane, + return INT_MAX; + } + +-static int intel_plane_max_height(struct intel_plane *plane, +- const struct drm_framebuffer *fb, +- int color_plane, +- unsigned int rotation) ++int intel_plane_max_height(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation) + { + if (plane->max_height) + return plane->max_height(fb, color_plane, rotation); +diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.h b/drivers/gpu/drm/i915/display/skl_universal_plane.h +--- a/drivers/gpu/drm/i915/display/skl_universal_plane.h ++++ b/drivers/gpu/drm/i915/display/skl_universal_plane.h +@@ -8,6 +8,7 @@ + + #include + ++struct drm_framebuffer; + struct intel_crtc; + struct intel_display; + struct intel_initial_plane_config; +@@ -42,5 +43,13 @@ bool icl_is_hdr_plane(struct intel_display *display, enum plane_id plane_id); + + u32 skl_plane_aux_dist(const struct intel_plane_state *plane_state, + int color_plane); ++int intel_plane_max_width(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation); ++int intel_plane_max_height(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation); + + #endif diff --git a/pkgbuilds/linux-omarchy/0440-xe3-peak-bandwidth.patch b/pkgbuilds/linux-omarchy/0440-xe3-peak-bandwidth.patch new file mode 100644 index 0000000..a13b165 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0440-xe3-peak-bandwidth.patch @@ -0,0 +1,67 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c +--- a/drivers/gpu/drm/i915/display/intel_bw.c ++++ b/drivers/gpu/drm/i915/display/intel_bw.c +@@ -52,6 +52,8 @@ + + #define DEPROGBWPCLIMIT 60 + ++#define PEAK_BW_THRESHOLD 20000 ++ + struct intel_psf_gv_point { + u8 clk; /* clock in multiples of 16.6666 MHz */ + }; +@@ -587,6 +589,33 @@ + return num_channels * (channel_width / 8) * dclk; + } + ++static void xe3_add_peakbw_threshold(struct intel_display *display) ++{ ++ u8 qgv_points = display->bw.max[0].num_qgv_points; ++ ++ if (!HAS_PEAK_BW_THRESHOLD(display)) ++ return; ++ ++ if (qgv_points >= I915_NUM_QGV_POINTS) { ++ drm_dbg_kms(display->drm, "QGV points maxed out; skipping peak bandwidth threshold.\n"); ++ return; ++ } ++ ++ if (qgv_points <= 1) ++ return; ++ ++ for (int i = 0; i < ARRAY_SIZE(display->bw.max); i++) { ++ struct intel_bw_info *bi = &display->bw.max[i]; ++ ++ bi->num_qgv_points++; ++ bi->peakbw[qgv_points] = PEAK_BW_THRESHOLD; ++ bi->deratedbw[qgv_points] = PEAK_BW_THRESHOLD; ++ } ++ ++ drm_dbg_kms(display->drm, "An extra QGV point %d added for Peak bw threshod of %d\n", ++ qgv_points, PEAK_BW_THRESHOLD); ++} ++ + static int tgl_get_bw_info(struct intel_display *display, + const struct dram_info *dram_info, + const struct intel_soc_bw_params *soc_bw_params, +@@ -684,6 +713,9 @@ + } + } + ++ /* For xe3 cases add an extra qgv point for Peak bw threshold */ ++ xe3_add_peakbw_threshold(display); ++ + /* + * In case if SAGV is disabled in BIOS, we always get 1 + * SAGV point, but we can't send PCode commands to restrict it +diff --git a/drivers/gpu/drm/i915/display/intel_display_device.h b/drivers/gpu/drm/i915/display/intel_display_device.h +--- a/drivers/gpu/drm/i915/display/intel_display_device.h ++++ b/drivers/gpu/drm/i915/display/intel_display_device.h +@@ -191,6 +191,7 @@ + #define HAS_MBUS_JOINING(__display) ((__display)->platform.alderlake_p || DISPLAY_VER(__display) >= 14) + #define HAS_MSO(__display) (DISPLAY_VER(__display) >= 12) + #define HAS_OVERLAY(__display) (DISPLAY_INFO(__display)->has_overlay) ++#define HAS_PEAK_BW_THRESHOLD(__display) (DISPLAY_VER(__display) >= 30) + #define HAS_PIPEDMC(__display) (DISPLAY_VER(__display) >= 12) + #define HAS_PIXEL_NORMALIZER(__display) (DISPLAY_VER(__display) >= 35) + #define HAS_PSR(__display) (DISPLAY_INFO(__display)->has_psr) diff --git a/pkgbuilds/linux-omarchy/0450-amd-hdmi-vrr-allm.patch b/pkgbuilds/linux-omarchy/0450-amd-hdmi-vrr-allm.patch new file mode 100644 index 0000000..ebe9a36 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0450-amd-hdmi-vrr-allm.patch @@ -0,0 +1,456 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -104,6 +104,7 @@ + #include "ivsrcid/dcn/irqsrcs_dcn_1_0.h" + + #include "modules/inc/mod_freesync.h" ++#include "modules/inc/mod_info_packet.h" + #include "modules/inc/mod_power.h" + #include "modules/power/power_helpers.h" + +@@ -10068,6 +10069,9 @@ static void update_freesync_state_on_stream( + &vrr_infopacket, + pack_sdp_v1_3); + ++ if (new_stream->sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) ++ mod_build_infopacket_vtem(new_stream, &vrr_params, 0, &vrr_infopacket); ++ + new_crtc_state->freesync_vrr_info_changed |= + (memcmp(&new_crtc_state->vrr_infopacket, + &vrr_infopacket, +@@ -10079,6 +10083,36 @@ static void update_freesync_state_on_stream( + new_stream->vrr_infopacket = vrr_infopacket; + new_stream->allow_freesync = mod_freesync_get_freesync_enabled(&vrr_params); + ++ /* ++ * HDMI ALLM: when Gaming-VRR is active (VRR_EN=1) and the sink ++ * advertises ALLM in the SCDS, the Source shall transmit the HF-VSIF ++ * with ALLM_Mode=1 (HDMI 2.1 Section 7.6.6). ++ */ ++ if (new_stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || ++ new_stream->signal == SIGNAL_TYPE_HDMI_FRL) { ++ struct dc_info_packet hf_vsif = {0}; ++ bool sink_allm = aconn && aconn->base.display_info.hdmi.allm; ++ bool allm = sink_allm && ++ (vrr_params.state == VRR_STATE_ACTIVE_VARIABLE || ++ vrr_params.state == VRR_STATE_ACTIVE_FIXED); ++ bool allm_changed; ++ ++ if (allm) ++ mod_build_hf_vsif_infopacket(new_stream, &hf_vsif, allm, allm); ++ ++ allm_changed = memcmp(&new_stream->hfvsif_infopacket, &hf_vsif, ++ sizeof(hf_vsif)) != 0; ++ new_crtc_state->freesync_vrr_info_changed |= allm_changed; ++ new_stream->hfvsif_infopacket = hf_vsif; ++ ++ if (allm_changed) ++ drm_dbg_driver(adev_to_drm(adev), ++ "ALLM: flip on crtc=%u: sink_allm=%d vrr_state=%d -> ALLM_Mode=%d\n", ++ new_crtc_state->base.crtc->base.id, ++ sink_allm, ++ vrr_params.state, allm); ++ } ++ + if (new_crtc_state->freesync_vrr_info_changed) + drm_dbg_kms(adev_to_drm(adev), "VRR packet update: crtc=%u enabled=%d state=%d", + new_crtc_state->base.crtc->base.id, +@@ -10645,9 +10679,12 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, + } + + if (acrtc_state->stream) { +- if (acrtc_state->freesync_vrr_info_changed) ++ if (acrtc_state->freesync_vrr_info_changed) { + bundle->stream_update.vrr_infopacket = + &acrtc_state->stream->vrr_infopacket; ++ bundle->stream_update.hfvsif_infopacket = ++ &acrtc_state->stream->hfvsif_infopacket; ++ } + } + } + +@@ -12049,6 +12086,14 @@ static void get_freesync_config_for_crtc( + } + out: + new_crtc_state->freesync_config = config; ++ ++ drm_dbg_driver(new_con_state->base.connector->dev, ++ "VRR: cfg vrr_enabled=%d vrr_supported=%d fs_capable=%d vrefresh=%d min=%d max=%d state=%d\n", ++ new_crtc_state->base.vrr_enabled, ++ new_crtc_state->vrr_supported, ++ new_con_state->freesync_capable, vrefresh, ++ aconnector->min_vfreq, aconnector->max_vfreq, ++ config.state); + } + + static void reset_freesync_config_for_crtc( +@@ -14011,6 +14056,15 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + if (!adev->dm.freesync_module || !dc_supports_vrr(sink->ctx->dce_version)) + goto update; + ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: enter signal=%d hdmi_vrr=%d mrange[%d-%d] hdmi.vrr_cap[sup=%d min=%d max=%d]\n", ++ sink->sink_signal, connector->display_info.hdmi.vrr_cap.supported, ++ connector->display_info.monitor_range.min_vfreq, ++ connector->display_info.monitor_range.max_vfreq, ++ connector->display_info.hdmi.vrr_cap.supported, ++ connector->display_info.hdmi.vrr_cap.vrr_min, ++ connector->display_info.hdmi.vrr_cap.vrr_max); ++ + edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw() + + /* Some eDP panels only have the refresh rate range info in DisplayID */ +@@ -14036,7 +14090,9 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_EDP; + } + +- } else if (drm_edid && sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) { ++ } else if (drm_edid && ++ (sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A || ++ sink->sink_signal == SIGNAL_TYPE_HDMI_FRL)) { + i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); + if (i >= 0) { + amdgpu_dm_connector->vsdb_info = vsdb_info; +@@ -14052,6 +14108,59 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz; + } + } ++ ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: amd_vsdb i=%d fs_sup=%d min=%d max=%d fs_capable=%d\n", ++ i, vsdb_info.freesync_supported, ++ vsdb_info.min_refresh_rate_hz, ++ vsdb_info.max_refresh_rate_hz, freesync_capable); ++ ++ /* ++ * If AMD VSDB didn't provide a valid FreeSync range, fall back to ++ * the HDMI 2.1 VRR capability parsed from the HF-VSDB. ++ */ ++ if (!freesync_capable && connector->display_info.hdmi.vrr_cap.supported) { ++ struct drm_hdmi_vrr_cap *vrr_cap = ++ &connector->display_info.hdmi.vrr_cap; ++ ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: HF-VSDB fallback: hdmi_vrr=1 vrr_cap[sup=%d min=%d max=%d] mrange_max=%d\n", ++ vrr_cap->supported, vrr_cap->vrr_min, vrr_cap->vrr_max, ++ connector->display_info.monitor_range.max_vfreq); ++ ++ if (vrr_cap->supported && vrr_cap->vrr_min > 0) { ++ amdgpu_dm_connector->min_vfreq = vrr_cap->vrr_min; ++ amdgpu_dm_connector->max_vfreq = vrr_cap->vrr_max ? ++ vrr_cap->vrr_max : ++ connector->display_info.monitor_range.max_vfreq; ++ ++ /* ++ * VRRMAX = 0 in the HF-VSDB means "up to the Base ++ * Refresh Rate". If the EDID also did not provide a ++ * monitor range max, fall back to the Base Refresh ++ * Rate (the highest refresh rate of the preferred ++ * timing) so a valid VRR range is still reported to ++ * userspace. ++ */ ++ if (!amdgpu_dm_connector->max_vfreq) { ++ struct drm_display_mode *brr_mode = ++ get_highest_refresh_rate_mode(amdgpu_dm_connector, true); ++ ++ if (brr_mode) ++ amdgpu_dm_connector->max_vfreq = ++ drm_mode_vrefresh(brr_mode); ++ } ++ ++ if (amdgpu_dm_connector->max_vfreq - ++ amdgpu_dm_connector->min_vfreq > 10) ++ freesync_capable = true; ++ ++ connector->display_info.monitor_range.min_vfreq = ++ amdgpu_dm_connector->min_vfreq; ++ connector->display_info.monitor_range.max_vfreq = ++ amdgpu_dm_connector->max_vfreq; ++ } ++ } + } + + if (amdgpu_dm_connector->dc_link) +@@ -14093,6 +14202,11 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + if (dm_con_state) + dm_con_state->freesync_capable = freesync_capable; + ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: caps result: freesync_capable=%d min_vfreq=%d max_vfreq=%d\n", ++ freesync_capable, amdgpu_dm_connector->min_vfreq, ++ amdgpu_dm_connector->max_vfreq); ++ + if (connector->state && amdgpu_dm_connector->dc_link && !freesync_capable && + amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported) { + amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported = false; +diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h +--- a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h ++++ b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h +@@ -67,6 +67,10 @@ struct AS_Df_params { + struct frame_duration_op decrease; + }; + ++void mod_build_infopacket_vtem(const struct dc_stream_state *stream, ++ const struct mod_vrr_params *vrr, int fva_factor, ++ struct dc_info_packet *infopacket); ++ + void mod_build_adaptive_sync_infopacket(const struct dc_stream_state *stream, + enum adaptive_sync_type asType, const struct AS_Df_params *param, + struct dc_info_packet *info_packet); +diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +--- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c ++++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +@@ -291,6 +291,21 @@ void set_vsc_packet_colorimetry_data( + info_packet->sb[18] = 0; + } + ++static void set_field_with_mask(unsigned char *dest, unsigned int mask, unsigned int value) ++{ ++ unsigned int shift = 0; ++ ++ if (!mask || !dest) ++ return; ++ ++ while (!((mask >> shift) & 1)) ++ shift++; ++ ++ *dest = *dest & ~mask; ++ value = value & (mask >> shift); ++ *dest = *dest | (value << shift); ++} ++ + void mod_build_vsc_infopacket(const struct dc_stream_state *stream, + struct dc_info_packet *info_packet, + enum dc_color_space cs, +@@ -644,6 +659,100 @@ void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, + info_packet->valid = true; + } + ++static void build_vtem_infopacket_data(const struct dc_stream_state *stream, ++ const struct mod_vrr_params *vrr, int fva_factor, ++ struct dc_info_packet *infopacket) ++{ ++ unsigned int field_rate_in_hz; ++ ++ /* FVA Factor setting */ ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__FVA_FACTOR_M1, ++ (fva_factor > 0) ? (fva_factor - 1) : 0); ++ /* VRR Parameters */ ++ if (vrr->state == VRR_STATE_ACTIVE_VARIABLE || ++ vrr->state == VRR_STATE_ACTIVE_FIXED) { ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__VRR_EN, 1); ++ } else { ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__VRR_EN, 0); ++ } ++ ++ if (vrr->state == VRR_STATE_ACTIVE_FIXED) ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__M_CONST, vrr->m_const); ++ ++ if (!stream->timing.vic) { ++ set_field_with_mask(&infopacket->sb[VTEM_MD1], MASK_VTEM_MD1__BASE_VFRONT, ++ stream->timing.v_front_porch); ++ ++ ++ /* TODO: In dal2, we check mode flags for a reduced blanking timing. ++ * Need a way to relay that information to this function. ++ * if("ReducedBlanking") ++ * { ++ * set_field_with_mask(&infopacket->sb[VRR_VTEM_MD2], MASK__VRR_VTEM_MD2__RB, 1; ++ * } ++ */ ++ ++ field_rate_in_hz = stream->timing.pix_clk_100hz * 100; ++ field_rate_in_hz /= stream->timing.h_total; ++ field_rate_in_hz = (field_rate_in_hz + stream->timing.v_total / 2) ++ / stream->timing.v_total; ++ ++ set_field_with_mask(&infopacket->sb[VTEM_MD2], MASK_VTEM_MD2__BASE_REFRESH_RATE_98, ++ field_rate_in_hz >> 8); ++ set_field_with_mask(&infopacket->sb[VTEM_MD3], MASK_VTEM_MD3__BASE_REFRESH_RATE_07, ++ field_rate_in_hz); ++ ++ } ++ ++ /* ++ * When no VTEM feature is enabled (neither VRR nor FVA), signal a ++ * zero-length data set (MLDS) by clearing Data_Set_Length. HDMI 2.1 ++ * 10.10.2.4 requires the Source to either stop transmitting the VTEM ++ * or set Data_Set_Length = 0 when no feature is enabled; keeping the ++ * VTEM with Data_Set_Length = 0 preserves the every-MTW cadence while ++ * staying compliant (e.g. HDMI GCTS HF1-58 step 6.2). ++ */ ++ if (vrr->state != VRR_STATE_ACTIVE_VARIABLE && ++ vrr->state != VRR_STATE_ACTIVE_FIXED && fva_factor == 0) ++ set_field_with_mask(&infopacket->sb[VTEM_PB6], ++ MASK_VTEM_PB6__DATA_SET_LENGTH_LSB, 0); ++ ++ infopacket->valid = true; ++} ++ ++static void build_infopacket_header_vtem(enum signal_type signal, ++ struct dc_info_packet *infopacket) ++{ ++ /* HEADER */ ++ ++ /* HB0, HB1, HB2 indicates PacketType VTEMPacket */ ++ infopacket->hb0 = 0x7F; ++ infopacket->hb1 = 0xC0; ++ infopacket->hb2 = 0x00; /* sequence_index */ ++ ++ set_field_with_mask(&infopacket->sb[VTEM_PB0], MASK_VTEM_PB0__VFR, 1); ++ set_field_with_mask(&infopacket->sb[VTEM_PB2], MASK_VTEM_PB2__ORGANIZATION_ID, 1); ++ set_field_with_mask(&infopacket->sb[VTEM_PB3], MASK_VTEM_PB3__DATA_SET_TAG_MSB, 0); ++ set_field_with_mask(&infopacket->sb[VTEM_PB4], MASK_VTEM_PB4__DATA_SET_TAG_LSB, 1); ++ set_field_with_mask(&infopacket->sb[VTEM_PB5], MASK_VTEM_PB5__DATA_SET_LENGTH_MSB, 0); ++ set_field_with_mask(&infopacket->sb[VTEM_PB6], MASK_VTEM_PB6__DATA_SET_LENGTH_LSB, 4); ++} ++ ++void mod_build_infopacket_vtem(const struct dc_stream_state *stream, ++ const struct mod_vrr_params *vrr, int fva_factor, ++ struct dc_info_packet *infopacket) ++{ ++ /* VTEM info packet for HdmiVrr */ ++ ++ memset(infopacket, 0, sizeof(struct dc_info_packet)); ++ ++ /* VTEM Packet is structured differently */ ++ build_infopacket_header_vtem(stream->signal, infopacket); ++ build_vtem_infopacket_data(stream, vrr, fva_factor, infopacket); ++ ++ infopacket->valid = true; ++} ++ + void mod_build_adaptive_sync_infopacket(const struct dc_stream_state *stream, + enum adaptive_sync_type asType, + const struct AS_Df_params *param, +diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c +--- a/drivers/gpu/drm/drm_edid.c ++++ b/drivers/gpu/drm/drm_edid.c +@@ -6182,6 +6182,33 @@ static void drm_parse_ycbcr420_deep_color_info(struct drm_connector *connector, + hdmi->y420_dc_modes = dc_mask; + } + ++static void drm_parse_hdmi_gaming_info(struct drm_hdmi_info *hdmi, const u8 *db) ++{ ++ struct drm_hdmi_vrr_cap *vrr = &hdmi->vrr_cap; ++ ++ if (cea_db_payload_len(db) < 8) ++ return; ++ ++ hdmi->fapa_start_location = db[8] & DRM_EDID_FAPA_START_LOCATION; ++ hdmi->allm = db[8] & DRM_EDID_ALLM; ++ vrr->fva = db[8] & DRM_EDID_FVA; ++ vrr->cnmvrr = db[8] & DRM_EDID_CNMVRR; ++ vrr->cinema_vrr = db[8] & DRM_EDID_CINEMA_VRR; ++ vrr->mdelta = db[8] & DRM_EDID_MDELTA; ++ ++ if (cea_db_payload_len(db) < 9) ++ return; ++ ++ vrr->vrr_min = db[9] & DRM_EDID_VRR_MIN_MASK; ++ vrr->supported = (vrr->vrr_min > 0 && vrr->vrr_min <= 48); ++ ++ if (cea_db_payload_len(db) < 10) ++ return; ++ ++ vrr->vrr_max = (db[9] & DRM_EDID_VRR_MAX_UPPER_MASK) << 2 | db[10]; ++ vrr->supported &= (vrr->vrr_max == 0 || vrr->vrr_max >= 100); ++} ++ + static void drm_parse_dsc_info(struct drm_hdmi_dsc_cap *hdmi_dsc, + const u8 *hf_scds) + { +@@ -6308,6 +6335,8 @@ static void drm_parse_hdmi_forum_scds(struct drm_connector *connector, + + drm_parse_ycbcr420_deep_color_info(connector, hf_scds); + ++ drm_parse_hdmi_gaming_info(&connector->display_info.hdmi, hf_scds); ++ + if (cea_db_payload_len(hf_scds) >= 11 && hf_scds[11]) { + drm_parse_dsc_info(hdmi_dsc, hf_scds); + dsc_support = true; +@@ -6317,6 +6346,19 @@ static void drm_parse_hdmi_forum_scds(struct drm_connector *connector, + "[CONNECTOR:%d:%s] HF-VSDB: max TMDS clock: %d KHz, HDMI 2.1 support: %s, DSC 1.2 support: %s\n", + connector->base.id, connector->name, + max_tmds_clock, str_yes_no(max_frl_rate), str_yes_no(dsc_support)); ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] FAPA in blanking: %s, ALLM support: %s, Fast Vactive support: %s\n", ++ connector->base.id, connector->name, str_yes_no(hdmi->fapa_start_location), ++ str_yes_no(hdmi->allm), str_yes_no(hdmi->vrr_cap.fva)); ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] Negative M VRR support: %s, CinemaVRR support: %s, Mdelta: %d\n", ++ connector->base.id, connector->name, str_yes_no(hdmi->vrr_cap.cnmvrr), ++ str_yes_no(hdmi->vrr_cap.cinema_vrr), hdmi->vrr_cap.mdelta); ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] VRRmin: %u, VRRmax: %u, VRR supported: %s\n", ++ connector->base.id, connector->name, hdmi->vrr_cap.vrr_min, ++ hdmi->vrr_cap.vrr_max, str_yes_no(hdmi->vrr_cap.supported)); ++ + } + + static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector, +diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h +--- a/include/drm/drm_connector.h ++++ b/include/drm/drm_connector.h +@@ -254,6 +254,44 @@ struct drm_scdc { + struct drm_scrambling scrambling; + }; + ++/** ++ * struct drm_hdmi_vrr_cap - Information about VRR capabilities of a HDMI sink ++ * ++ * Describes the VRR support provided by HDMI 2.1 sink. The information is ++ * fetched fom additional HFVSDB blocks defined for HDMI 2.1. ++ */ ++struct drm_hdmi_vrr_cap { ++ /** @fva: flag for Fast VActive (Quick Frame Transport) support */ ++ bool fva; ++ ++ /** @mcnmvrr: flag for Negative M VRR support */ ++ bool cnmvrr; ++ ++ /** @mcinema_vrr: flag for Cinema VRR support */ ++ bool cinema_vrr; ++ ++ /** @mdelta: flag for limited frame-to-frame compensation support */ ++ bool mdelta; ++ ++ /** ++ * @vrr_min : minimum supported variable refresh rate in Hz. ++ * Valid values only inide 1 - 48 range ++ */ ++ u16 vrr_min; ++ ++ /** ++ * @vrr_max : maximum supported variable refresh rate in Hz (optional). ++ * Valid values are either 0 (max based on video mode) or >= 100 ++ */ ++ u16 vrr_max; ++ ++ /** ++ * @supported: flag for vrr support based on checking for VRRmin and ++ * VRRmax values having correct values. ++ */ ++ bool supported; ++}; ++ + /** + * struct drm_hdmi_dsc_cap - DSC capabilities of HDMI sink + * +@@ -330,6 +368,15 @@ struct drm_hdmi_info { + /** @max_lanes: supported by sink */ + u8 max_lanes; + ++ /** @fapa_start_location: flag for the FAPA in blanking support */ ++ bool fapa_start_location; ++ ++ /** @allm: flag for Auto Low Latency Mode support by sink */ ++ bool allm; ++ ++ /** @vrr_cap: VRR capabilities of the sink */ ++ struct drm_hdmi_vrr_cap vrr_cap; ++ + /** @dsc_cap: DSC capabilities of the sink */ + struct drm_hdmi_dsc_cap dsc_cap; + }; diff --git a/pkgbuilds/linux-omarchy/0451-amd-vtem-tmds-links.patch b/pkgbuilds/linux-omarchy/0451-amd-vtem-tmds-links.patch new file mode 100644 index 0000000..f61c400 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0451-amd-vtem-tmds-links.patch @@ -0,0 +1,15 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -10069,7 +10069,10 @@ static void update_freesync_state_on_stream( + &vrr_infopacket, + pack_sdp_v1_3); + +- if (new_stream->sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) ++ /* Per HDMI 2.1, VTEM is valid on TMDS as well as FRL */ ++ if (new_stream->sink->sink_signal == SIGNAL_TYPE_HDMI_FRL || ++ (new_stream->sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A && ++ aconn && aconn->base.display_info.hdmi.vrr_cap.supported)) + mod_build_infopacket_vtem(new_stream, &vrr_params, 0, &vrr_infopacket); + + new_crtc_state->freesync_vrr_info_changed |= diff --git a/pkgbuilds/linux-omarchy/0452-amd-hdmi-frl-default.patch b/pkgbuilds/linux-omarchy/0452-amd-hdmi-frl-default.patch new file mode 100644 index 0000000..e392ccc --- /dev/null +++ b/pkgbuilds/linux-omarchy/0452-amd-hdmi-frl-default.patch @@ -0,0 +1,28 @@ +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +index 1aed121f4ddb..081de37c2083 100644 +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +@@ -216,8 +216,9 @@ int amdgpu_smu_pptable_id = -1; + * DISABLE_FRACTIONAL_PWM (bit 2) disabled by default + * PSR (bit 3) disabled by default + * EDP NO POWER SEQUENCING (bit 4) disabled by default ++ * FRL (bit 10) enabled by default + */ +-uint amdgpu_dc_feature_mask = 2; ++uint amdgpu_dc_feature_mask = DC_MULTI_MON_PP_MCLK_SWITCH_MASK | DC_FRL_MASK; + uint amdgpu_dc_debug_mask; + uint amdgpu_dc_visual_confirm; + int amdgpu_async_gfx_ring = 1; +diff --git a/drivers/gpu/drm/amd/include/amd_shared.h b/drivers/gpu/drm/amd/include/amd_shared.h +index 3fd38323a88b..bdd80c5e378e 100644 +--- a/drivers/gpu/drm/amd/include/amd_shared.h ++++ b/drivers/gpu/drm/amd/include/amd_shared.h +@@ -287,7 +287,7 @@ enum DC_FEATURE_MASK { + */ + DC_REPLAY_MASK = (1 << 9), + /** +- * @DC_FRL_MASK: (0x400) disabled by default ++ * @DC_FRL_MASK: (0x400) enabled by default + */ + DC_FRL_MASK = (1 << 10), + }; diff --git a/pkgbuilds/linux-omarchy/0460-vesa-displayid-dsc-bpp.patch b/pkgbuilds/linux-omarchy/0460-vesa-displayid-dsc-bpp.patch new file mode 100644 index 0000000..1ba6ec7 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0460-vesa-displayid-dsc-bpp.patch @@ -0,0 +1,335 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -7096,6 +7096,11 @@ static void fill_stream_properties_from_drm_display_mode( + + stream->output_color_space = get_output_color_space(timing_out, connector_state); + stream->content_type = get_output_content_type(connector_state); ++ ++ /* DisplayID Type VII pass-through timings. */ ++ if (mode_in->dsc_passthrough_timings_support && info->dp_dsc_bpp_x16 != 0) { ++ stream->timing.dsc_fixed_bits_per_pixel_x16 = info->dp_dsc_bpp_x16; ++ } + } + + static void fill_audio_info(struct audio_info *audio_info, +@@ -7616,6 +7621,7 @@ create_stream_for_sink(struct drm_connector *connector, + struct drm_display_mode mode; + struct drm_display_mode saved_mode; + struct drm_display_mode *freesync_mode = NULL; ++ struct drm_display_mode *dsc_passthru_mode = NULL; + bool native_mode_found = false; + bool recalculate_timing = false; + bool scale = dm_state->scaling != RMX_OFF; +@@ -7707,6 +7713,16 @@ create_stream_for_sink(struct drm_connector *connector, + } + } + ++ list_for_each_entry(dsc_passthru_mode, &connector->modes, head) { ++ if (dsc_passthru_mode->hdisplay == mode.hdisplay && ++ dsc_passthru_mode->vdisplay == mode.vdisplay && ++ drm_mode_vrefresh(dsc_passthru_mode) == mode_refresh) { ++ mode.dsc_passthrough_timings_support = ++ dsc_passthru_mode->dsc_passthrough_timings_support; ++ break; ++ } ++ } ++ + if (recalculate_timing) + drm_mode_set_crtcinfo(&saved_mode, 0); + +diff --git a/drivers/gpu/drm/drm_displayid_internal.h b/drivers/gpu/drm/drm_displayid_internal.h +--- a/drivers/gpu/drm/drm_displayid_internal.h ++++ b/drivers/gpu/drm/drm_displayid_internal.h +@@ -98,6 +98,7 @@ struct displayid_header { + u8 ext_count; + } __packed; + ++#define DISPLAYID_BLOCK_REV GENMASK(2, 0) + struct displayid_block { + u8 tag; + u8 rev; +@@ -126,6 +127,7 @@ struct displayid_detailed_timings_1 { + __le16 vsw; + } __packed; + ++#define DISPLAYID_BLOCK_PASSTHROUGH_TIMINGS_SUPPORT BIT(3) + struct displayid_detailed_timing_block { + struct displayid_block base; + struct displayid_detailed_timings_1 timings[]; +@@ -138,19 +140,28 @@ struct displayid_formula_timings_9 { + u8 vrefresh; + } __packed; + ++#define DISPLAYID_BLOCK_DESCRIPTOR_PAYLOAD_BYTES GENMASK(6, 4) + struct displayid_formula_timing_block { + struct displayid_block base; + struct displayid_formula_timings_9 timings[]; + } __packed; + ++#define DISPLAYID_VESA_DP_TYPE GENMASK(2, 0) + #define DISPLAYID_VESA_MSO_OVERLAP GENMASK(3, 0) + #define DISPLAYID_VESA_MSO_MODE GENMASK(6, 5) ++#define DISPLAYID_VESA_DSC_BPP_INT GENMASK(5, 0) ++#define DISPLAYID_VESA_DSC_BPP_FRACT GENMASK(3, 0) ++ ++#define DISPLAYID_VESA_DP_TYPE_EDP 0 ++#define DISPLAYID_VESA_DP_TYPE_DP 1 + + struct displayid_vesa_vendor_specific_block { + struct displayid_block base; + u8 oui[3]; + u8 data_structure_type; + u8 mso; ++ u8 dsc_bpp_int; ++ u8 dsc_bpp_fract; + } __packed; + + /* +diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c +--- a/drivers/gpu/drm/drm_edid.c ++++ b/drivers/gpu/drm/drm_edid.c +@@ -45,6 +45,7 @@ + #include + #include + #include ++#include + #include + + #include "drm_crtc_internal.h" +@@ -6683,12 +6684,13 @@ static bool drm_update_displayid_adaptive_sync_range(struct drm_connector *conne + return false; + } + +-static void drm_parse_vesa_mso_data(struct drm_connector *connector, +- const struct displayid_block *block) ++static void drm_parse_vesa_specific_block(struct drm_connector *connector, ++ const struct displayid_block *block) + { + struct displayid_vesa_vendor_specific_block *vesa = + (struct displayid_vesa_vendor_specific_block *)block; + struct drm_display_info *info = &connector->display_info; ++ int dp_type; + + if (block->num_bytes < 3) { + drm_dbg_kms(connector->dev, +@@ -6700,51 +6702,73 @@ static void drm_parse_vesa_mso_data(struct drm_connector *connector, + if (oui(vesa->oui[0], vesa->oui[1], vesa->oui[2]) != VESA_IEEE_OUI) + return; + +- if (sizeof(*vesa) != sizeof(*block) + block->num_bytes) { ++ if (block->num_bytes < 5) { + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] Unexpected VESA vendor block size\n", + connector->base.id, connector->name); + return; + } + +- switch (FIELD_GET(DISPLAYID_VESA_MSO_MODE, vesa->mso)) { +- default: +- drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Reserved MSO mode value\n", ++ dp_type = FIELD_GET(DISPLAYID_VESA_DP_TYPE, vesa->data_structure_type); ++ if (dp_type > 1) { ++ drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Reserved dp type value\n", + connector->base.id, connector->name); +- fallthrough; +- case 0: +- info->mso_stream_count = 0; +- break; +- case 1: +- info->mso_stream_count = 2; /* 2 or 4 links */ +- break; +- case 2: +- info->mso_stream_count = 4; /* 4 links */ +- break; + } + +- if (!info->mso_stream_count) { ++ /* MSO is only supported for eDP */ ++ if (dp_type == DISPLAYID_VESA_DP_TYPE_EDP) { ++ switch (FIELD_GET(DISPLAYID_VESA_MSO_MODE, vesa->mso)) { ++ default: ++ drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Reserved MSO mode value\n", ++ connector->base.id, connector->name); ++ fallthrough; ++ case 0: ++ info->mso_stream_count = 0; ++ break; ++ case 1: ++ info->mso_stream_count = 2; /* 2 or 4 links */ ++ break; ++ case 2: ++ info->mso_stream_count = 4; /* 4 links */ ++ break; ++ } ++ } ++ ++ if (info->mso_stream_count) { ++ info->mso_pixel_overlap = FIELD_GET(DISPLAYID_VESA_MSO_OVERLAP, vesa->mso); ++ if (info->mso_pixel_overlap > 8) { ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] Reserved MSO pixel overlap value %u\n", ++ connector->base.id, connector->name, ++ info->mso_pixel_overlap); ++ info->mso_pixel_overlap = 8; ++ } ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] MSO stream count %u, pixel overlap %u\n", ++ connector->base.id, connector->name, ++ info->mso_stream_count, info->mso_pixel_overlap); ++ } else { + info->mso_pixel_overlap = 0; ++ } ++ ++ if (block->num_bytes < 7) { ++ /* DSC bpp is optional */ + return; + } + +- info->mso_pixel_overlap = FIELD_GET(DISPLAYID_VESA_MSO_OVERLAP, vesa->mso); +- if (info->mso_pixel_overlap > 8) { +- drm_dbg_kms(connector->dev, +- "[CONNECTOR:%d:%s] Reserved MSO pixel overlap value %u\n", +- connector->base.id, connector->name, +- info->mso_pixel_overlap); +- info->mso_pixel_overlap = 8; +- } ++ info->dp_dsc_bpp_x16 = FIELD_GET(DISPLAYID_VESA_DSC_BPP_INT, vesa->dsc_bpp_int) << 4 | ++ FIELD_GET(DISPLAYID_VESA_DSC_BPP_FRACT, vesa->dsc_bpp_fract); + +- drm_dbg_kms(connector->dev, +- "[CONNECTOR:%d:%s] MSO stream count %u, pixel overlap %u\n", +- connector->base.id, connector->name, +- info->mso_stream_count, info->mso_pixel_overlap); ++ if (info->dp_dsc_bpp_x16 > 0) { ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] DSC bits per pixel " FXP_Q4_FMT "\n", ++ connector->base.id, connector->name, ++ FXP_Q4_ARGS(info->dp_dsc_bpp_x16)); ++ } + } + +-static void drm_update_mso(struct drm_connector *connector, +- const struct drm_edid *drm_edid) ++static void drm_update_vesa_specific_block(struct drm_connector *connector, ++ const struct drm_edid *drm_edid) + { + const struct displayid_block *block; + struct displayid_iter iter; +@@ -6752,7 +6776,7 @@ static void drm_update_mso(struct drm_connector *connector, + displayid_iter_edid_begin(drm_edid, &iter); + displayid_iter_for_each(block, &iter) { + if (block->tag == DATA_BLOCK_2_VENDOR_SPECIFIC) +- drm_parse_vesa_mso_data(connector, block); ++ drm_parse_vesa_specific_block(connector, block); + } + displayid_iter_end(&iter); + } +@@ -6789,6 +6813,7 @@ static void drm_reset_display_info(struct drm_connector *connector) + info->mso_stream_count = 0; + info->mso_pixel_overlap = 0; + info->max_dsc_bpp = 0; ++ info->dp_dsc_bpp_x16 = 0; + + kfree(info->vics); + info->vics = NULL; +@@ -6930,7 +6955,7 @@ static void update_display_info(struct drm_connector *connector, + if (edid->features & DRM_EDID_FEATURE_RGB_YCRCB422) + info->color_formats |= BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422); + +- drm_update_mso(connector, drm_edid); ++ drm_update_vesa_specific_block(connector, drm_edid); + + out: + if (drm_edid_has_internal_quirk(connector, EDID_QUIRK_NON_DESKTOP)) { +@@ -6960,8 +6985,8 @@ static void update_display_info(struct drm_connector *connector, + } + + static struct drm_display_mode *drm_mode_displayid_detailed(struct drm_device *dev, +- const struct displayid_detailed_timings_1 *timings, +- bool type_7) ++ const struct displayid_block *block, ++ const struct displayid_detailed_timings_1 *timings) + { + struct drm_display_mode *mode; + unsigned int pixel_clock = (timings->pixel_clock[0] | +@@ -6977,11 +7002,16 @@ static struct drm_display_mode *drm_mode_displayid_detailed(struct drm_device *d + unsigned int vsync_width = le16_to_cpu(timings->vsw) + 1; + bool hsync_positive = le16_to_cpu(timings->hsync) & (1 << 15); + bool vsync_positive = le16_to_cpu(timings->vsync) & (1 << 15); ++ bool type_7 = block->tag == DATA_BLOCK_2_TYPE_7_DETAILED_TIMING; + + mode = drm_mode_create(dev); + if (!mode) + return NULL; + ++ if (type_7 && FIELD_GET(DISPLAYID_BLOCK_REV, block->rev) >= 1) ++ mode->dsc_passthrough_timings_support = ++ block->rev & DISPLAYID_BLOCK_PASSTHROUGH_TIMINGS_SUPPORT; ++ + /* resolution is kHz for type VII, and 10 kHz for type I */ + mode->clock = type_7 ? pixel_clock : pixel_clock * 10; + mode->hdisplay = hactive; +@@ -7014,7 +7044,6 @@ static int add_displayid_detailed_1_modes(struct drm_connector *connector, + int num_timings; + struct drm_display_mode *newmode; + int num_modes = 0; +- bool type_7 = block->tag == DATA_BLOCK_2_TYPE_7_DETAILED_TIMING; + /* blocks must be multiple of 20 bytes length */ + if (block->num_bytes % 20) + return 0; +@@ -7023,7 +7052,7 @@ static int add_displayid_detailed_1_modes(struct drm_connector *connector, + for (i = 0; i < num_timings; i++) { + struct displayid_detailed_timings_1 *timings = &det->timings[i]; + +- newmode = drm_mode_displayid_detailed(connector->dev, timings, type_7); ++ newmode = drm_mode_displayid_detailed(connector->dev, block, timings); + if (!newmode) + continue; + +@@ -7070,7 +7099,8 @@ static int add_displayid_formula_modes(struct drm_connector *connector, + struct drm_display_mode *newmode; + int num_modes = 0; + bool type_10 = block->tag == DATA_BLOCK_2_TYPE_10_FORMULA_TIMING; +- int timing_size = 6 + ((formula_block->base.rev & 0x70) >> 4); ++ int timing_size = 6 + ++ FIELD_GET(DISPLAYID_BLOCK_DESCRIPTOR_PAYLOAD_BYTES, formula_block->base.rev); + + /* extended blocks are not supported yet */ + if (timing_size != 6) +diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h +--- a/include/drm/drm_connector.h ++++ b/include/drm/drm_connector.h +@@ -939,6 +939,12 @@ struct drm_display_info { + */ + u32 max_dsc_bpp; + ++ /** ++ * @dp_dsc_bpp: DP Display-Stream-Compression (DSC) timing's target ++ * DSC bits per pixel in 6.4 fixed point format. 0 means undefined. ++ */ ++ u16 dp_dsc_bpp_x16; ++ + /** + * @vics: Array of vics_len VICs. Internal to EDID parsing. + */ +diff --git a/include/drm/drm_modes.h b/include/drm/drm_modes.h +--- a/include/drm/drm_modes.h ++++ b/include/drm/drm_modes.h +@@ -417,6 +417,16 @@ struct drm_display_mode { + */ + enum hdmi_picture_aspect picture_aspect_ratio; + ++ /** ++ * @dsc_passthrough_timing_support: ++ * ++ * Indicates whether this mode timing descriptor is supported ++ * with specific target DSC bits per pixel only. ++ * ++ * VESA vendor-specific data block shall exist with the relevant ++ * DSC bits per pixel declaration when this flag is set to true. ++ */ ++ bool dsc_passthrough_timings_support; + }; + + /** diff --git a/pkgbuilds/linux-omarchy/0461-vesa-dsc-passthru-mode-match-fix.patch b/pkgbuilds/linux-omarchy/0461-vesa-dsc-passthru-mode-match-fix.patch new file mode 100644 index 0000000..e27b493 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0461-vesa-dsc-passthru-mode-match-fix.patch @@ -0,0 +1,51 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -7622,6 +7622,7 @@ create_stream_for_sink(struct drm_connector *connector, + struct drm_display_mode saved_mode; + struct drm_display_mode *freesync_mode = NULL; + struct drm_display_mode *dsc_passthru_mode = NULL; ++ unsigned int dsc_passthru_match_flags; + bool native_mode_found = false; + bool recalculate_timing = false; + bool scale = dm_state->scaling != RMX_OFF; +@@ -7688,6 +7689,22 @@ create_stream_for_sink(struct drm_connector *connector, + + mode_refresh = drm_mode_vrefresh(&mode); + ++ dsc_passthru_match_flags = DRM_MODE_MATCH_TIMINGS | ++ DRM_MODE_MATCH_CLOCK | ++ DRM_MODE_MATCH_FLAGS | ++ DRM_MODE_MATCH_3D_FLAGS; ++ if (mode.picture_aspect_ratio) ++ dsc_passthru_match_flags |= DRM_MODE_MATCH_ASPECT_RATIO; ++ ++ list_for_each_entry(dsc_passthru_mode, &connector->modes, head) { ++ if (drm_mode_match(dsc_passthru_mode, &mode, ++ dsc_passthru_match_flags)) { ++ mode.dsc_passthrough_timings_support = ++ dsc_passthru_mode->dsc_passthrough_timings_support; ++ break; ++ } ++ } ++ + if (preferred_mode == NULL) { + /* + * This may not be an error, the use case is when we have no +@@ -7713,16 +7730,6 @@ create_stream_for_sink(struct drm_connector *connector, + } + } + +- list_for_each_entry(dsc_passthru_mode, &connector->modes, head) { +- if (dsc_passthru_mode->hdisplay == mode.hdisplay && +- dsc_passthru_mode->vdisplay == mode.vdisplay && +- drm_mode_vrefresh(dsc_passthru_mode) == mode_refresh) { +- mode.dsc_passthrough_timings_support = +- dsc_passthru_mode->dsc_passthrough_timings_support; +- break; +- } +- } +- + if (recalculate_timing) + drm_mode_set_crtcinfo(&saved_mode, 0); + diff --git a/pkgbuilds/linux-omarchy/0472-amdgpu-userq-post-reset-error.patch b/pkgbuilds/linux-omarchy/0472-amdgpu-userq-post-reset-error.patch new file mode 100644 index 0000000..942772c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0472-amdgpu-userq-post-reset-error.patch @@ -0,0 +1,26 @@ +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +@@ -1426,7 +1426,7 @@ int amdgpu_userq_post_reset(struct amdgpu_device *adev, bool vram_lost) + struct amdgpu_usermode_queue *queue; + const struct amdgpu_userq_funcs *userq_funcs; + unsigned long queue_id; +- int r = 0; ++ int ret = 0, r; + + xa_for_each(&adev->userq_doorbell_xa, queue_id, queue) { + if (queue->state == AMDGPU_USERQ_STATE_HUNG && !vram_lost) { +@@ -1435,11 +1435,12 @@ int amdgpu_userq_post_reset(struct amdgpu_device *adev, bool vram_lost) + r = userq_funcs->map(queue); + if (r) { + dev_err(adev->dev, "Failed to remap queue %ld\n", queue_id); ++ ret = r; + continue; + } + queue->state = AMDGPU_USERQ_STATE_MAPPED; + } + } + +- return r; ++ return ret; + } diff --git a/pkgbuilds/linux-omarchy/0473-i915-ptl-cdclk-sanitize.patch b/pkgbuilds/linux-omarchy/0473-i915-ptl-cdclk-sanitize.patch new file mode 100644 index 0000000..08d39ec --- /dev/null +++ b/pkgbuilds/linux-omarchy/0473-i915-ptl-cdclk-sanitize.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c b/drivers/gpu/drm/i915/display/intel_cdclk.c +--- a/drivers/gpu/drm/i915/display/intel_cdclk.c ++++ b/drivers/gpu/drm/i915/display/intel_cdclk.c +@@ -2366,8 +2366,10 @@ static void bxt_sanitize_cdclk(struct intel_display *display) + * dividers both syncing to an active pipe, or asynchronously + * (PIPE_NONE). + */ +- cdctl &= ~bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); +- cdctl |= bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); ++ if (DISPLAY_VER(display) < 30) { ++ cdctl &= ~bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); ++ cdctl |= bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); ++ } + + if (cdctl != expected) { + if (DISPLAY_VER(display) < 20) { diff --git a/pkgbuilds/linux-omarchy/0474-amd-display-oled-vesa-backlight.patch b/pkgbuilds/linux-omarchy/0474-amd-display-oled-vesa-backlight.patch new file mode 100644 index 0000000..590d8a0 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0474-amd-display-oled-vesa-backlight.patch @@ -0,0 +1,18 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -4160,8 +4160,12 @@ static void update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) + else if (!IS_ERR_OR_NULL(panel_backlight_quirk) && + panel_backlight_quirk->force_pwm) + caps->aux_support = false; +- if (caps->aux_support) +- aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX; ++ if (caps->aux_support) { ++ if (aconnector->dc_link->dpcd_caps.panel_luminance_control) ++ aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_VESA_AUX; ++ else ++ aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX; ++ } + + luminance_range = &conn_base->display_info.luminance_range; + diff --git a/pkgbuilds/linux-omarchy/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch b/pkgbuilds/linux-omarchy/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch new file mode 100644 index 0000000..a58a962 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c +--- a/drivers/gpu/drm/i915/display/intel_dp.c ++++ b/drivers/gpu/drm/i915/display/intel_dp.c +@@ -3219,11 +3219,7 @@ static bool intel_dp_needs_as_sdp(struct intel_dp *intel_dp, + if (drm_dp_is_branch(intel_dp->dpcd)) + return false; + +- if (intel_psr_needs_alpm_aux_less(intel_dp, crtc_state) && +- !intel_psr_pr_async_video_timing_supported(intel_dp)) +- return true; +- +- return intel_vrr_possible(crtc_state); ++ return crtc_state->vrr.enable; + } + + static void intel_dp_compute_as_sdp(struct intel_dp *intel_dp, diff --git a/pkgbuilds/linux-omarchy/0510-sound-updates.patch b/pkgbuilds/linux-omarchy/0510-sound-updates.patch new file mode 100644 index 0000000..29d72a3 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0510-sound-updates.patch @@ -0,0 +1,50784 @@ +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -1752,6 +1752,7 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) + * by the drivers/platform/x86/serial-multi-instantiate.c driver, which + * knows which client device id to use for each resource. + */ ++ {"AWDZ8399", }, + {"BSG1160", }, + {"BSG2150", }, + {"CSC3551", }, +diff --git a/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c b/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c +--- a/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c ++++ b/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c +@@ -147,7 +147,7 @@ static const struct reg_default halo_register_defaults[] = { + { 0x2b805c0, 0 }, /* HALO_SCRATCH1 */ + { 0x2b805c8, 0 }, /* HALO_SCRATCH2 */ + { 0x2b805d0, 0 }, /* HALO_SCRATCH3 */ +- { 0x2b805c8, 0 }, /* HALO_SCRATCH4 */ ++ { 0x2b805d8, 0 }, /* HALO_SCRATCH4 */ + { 0x2bc1000, 0 }, /* HALO_CCM_CORE_CONTROL */ + { 0x2bc7000, 0 }, /* HALO_WDT_CONTROL */ + +diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c +--- a/drivers/platform/x86/serial-multi-instantiate.c ++++ b/drivers/platform/x86/serial-multi-instantiate.c +@@ -307,6 +307,15 @@ static void smi_remove(struct platform_device *pdev) + smi_devs_unregister(smi); + } + ++static const struct smi_node aw88399_hda = { ++ .instances = { ++ { "aw88399-hda", IRQ_RESOURCE_AUTO, 0 }, ++ { "aw88399-hda", IRQ_RESOURCE_AUTO, 0 }, ++ {} ++ }, ++ .bus_type = SMI_AUTO_DETECT, ++}; ++ + static const struct smi_node bsg1160_data = { + .instances = { + { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, +@@ -405,6 +414,7 @@ static const struct smi_node tas2781_hda = { + * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). + */ + static const struct acpi_device_id smi_acpi_ids[] = { ++ { "AWDZ8399", (unsigned long)&aw88399_hda }, + { "BSG1160", (unsigned long)&bsg1160_data }, + { "BSG2150", (unsigned long)&bsg2150_data }, + { "CSC3551", (unsigned long)&cs35l41_hda }, +diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c +--- a/drivers/soundwire/bus_type.c ++++ b/drivers/soundwire/bus_type.c +@@ -105,6 +105,9 @@ static int sdw_bus_probe(struct device *dev) + } + slave->index = ret; + ++ /* Create IRQ mapping now so the driver can get it in probe() */ ++ sdw_irq_create_mapping(slave); ++ + ret = drv->probe(slave, id); + if (ret) { + ida_free(&slave->bus->slave_ida, slave->index); +@@ -117,9 +120,6 @@ static int sdw_bus_probe(struct device *dev) + if (drv->ops && drv->ops->read_prop) + drv->ops->read_prop(slave); + +- if (slave->prop.use_domain_irq) +- sdw_irq_create_mapping(slave); +- + /* init the dynamic sysfs attributes we need */ + ret = sdw_slave_sysfs_dpn_init(slave); + if (ret < 0) +diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +--- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h ++++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +@@ -152,6 +152,10 @@ + #define LPI_MI2S_TX_4 146 + #define SENARY_MI2S_RX 147 + #define SENARY_MI2S_TX 148 ++#define LPI_MI2S_RX_5 149 ++#define LPI_MI2S_TX_5 150 ++#define LPI_MI2S_RX_6 151 ++#define LPI_MI2S_TX_6 152 + + #define LPASS_CLK_ID_PRI_MI2S_IBIT 1 + #define LPASS_CLK_ID_PRI_MI2S_EBIT 2 +@@ -233,11 +237,39 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 70 + ++#define LAPSS_CLK_ID_QAIF_IF0_IBIT 71 ++#define LAPSS_CLK_ID_QAIF_IF0_EBIT 72 ++#define LAPSS_CLK_ID_QAIF_IF1_IBIT 73 ++#define LAPSS_CLK_ID_QAIF_IF1_EBIT 74 ++#define LAPSS_CLK_ID_QAIF_IF2_IBIT 75 ++#define LAPSS_CLK_ID_QAIF_IF2_EBIT 76 ++#define LAPSS_CLK_ID_QAIF_IF3_IBIT 77 ++#define LAPSS_CLK_ID_QAIF_IF3_EBIT 78 ++#define LAPSS_CLK_ID_QAIF_IF4_IBIT 79 ++#define LAPSS_CLK_ID_QAIF_IF4_EBIT 80 ++#define LAPSS_CLK_ID_QAIF_IF5_IBIT 81 ++#define LAPSS_CLK_ID_QAIF_IF5_EBIT 82 ++#define LAPSS_CLK_ID_QAIF_IF6_IBIT 83 ++#define LAPSS_CLK_ID_QAIF_IF6_EBIT 84 ++#define LAPSS_CLK_ID_QAIF_IF7_IBIT 85 ++#define LAPSS_CLK_ID_QAIF_IF7_EBIT 86 ++#define LAPSS_CLK_ID_QAIF_IF8_IBIT 87 ++#define LAPSS_CLK_ID_QAIF_IF8_EBIT 88 ++#define LAPSS_CLK_ID_QAIF_IF9_IBIT 89 ++#define LAPSS_CLK_ID_QAIF_IF9_EBIT 90 ++#define LAPSS_CLK_ID_QAIF_IF10_IBIT 91 ++#define LAPSS_CLK_ID_QAIF_IF10_EBIT 92 ++#define LAPSS_CLK_ID_QAIF_IF11_IBIT 93 ++#define LAPSS_CLK_ID_QAIF_IF11_EBIT 94 ++#define LAPSS_CLK_ID_QAIF_IF12_IBIT 95 ++#define LAPSS_CLK_ID_QAIF_IF12_EBIT 96 ++#define LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 97 ++#define LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 98 ++ + #define LPASS_HW_AVTIMER_VOTE 101 + #define LPASS_HW_MACRO_VOTE 102 + #define LPASS_HW_DCODEC_VOTE 103 +- +-#define Q6AFE_MAX_CLK_ID 104 ++#define LPASS_HW_LPR_VOTE 104 + + #define LPASS_CLK_ATTRIBUTE_INVALID 0x0 + #define LPASS_CLK_ATTRIBUTE_COUPLE_NO 0x1 +diff --git a/include/linux/platform_data/omap-twl4030.h b/include/linux/platform_data/omap-twl4030.h +deleted file mode 100644 +--- a/include/linux/platform_data/omap-twl4030.h ++++ /dev/null +@@ -1,42 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-only */ +-/** +- * omap-twl4030.h - ASoC machine driver for TI SoC based boards with twl4030 +- * codec, header. +- * +- * Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com +- * All rights reserved. +- * +- * Author: Peter Ujfalusi +- */ +- +-#ifndef _OMAP_TWL4030_H_ +-#define _OMAP_TWL4030_H_ +- +-/* To select if only one channel is connected in a stereo port */ +-#define OMAP_TWL4030_LEFT (1 << 0) +-#define OMAP_TWL4030_RIGHT (1 << 1) +- +-struct omap_tw4030_pdata { +- const char *card_name; +- /* Voice port is connected to McBSP3 */ +- bool voice_connected; +- +- /* The driver will parse the connection flags if this flag is set */ +- bool custom_routing; +- /* Flags to indicate connected audio ports. */ +- u8 has_hs; +- u8 has_hf; +- u8 has_predriv; +- u8 has_carkit; +- bool has_ear; +- +- bool has_mainmic; +- bool has_submic; +- bool has_hsmic; +- bool has_carkitmic; +- bool has_digimic0; +- bool has_digimic1; +- u8 has_linein; +-}; +- +-#endif /* _OMAP_TWL4030_H_ */ +diff --git a/include/sound/ac97/codec.h b/include/sound/ac97/codec.h +--- a/include/sound/ac97/codec.h ++++ b/include/sound/ac97/codec.h +@@ -33,7 +33,7 @@ struct ac97_id { + }; + + /** +- * ac97_codec_device - a ac97 codec ++ * struct ac97_codec_device - a ac97 codec + * @dev: the core device + * @vendor_id: the vendor_id of the codec, as sensed on the AC-link + * @num: the codec number, 0 is primary, 1 is first slave, etc ... +@@ -53,7 +53,7 @@ struct ac97_codec_device { + }; + + /** +- * ac97_codec_driver - a ac97 codec driver ++ * struct ac97_codec_driver - a ac97 codec driver + * @driver: the device driver structure + * @probe: the function called when a ac97_codec_device is matched + * @remove: the function called when the device is unbound/removed +diff --git a/include/sound/asequencer.h b/include/sound/asequencer.h +--- a/include/sound/asequencer.h ++++ b/include/sound/asequencer.h +@@ -11,9 +11,6 @@ + #include + #include + +-/* helper macro */ +-#define snd_seq_event_bounce_ext_data(ev) ((void*)((char *)(ev)->data.ext.ptr + sizeof(struct snd_seq_event_bounce))) +- + /* + * type check macros + */ +diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h +new file mode 100644 +--- /dev/null ++++ b/include/sound/aw88399.h +@@ -0,0 +1,621 @@ ++/* SPDX-License-Identifier: GPL-2.0-only ++ * ++ * linux/sound/aw88399.h -- Platform data for AW88399 ++ * ++ * Copyright (c) 2023 AWINIC Technology CO., LTD ++ * ++ * Author: Weidong Wang ++ */ ++ ++#ifndef __SOUND_AW88399_H ++#define __SOUND_AW88399_H ++ ++#include ++#include ++ ++/* registers list */ ++#define AW88399_ID_REG (0x00) ++#define AW88399_SYSST_REG (0x01) ++#define AW88399_SYSINT_REG (0x02) ++#define AW88399_SYSINTM_REG (0x03) ++#define AW88399_SYSCTRL_REG (0x04) ++#define AW88399_SYSCTRL2_REG (0x05) ++#define AW88399_I2SCTRL1_REG (0x06) ++#define AW88399_I2SCTRL2_REG (0x07) ++#define AW88399_I2SCTRL3_REG (0x08) ++#define AW88399_DACCFG1_REG (0x09) ++#define AW88399_DACCFG2_REG (0x0A) ++#define AW88399_DACCFG3_REG (0x0B) ++#define AW88399_DACCFG4_REG (0x0C) ++#define AW88399_DACCFG5_REG (0x0D) ++#define AW88399_DACCFG6_REG (0x0E) ++#define AW88399_DACCFG7_REG (0x0F) ++#define AW88399_MPDCFG1_REG (0x10) ++#define AW88399_MPDCFG2_REG (0x11) ++#define AW88399_MPDCFG3_REG (0x12) ++#define AW88399_MPDCFG4_REG (0x13) ++#define AW88399_PWMCTRL1_REG (0x14) ++#define AW88399_PWMCTRL2_REG (0x15) ++#define AW88399_PWMCTRL3_REG (0x16) ++#define AW88399_I2SCFG1_REG (0x17) ++#define AW88399_DBGCTRL_REG (0x18) ++#define AW88399_HAGCST_REG (0x20) ++#define AW88399_VBAT_REG (0x21) ++#define AW88399_TEMP_REG (0x22) ++#define AW88399_PVDD_REG (0x23) ++#define AW88399_ISNDAT_REG (0x24) ++#define AW88399_VSNDAT_REG (0x25) ++#define AW88399_I2SINT_REG (0x26) ++#define AW88399_I2SCAPCNT_REG (0x27) ++#define AW88399_ANASTA1_REG (0x28) ++#define AW88399_ANASTA2_REG (0x29) ++#define AW88399_ANASTA3_REG (0x2A) ++#define AW88399_TESTDET_REG (0x2B) ++#define AW88399_DSMCFG1_REG (0x30) ++#define AW88399_DSMCFG2_REG (0x31) ++#define AW88399_DSMCFG3_REG (0x32) ++#define AW88399_DSMCFG4_REG (0x33) ++#define AW88399_DSMCFG5_REG (0x34) ++#define AW88399_DSMCFG6_REG (0x35) ++#define AW88399_DSMCFG7_REG (0x36) ++#define AW88399_DSMCFG8_REG (0x37) ++#define AW88399_TESTIN_REG (0x38) ++#define AW88399_TESTOUT_REG (0x39) ++#define AW88399_MEMTEST_REG (0x3A) ++#define AW88399_VSNCTRL1_REG (0x3B) ++#define AW88399_ISNCTRL1_REG (0x3C) ++#define AW88399_ISNCTRL2_REG (0x3D) ++#define AW88399_DSPMADD_REG (0x40) ++#define AW88399_DSPMDAT_REG (0x41) ++#define AW88399_WDT_REG (0x42) ++#define AW88399_ACR1_REG (0x43) ++#define AW88399_ACR2_REG (0x44) ++#define AW88399_ASR1_REG (0x45) ++#define AW88399_ASR2_REG (0x46) ++#define AW88399_DSPCFG_REG (0x47) ++#define AW88399_ASR3_REG (0x48) ++#define AW88399_ASR4_REG (0x49) ++#define AW88399_DSPVCALB_REG (0x4A) ++#define AW88399_CRCCTRL_REG (0x4B) ++#define AW88399_DSPDBG1_REG (0x4C) ++#define AW88399_DSPDBG2_REG (0x4D) ++#define AW88399_DSPDBG3_REG (0x4E) ++#define AW88399_PLLCTRL1_REG (0x50) ++#define AW88399_PLLCTRL2_REG (0x51) ++#define AW88399_PLLCTRL3_REG (0x52) ++#define AW88399_CDACTRL1_REG (0x53) ++#define AW88399_CDACTRL2_REG (0x54) ++#define AW88399_CDACTRL3_REG (0x55) ++#define AW88399_SADCCTRL1_REG (0x56) ++#define AW88399_SADCCTRL2_REG (0x57) ++#define AW88399_BOPCTRL1_REG (0x58) ++#define AW88399_BOPCTRL2_REG (0x5A) ++#define AW88399_BOPCTRL3_REG (0x5B) ++#define AW88399_BOPCTRL4_REG (0x5C) ++#define AW88399_BOPCTRL5_REG (0x5D) ++#define AW88399_BOPCTRL6_REG (0x5E) ++#define AW88399_BOPCTRL7_REG (0x5F) ++#define AW88399_BSTCTRL1_REG (0x60) ++#define AW88399_BSTCTRL2_REG (0x61) ++#define AW88399_BSTCTRL3_REG (0x62) ++#define AW88399_BSTCTRL4_REG (0x63) ++#define AW88399_BSTCTRL5_REG (0x64) ++#define AW88399_BSTCTRL6_REG (0x65) ++#define AW88399_BSTCTRL7_REG (0x66) ++#define AW88399_BSTCTRL8_REG (0x67) ++#define AW88399_BSTCTRL9_REG (0x68) ++#define AW88399_BSTCTRL10_REG (0x69) ++#define AW88399_CPCTRL_REG (0x6A) ++#define AW88399_EFWH_REG (0x6C) ++#define AW88399_EFWM2_REG (0x6D) ++#define AW88399_EFWM1_REG (0x6E) ++#define AW88399_EFWL_REG (0x6F) ++#define AW88399_TESTCTRL1_REG (0x70) ++#define AW88399_TESTCTRL2_REG (0x71) ++#define AW88399_EFCTRL1_REG (0x72) ++#define AW88399_EFCTRL2_REG (0x73) ++#define AW88399_EFRH4_REG (0x74) ++#define AW88399_EFRH3_REG (0x75) ++#define AW88399_EFRH2_REG (0x76) ++#define AW88399_EFRH1_REG (0x77) ++#define AW88399_EFRL4_REG (0x78) ++#define AW88399_EFRL3_REG (0x79) ++#define AW88399_EFRL2_REG (0x7A) ++#define AW88399_EFRL1_REG (0x7B) ++#define AW88399_TM_REG (0x7C) ++#define AW88399_TM2_REG (0x7D) ++ ++#define AW88399_REG_MAX (0x7E) ++#define AW88399_MUTE_VOL (1023) ++ ++#define AW88399_DSP_CFG_ADDR (0x9B00) ++#define AW88399_DSP_REG_CFG_ADPZ_RA (0x9B68) ++#define AW88399_DSP_FW_ADDR (0x8980) ++#define AW88399_DSP_ROM_CHECK_ADDR (0x1F40) ++#define AW88399_DSP_ROM_CHECK_DATA (0x4638) ++ ++#define AW88399_CALI_RE_HBITS_MASK (~(0xFFFF0000)) ++#define AW88399_CALI_RE_HBITS_SHIFT (16) ++ ++#define AW88399_CALI_RE_LBITS_MASK (~(0xFFFF)) ++#define AW88399_CALI_RE_LBITS_SHIFT (0) ++ ++#define AW88399_I2STXEN_START_BIT (9) ++#define AW88399_I2STXEN_BITS_LEN (1) ++#define AW88399_I2STXEN_MASK \ ++ (~(((1<> (shift)) ++#define AW88399_SHOW_RE_TO_DSP_RE(re, shift) (((re) << shift) / (1000)) ++#define AW88399_CRC_CHECK_PASS_VAL (0x4) ++ ++#define AW88399_CRC_CFG_BASE_ADDR (0xD80) ++#define AW88399_CRC_FW_BASE_ADDR (0x4C0) ++#define AW88399_ACF_FILE "aw88399_acf.bin" ++#define AW88399_DEV_SYSST_CHECK_MAX (10) ++#define AW88399_CHIP_ID 0x2183 ++ ++#define AW88399_START_RETRIES (5) ++#define AW88399_START_WORK_DELAY_MS (0) ++ ++enum { ++ AW_EF_AND_CHECK = 0, ++ AW_EF_OR_CHECK, ++}; ++ ++enum { ++ AW88399_DEV_VDSEL_DAC = 0, ++ AW88399_DEV_VDSEL_VSENSE = 32, ++}; ++ ++enum { ++ AW88399_DSP_CRC_NA = 0, ++ AW88399_DSP_CRC_OK = 1, ++}; ++ ++enum { ++ AW88399_DSP_FW_UPDATE_OFF = 0, ++ AW88399_DSP_FW_UPDATE_ON = 1, ++}; ++ ++enum { ++ AW88399_FORCE_UPDATE_OFF = 0, ++ AW88399_FORCE_UPDATE_ON = 1, ++}; ++ ++enum { ++ AW88399_1000_US = 1000, ++ AW88399_2000_US = 2000, ++ AW88399_3000_US = 3000, ++ AW88399_4000_US = 4000, ++}; ++ ++enum AW88399_DEV_STATUS { ++ AW88399_DEV_PW_OFF = 0, ++ AW88399_DEV_PW_ON, ++}; ++ ++enum AW88399_DEV_FW_STATUS { ++ AW88399_DEV_FW_FAILED = 0, ++ AW88399_DEV_FW_OK, ++}; ++ ++enum AW88399_DEV_MEMCLK { ++ AW88399_DEV_MEMCLK_OSC = 0, ++ AW88399_DEV_MEMCLK_PLL = 1, ++}; ++ ++enum AW88399_DEV_DSP_CFG { ++ AW88399_DEV_DSP_WORK = 0, ++ AW88399_DEV_DSP_BYPASS = 1, ++}; ++ ++enum { ++ AW88399_NOT_RCV_MODE = 0, ++ AW88399_RCV_MODE = 1, ++}; ++ ++enum { ++ AW88399_SYNC_START = 0, ++ AW88399_ASYNC_START, ++}; ++ ++struct aw_device; ++struct aw_container; ++struct aw_cali_desc; ++struct gpio_desc; ++struct i2c_client; ++struct regmap; ++ ++struct aw88399 { ++ struct aw_device *aw_pa; ++ struct mutex lock; ++ struct gpio_desc *reset_gpio; ++ struct delayed_work start_work; ++ struct regmap *regmap; ++ struct aw_container *aw_cfg; ++ ++ unsigned int check_val; ++ unsigned int crc_init_val; ++ unsigned int vcalb_init_val; ++ unsigned int dither_st; ++ bool bsts_unreliable; ++ bool fw_needs_reload; ++}; ++ ++int aw_dev_check_syspll(struct aw_device *aw_dev); ++void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable); ++int aw_dev_get_dsp_status(struct aw_device *aw_dev); ++int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value); ++int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc); ++int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name); ++void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute); ++void aw88399_dev_set_channel(struct aw88399 *aw88399, int channel); ++void aw88399_hw_reset(struct aw88399 *aw88399); ++int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap); ++extern const struct regmap_config aw88399_remap_config; ++int aw88399_request_firmware_file(struct aw88399 *aw88399); ++void aw88399_start(struct aw88399 *aw88399, bool sync_start); ++void aw88399_startup_work(struct work_struct *work); ++int aw88399_stop(struct aw_device *aw_dev); ++ ++#endif /* __SOUND_AW88399_H */ +diff --git a/include/sound/control.h b/include/sound/control.h +--- a/include/sound/control.h ++++ b/include/sound/control.h +@@ -7,6 +7,7 @@ + * Copyright (c) by Jaroslav Kysela + */ + ++#include + #include + #include + #include +@@ -167,6 +168,8 @@ snd_ctl_find_id_mixer(struct snd_card *card, const char *name) + + int snd_ctl_create(struct snd_card *card); + ++extern struct rw_semaphore snd_ioctl_rwsem; ++ + int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn); + int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn); + #ifdef CONFIG_COMPAT +diff --git a/include/sound/core.h b/include/sound/core.h +--- a/include/sound/core.h ++++ b/include/sound/core.h +@@ -108,8 +108,8 @@ struct snd_card { + char longname[80]; /* name of this soundcard */ + char irq_descr[32]; /* Interrupt description */ + char mixername[80]; /* mixer name */ +- char components[128]; /* card components delimited with +- space */ ++ char *components; /* card components, space-delimited */ ++ unsigned int components_alloc_size; /* current allocation size of components */ + struct module *module; /* top-level module */ + + void *private_data; /* private data for soundcard */ +diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h +--- a/include/sound/cs35l56.h ++++ b/include/sound/cs35l56.h +@@ -417,7 +417,6 @@ void cs35l56_wait_control_port_ready(void); + void cs35l56_wait_min_reset_pulse(void); + void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire); + int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq); +-irqreturn_t cs35l56_irq(int irq, void *data); + int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base); + int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base); + int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire); +diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h +--- a/include/sound/dmaengine_pcm.h ++++ b/include/sound/dmaengine_pcm.h +@@ -175,12 +175,6 @@ int snd_dmaengine_pcm_prepare_slave_config(struct snd_pcm_substream *substream, + struct dmaengine_pcm { + struct dma_chan *chan[SNDRV_PCM_STREAM_LAST + 1]; + const struct snd_dmaengine_pcm_config *config; +- struct snd_soc_component component; + unsigned int flags; + }; +- +-static inline struct dmaengine_pcm *soc_component_to_pcm(struct snd_soc_component *p) +-{ +- return container_of(p, struct dmaengine_pcm, component); +-} + #endif +diff --git a/include/sound/hda-mlink.h b/include/sound/hda-mlink.h +--- a/include/sound/hda-mlink.h ++++ b/include/sound/hda-mlink.h +@@ -9,6 +9,22 @@ + struct hdac_bus; + struct hdac_ext_link; + ++/** ++ * enum hda_bus_ml_link_type - mlink link type, used by SOF link DMA ++ * allocator constraints (see struct sof_intel_hda_dev). ++ * ++ * @HDA_BUS_ML_LINK_HDA: non-alt link, i.e. HDA codec or iDisp ++ * @HDA_BUS_ML_LINK_SDW: alt link, SoundWire ++ * @HDA_BUS_ML_LINK_UAOL: alt link, USB Audio Offload ++ * @HDA_BUS_ML_LINK_OTHER: alt link, SSP or DMIC ++ */ ++enum hda_bus_ml_link_type { ++ HDA_BUS_ML_LINK_HDA, ++ HDA_BUS_ML_LINK_SDW, ++ HDA_BUS_ML_LINK_UAOL, ++ HDA_BUS_ML_LINK_OTHER, ++}; ++ + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK) + + int hda_bus_ml_init(struct hdac_bus *bus); +@@ -49,11 +65,12 @@ int hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num); + int hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y, + int channel_mask, int stream_id, int dir); + +-void hda_bus_ml_put_all(struct hdac_bus *bus); + void hda_bus_ml_reset_losidv(struct hdac_bus *bus); + int hda_bus_ml_resume(struct hdac_bus *bus); + int hda_bus_ml_suspend(struct hdac_bus *bus); + ++enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink); ++ + struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus); + struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus); + struct hdac_ext_link *hdac_bus_eml_sdw_get_hlink(struct hdac_bus *bus); +@@ -169,11 +186,13 @@ hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y, + return 0; + } + +-static inline void hda_bus_ml_put_all(struct hdac_bus *bus) { } + static inline void hda_bus_ml_reset_losidv(struct hdac_bus *bus) { } + static inline int hda_bus_ml_resume(struct hdac_bus *bus) { return 0; } + static inline int hda_bus_ml_suspend(struct hdac_bus *bus) { return 0; } + ++static inline enum hda_bus_ml_link_type ++hda_bus_ml_link_get_type(struct hdac_ext_link *hlink) { return HDA_BUS_ML_LINK_HDA; } ++ + static inline struct hdac_ext_link * + hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus) { return NULL; } + +diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h +--- a/include/sound/hda_codec.h ++++ b/include/sound/hda_codec.h +@@ -256,6 +256,7 @@ struct hda_codec { + unsigned int link_down_at_suspend:1; /* link down at runtime suspend */ + unsigned int relaxed_resume:1; /* don't resume forcibly for jack */ + unsigned int forced_resume:1; /* forced resume for jack */ ++ unsigned int acomp_requested_resume:1; /* resume requested by acomp */ + unsigned int no_stream_clean_at_suspend:1; /* do not clean streams at suspend */ + unsigned int ctl_dev_id:1; /* old control element id build behaviour */ + unsigned int eld_jack_detect:1; /* Machine jack-detection by ELD */ +diff --git a/include/sound/hda_regmap.h b/include/sound/hda_regmap.h +--- a/include/sound/hda_regmap.h ++++ b/include/sound/hda_regmap.h +@@ -69,11 +69,14 @@ void snd_hdac_regmap_sync(struct hdac_device *codec); + + /** + * snd_hdac_regmap_write - Write a verb with caching ++ * @codec: HD-audio codec base device + * @nid: codec NID +- * @reg: verb to write ++ * @verb: verb to write + * @val: value to write + * + * For writing an amp value, use snd_hdac_regmap_update_amp(). ++ * ++ * Returns: %0 if successful or a negative error code. + */ + static inline int + snd_hdac_regmap_write(struct hdac_device *codec, hda_nid_t nid, +@@ -85,13 +88,16 @@ snd_hdac_regmap_write(struct hdac_device *codec, hda_nid_t nid, + } + + /** +- * snd_hda_regmap_update - Update a verb value with caching ++ * snd_hdac_regmap_update - Update a verb value with caching ++ * @codec: HD-audio codec + * @nid: codec NID + * @verb: verb to update + * @mask: bit mask to update + * @val: value to update + * + * For updating an amp value, use snd_hdac_regmap_update_amp(). ++ * ++ * Returns: %0 if successful or a negative error code. + */ + static inline int + snd_hdac_regmap_update(struct hdac_device *codec, hda_nid_t nid, +@@ -104,12 +110,15 @@ snd_hdac_regmap_update(struct hdac_device *codec, hda_nid_t nid, + } + + /** +- * snd_hda_regmap_read - Read a verb with caching ++ * snd_hdac_regmap_read - Read a verb with caching ++ * @codec: HD-audio codec + * @nid: codec NID + * @verb: verb to read + * @val: pointer to store the value + * + * For reading an amp value, use snd_hda_regmap_get_amp(). ++ * ++ * Returns: %0 if successful or a negative error code. + */ + static inline int + snd_hdac_regmap_read(struct hdac_device *codec, hda_nid_t nid, +@@ -125,12 +134,12 @@ snd_hdac_regmap_read(struct hdac_device *codec, hda_nid_t nid, + * @codec: HD-audio codec + * @nid: NID to read the AMP value + * @ch: channel (left=0 or right=1) +- * @direction: #HDA_INPUT or #HDA_OUTPUT +- * @index: the index value (only for input direction) +- * @val: the pointer to store the value ++ * @dir: #HDA_INPUT or #HDA_OUTPUT ++ * @idx: the index value (only for input direction) + * + * Read AMP value. The volume is between 0 to 0x7f, 0x80 = mute bit. +- * Returns the value or a negative error. ++ * ++ * Returns: the value or a negative error. + */ + static inline int + snd_hdac_regmap_get_amp(struct hdac_device *codec, hda_nid_t nid, +@@ -148,13 +157,14 @@ snd_hdac_regmap_get_amp(struct hdac_device *codec, hda_nid_t nid, + * @codec: HD-audio codec + * @nid: NID to read the AMP value + * @ch: channel (left=0 or right=1) +- * @direction: #HDA_INPUT or #HDA_OUTPUT ++ * @dir: #HDA_INPUT or #HDA_OUTPUT + * @idx: the index value (only for input direction) + * @mask: bit mask to set + * @val: the bits value to set + * + * Update the AMP value with a bit mask. +- * Returns 0 if the value is unchanged, 1 if changed, or a negative error. ++ * ++ * Returns: 0 if the value is unchanged, 1 if changed, or a negative error. + */ + static inline int + snd_hdac_regmap_update_amp(struct hdac_device *codec, hda_nid_t nid, +@@ -169,13 +179,12 @@ snd_hdac_regmap_update_amp(struct hdac_device *codec, hda_nid_t nid, + * snd_hdac_regmap_get_amp_stereo - Read stereo AMP values + * @codec: HD-audio codec + * @nid: NID to read the AMP value +- * @ch: channel (left=0 or right=1) +- * @direction: #HDA_INPUT or #HDA_OUTPUT +- * @index: the index value (only for input direction) +- * @val: the pointer to store the value ++ * @dir: #HDA_INPUT or #HDA_OUTPUT ++ * @idx: the index value (only for input direction) + * + * Read stereo AMP values. The lower byte is left, the upper byte is right. +- * Returns the value or a negative error. ++ * ++ * Returns: the value or a negative error. + */ + static inline int + snd_hdac_regmap_get_amp_stereo(struct hdac_device *codec, hda_nid_t nid, +@@ -192,14 +201,15 @@ snd_hdac_regmap_get_amp_stereo(struct hdac_device *codec, hda_nid_t nid, + * snd_hdac_regmap_update_amp_stereo - update the stereo AMP value + * @codec: HD-audio codec + * @nid: NID to read the AMP value +- * @direction: #HDA_INPUT or #HDA_OUTPUT ++ * @dir: #HDA_INPUT or #HDA_OUTPUT + * @idx: the index value (only for input direction) + * @mask: bit mask to set + * @val: the bits value to set + * + * Update the stereo AMP value with a bit mask. + * The lower byte is left, the upper byte is right. +- * Returns 0 if the value is unchanged, 1 if changed, or a negative error. ++ * ++ * Returns: 0 if the value is unchanged, 1 if changed, or a negative error. + */ + static inline int + snd_hdac_regmap_update_amp_stereo(struct hdac_device *codec, hda_nid_t nid, +diff --git a/include/sound/pcm.h b/include/sound/pcm.h +--- a/include/sound/pcm.h ++++ b/include/sound/pcm.h +@@ -145,61 +145,60 @@ struct snd_pcm_ops { + #define SNDRV_PCM_RATE_8000_768000 (SNDRV_PCM_RATE_8000_384000|\ + SNDRV_PCM_RATE_705600|\ + SNDRV_PCM_RATE_768000) +-#define _SNDRV_PCM_FMTBIT(fmt) (1ULL << (__force int)SNDRV_PCM_FORMAT_##fmt) +-#define SNDRV_PCM_FMTBIT_S8 _SNDRV_PCM_FMTBIT(S8) +-#define SNDRV_PCM_FMTBIT_U8 _SNDRV_PCM_FMTBIT(U8) +-#define SNDRV_PCM_FMTBIT_S16_LE _SNDRV_PCM_FMTBIT(S16_LE) +-#define SNDRV_PCM_FMTBIT_S16_BE _SNDRV_PCM_FMTBIT(S16_BE) +-#define SNDRV_PCM_FMTBIT_U16_LE _SNDRV_PCM_FMTBIT(U16_LE) +-#define SNDRV_PCM_FMTBIT_U16_BE _SNDRV_PCM_FMTBIT(U16_BE) +-#define SNDRV_PCM_FMTBIT_S24_LE _SNDRV_PCM_FMTBIT(S24_LE) +-#define SNDRV_PCM_FMTBIT_S24_BE _SNDRV_PCM_FMTBIT(S24_BE) +-#define SNDRV_PCM_FMTBIT_U24_LE _SNDRV_PCM_FMTBIT(U24_LE) +-#define SNDRV_PCM_FMTBIT_U24_BE _SNDRV_PCM_FMTBIT(U24_BE) ++#define SNDRV_PCM_FMTBIT_S8 (1ULL << SNDRV_PCM_FORMAT_S8) ++#define SNDRV_PCM_FMTBIT_U8 (1ULL << SNDRV_PCM_FORMAT_U8) ++#define SNDRV_PCM_FMTBIT_S16_LE (1ULL << SNDRV_PCM_FORMAT_S16_LE) ++#define SNDRV_PCM_FMTBIT_S16_BE (1ULL << SNDRV_PCM_FORMAT_S16_BE) ++#define SNDRV_PCM_FMTBIT_U16_LE (1ULL << SNDRV_PCM_FORMAT_U16_LE) ++#define SNDRV_PCM_FMTBIT_U16_BE (1ULL << SNDRV_PCM_FORMAT_U16_BE) ++#define SNDRV_PCM_FMTBIT_S24_LE (1ULL << SNDRV_PCM_FORMAT_S24_LE) ++#define SNDRV_PCM_FMTBIT_S24_BE (1ULL << SNDRV_PCM_FORMAT_S24_BE) ++#define SNDRV_PCM_FMTBIT_U24_LE (1ULL << SNDRV_PCM_FORMAT_U24_LE) ++#define SNDRV_PCM_FMTBIT_U24_BE (1ULL << SNDRV_PCM_FORMAT_U24_BE) + // For S32/U32 formats, 'msbits' hardware parameter is often used to deliver information about the + // available bit count in most significant bit. It's for the case of so-called 'left-justified' or + // `right-padding` sample which has less width than 32 bit. +-#define SNDRV_PCM_FMTBIT_S32_LE _SNDRV_PCM_FMTBIT(S32_LE) +-#define SNDRV_PCM_FMTBIT_S32_BE _SNDRV_PCM_FMTBIT(S32_BE) +-#define SNDRV_PCM_FMTBIT_U32_LE _SNDRV_PCM_FMTBIT(U32_LE) +-#define SNDRV_PCM_FMTBIT_U32_BE _SNDRV_PCM_FMTBIT(U32_BE) +-#define SNDRV_PCM_FMTBIT_FLOAT_LE _SNDRV_PCM_FMTBIT(FLOAT_LE) +-#define SNDRV_PCM_FMTBIT_FLOAT_BE _SNDRV_PCM_FMTBIT(FLOAT_BE) +-#define SNDRV_PCM_FMTBIT_FLOAT64_LE _SNDRV_PCM_FMTBIT(FLOAT64_LE) +-#define SNDRV_PCM_FMTBIT_FLOAT64_BE _SNDRV_PCM_FMTBIT(FLOAT64_BE) +-#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE _SNDRV_PCM_FMTBIT(IEC958_SUBFRAME_LE) +-#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE _SNDRV_PCM_FMTBIT(IEC958_SUBFRAME_BE) +-#define SNDRV_PCM_FMTBIT_MU_LAW _SNDRV_PCM_FMTBIT(MU_LAW) +-#define SNDRV_PCM_FMTBIT_A_LAW _SNDRV_PCM_FMTBIT(A_LAW) +-#define SNDRV_PCM_FMTBIT_IMA_ADPCM _SNDRV_PCM_FMTBIT(IMA_ADPCM) +-#define SNDRV_PCM_FMTBIT_MPEG _SNDRV_PCM_FMTBIT(MPEG) +-#define SNDRV_PCM_FMTBIT_GSM _SNDRV_PCM_FMTBIT(GSM) +-#define SNDRV_PCM_FMTBIT_S20_LE _SNDRV_PCM_FMTBIT(S20_LE) +-#define SNDRV_PCM_FMTBIT_U20_LE _SNDRV_PCM_FMTBIT(U20_LE) +-#define SNDRV_PCM_FMTBIT_S20_BE _SNDRV_PCM_FMTBIT(S20_BE) +-#define SNDRV_PCM_FMTBIT_U20_BE _SNDRV_PCM_FMTBIT(U20_BE) +-#define SNDRV_PCM_FMTBIT_SPECIAL _SNDRV_PCM_FMTBIT(SPECIAL) +-#define SNDRV_PCM_FMTBIT_S24_3LE _SNDRV_PCM_FMTBIT(S24_3LE) +-#define SNDRV_PCM_FMTBIT_U24_3LE _SNDRV_PCM_FMTBIT(U24_3LE) +-#define SNDRV_PCM_FMTBIT_S24_3BE _SNDRV_PCM_FMTBIT(S24_3BE) +-#define SNDRV_PCM_FMTBIT_U24_3BE _SNDRV_PCM_FMTBIT(U24_3BE) +-#define SNDRV_PCM_FMTBIT_S20_3LE _SNDRV_PCM_FMTBIT(S20_3LE) +-#define SNDRV_PCM_FMTBIT_U20_3LE _SNDRV_PCM_FMTBIT(U20_3LE) +-#define SNDRV_PCM_FMTBIT_S20_3BE _SNDRV_PCM_FMTBIT(S20_3BE) +-#define SNDRV_PCM_FMTBIT_U20_3BE _SNDRV_PCM_FMTBIT(U20_3BE) +-#define SNDRV_PCM_FMTBIT_S18_3LE _SNDRV_PCM_FMTBIT(S18_3LE) +-#define SNDRV_PCM_FMTBIT_U18_3LE _SNDRV_PCM_FMTBIT(U18_3LE) +-#define SNDRV_PCM_FMTBIT_S18_3BE _SNDRV_PCM_FMTBIT(S18_3BE) +-#define SNDRV_PCM_FMTBIT_U18_3BE _SNDRV_PCM_FMTBIT(U18_3BE) +-#define SNDRV_PCM_FMTBIT_G723_24 _SNDRV_PCM_FMTBIT(G723_24) +-#define SNDRV_PCM_FMTBIT_G723_24_1B _SNDRV_PCM_FMTBIT(G723_24_1B) +-#define SNDRV_PCM_FMTBIT_G723_40 _SNDRV_PCM_FMTBIT(G723_40) +-#define SNDRV_PCM_FMTBIT_G723_40_1B _SNDRV_PCM_FMTBIT(G723_40_1B) +-#define SNDRV_PCM_FMTBIT_DSD_U8 _SNDRV_PCM_FMTBIT(DSD_U8) +-#define SNDRV_PCM_FMTBIT_DSD_U16_LE _SNDRV_PCM_FMTBIT(DSD_U16_LE) +-#define SNDRV_PCM_FMTBIT_DSD_U32_LE _SNDRV_PCM_FMTBIT(DSD_U32_LE) +-#define SNDRV_PCM_FMTBIT_DSD_U16_BE _SNDRV_PCM_FMTBIT(DSD_U16_BE) +-#define SNDRV_PCM_FMTBIT_DSD_U32_BE _SNDRV_PCM_FMTBIT(DSD_U32_BE) ++#define SNDRV_PCM_FMTBIT_S32_LE (1ULL << SNDRV_PCM_FORMAT_S32_LE) ++#define SNDRV_PCM_FMTBIT_S32_BE (1ULL << SNDRV_PCM_FORMAT_S32_BE) ++#define SNDRV_PCM_FMTBIT_U32_LE (1ULL << SNDRV_PCM_FORMAT_U32_LE) ++#define SNDRV_PCM_FMTBIT_U32_BE (1ULL << SNDRV_PCM_FORMAT_U32_BE) ++#define SNDRV_PCM_FMTBIT_FLOAT_LE (1ULL << SNDRV_PCM_FORMAT_FLOAT_LE) ++#define SNDRV_PCM_FMTBIT_FLOAT_BE (1ULL << SNDRV_PCM_FORMAT_FLOAT_BE) ++#define SNDRV_PCM_FMTBIT_FLOAT64_LE (1ULL << SNDRV_PCM_FORMAT_FLOAT64_LE) ++#define SNDRV_PCM_FMTBIT_FLOAT64_BE (1ULL << SNDRV_PCM_FORMAT_FLOAT64_BE) ++#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE (1ULL << SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE) ++#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE (1ULL << SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE) ++#define SNDRV_PCM_FMTBIT_MU_LAW (1ULL << SNDRV_PCM_FORMAT_MU_LAW) ++#define SNDRV_PCM_FMTBIT_A_LAW (1ULL << SNDRV_PCM_FORMAT_A_LAW) ++#define SNDRV_PCM_FMTBIT_IMA_ADPCM (1ULL << SNDRV_PCM_FORMAT_IMA_ADPCM) ++#define SNDRV_PCM_FMTBIT_MPEG (1ULL << SNDRV_PCM_FORMAT_MPEG) ++#define SNDRV_PCM_FMTBIT_GSM (1ULL << SNDRV_PCM_FORMAT_GSM) ++#define SNDRV_PCM_FMTBIT_S20_LE (1ULL << SNDRV_PCM_FORMAT_S20_LE) ++#define SNDRV_PCM_FMTBIT_U20_LE (1ULL << SNDRV_PCM_FORMAT_U20_LE) ++#define SNDRV_PCM_FMTBIT_S20_BE (1ULL << SNDRV_PCM_FORMAT_S20_BE) ++#define SNDRV_PCM_FMTBIT_U20_BE (1ULL << SNDRV_PCM_FORMAT_U20_BE) ++#define SNDRV_PCM_FMTBIT_SPECIAL (1ULL << SNDRV_PCM_FORMAT_SPECIAL) ++#define SNDRV_PCM_FMTBIT_S24_3LE (1ULL << SNDRV_PCM_FORMAT_S24_3LE) ++#define SNDRV_PCM_FMTBIT_U24_3LE (1ULL << SNDRV_PCM_FORMAT_U24_3LE) ++#define SNDRV_PCM_FMTBIT_S24_3BE (1ULL << SNDRV_PCM_FORMAT_S24_3BE) ++#define SNDRV_PCM_FMTBIT_U24_3BE (1ULL << SNDRV_PCM_FORMAT_U24_3BE) ++#define SNDRV_PCM_FMTBIT_S20_3LE (1ULL << SNDRV_PCM_FORMAT_S20_3LE) ++#define SNDRV_PCM_FMTBIT_U20_3LE (1ULL << SNDRV_PCM_FORMAT_U20_3LE) ++#define SNDRV_PCM_FMTBIT_S20_3BE (1ULL << SNDRV_PCM_FORMAT_S20_3BE) ++#define SNDRV_PCM_FMTBIT_U20_3BE (1ULL << SNDRV_PCM_FORMAT_U20_3BE) ++#define SNDRV_PCM_FMTBIT_S18_3LE (1ULL << SNDRV_PCM_FORMAT_S18_3LE) ++#define SNDRV_PCM_FMTBIT_U18_3LE (1ULL << SNDRV_PCM_FORMAT_U18_3LE) ++#define SNDRV_PCM_FMTBIT_S18_3BE (1ULL << SNDRV_PCM_FORMAT_S18_3BE) ++#define SNDRV_PCM_FMTBIT_U18_3BE (1ULL << SNDRV_PCM_FORMAT_U18_3BE) ++#define SNDRV_PCM_FMTBIT_G723_24 (1ULL << SNDRV_PCM_FORMAT_G723_24) ++#define SNDRV_PCM_FMTBIT_G723_24_1B (1ULL << SNDRV_PCM_FORMAT_G723_24_1B) ++#define SNDRV_PCM_FMTBIT_G723_40 (1ULL << SNDRV_PCM_FORMAT_G723_40) ++#define SNDRV_PCM_FMTBIT_G723_40_1B (1ULL << SNDRV_PCM_FORMAT_G723_40_1B) ++#define SNDRV_PCM_FMTBIT_DSD_U8 (1ULL << SNDRV_PCM_FORMAT_DSD_U8) ++#define SNDRV_PCM_FMTBIT_DSD_U16_LE (1ULL << SNDRV_PCM_FORMAT_DSD_U16_LE) ++#define SNDRV_PCM_FMTBIT_DSD_U32_LE (1ULL << SNDRV_PCM_FORMAT_DSD_U32_LE) ++#define SNDRV_PCM_FMTBIT_DSD_U16_BE (1ULL << SNDRV_PCM_FORMAT_DSD_U16_BE) ++#define SNDRV_PCM_FMTBIT_DSD_U32_BE (1ULL << SNDRV_PCM_FORMAT_DSD_U32_BE) + + #ifdef SNDRV_LITTLE_ENDIAN + #define SNDRV_PCM_FMTBIT_S16 SNDRV_PCM_FMTBIT_S16_LE +@@ -228,11 +227,10 @@ struct snd_pcm_ops { + #define SNDRV_PCM_FMTBIT_U20 SNDRV_PCM_FMTBIT_U20_BE + #endif + +-#define _SNDRV_PCM_SUBFMTBIT(fmt) BIT((__force int)SNDRV_PCM_SUBFORMAT_##fmt) +-#define SNDRV_PCM_SUBFMTBIT_STD _SNDRV_PCM_SUBFMTBIT(STD) +-#define SNDRV_PCM_SUBFMTBIT_MSBITS_MAX _SNDRV_PCM_SUBFMTBIT(MSBITS_MAX) +-#define SNDRV_PCM_SUBFMTBIT_MSBITS_20 _SNDRV_PCM_SUBFMTBIT(MSBITS_20) +-#define SNDRV_PCM_SUBFMTBIT_MSBITS_24 _SNDRV_PCM_SUBFMTBIT(MSBITS_24) ++#define SNDRV_PCM_SUBFMTBIT_STD (1U << SNDRV_PCM_SUBFORMAT_STD) ++#define SNDRV_PCM_SUBFMTBIT_MSBITS_MAX (1U << SNDRV_PCM_SUBFORMAT_MSBITS_MAX) ++#define SNDRV_PCM_SUBFMTBIT_MSBITS_20 (1U << SNDRV_PCM_SUBFORMAT_MSBITS_20) ++#define SNDRV_PCM_SUBFMTBIT_MSBITS_24 (1U << SNDRV_PCM_SUBFORMAT_MSBITS_24) + + struct snd_pcm_file { + struct snd_pcm_substream *substream; +@@ -1515,7 +1513,7 @@ int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream, + */ + static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format) + { +- return 1ULL << (__force int) pcm_format; ++ return 1ULL << pcm_format; + } + + /** +@@ -1523,9 +1521,7 @@ static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format) + * @f: the iterator variable in snd_pcm_format_t type + */ + #define pcm_for_each_format(f) \ +- for ((f) = SNDRV_PCM_FORMAT_FIRST; \ +- (__force int)(f) <= (__force int)SNDRV_PCM_FORMAT_LAST; \ +- (f) = (__force snd_pcm_format_t)((__force int)(f) + 1)) ++ for ((f) = SNDRV_PCM_FORMAT_FIRST; (f) <= SNDRV_PCM_FORMAT_LAST; (f)++) + + /* printk helpers */ + #define pcm_err(pcm, fmt, args...) \ +diff --git a/include/sound/pcm_params.h b/include/sound/pcm_params.h +--- a/include/sound/pcm_params.h ++++ b/include/sound/pcm_params.h +@@ -71,7 +71,7 @@ static inline void snd_mask_set(struct snd_mask *mask, unsigned int val) + static inline void snd_mask_set_format(struct snd_mask *mask, + snd_pcm_format_t format) + { +- snd_mask_set(mask, (__force unsigned int)format); ++ snd_mask_set(mask, format); + } + + static inline void snd_mask_reset(struct snd_mask *mask, unsigned int val) +@@ -132,7 +132,7 @@ static inline int snd_mask_test(const struct snd_mask *mask, unsigned int val) + static inline int snd_mask_test_format(const struct snd_mask *mask, + snd_pcm_format_t format) + { +- return snd_mask_test(mask, (__force unsigned int)format); ++ return snd_mask_test(mask, format); + } + + static inline int snd_mask_single(const struct snd_mask *mask) +@@ -302,8 +302,7 @@ static inline int snd_interval_eq(const struct snd_interval *i1, const struct sn + */ + static inline snd_pcm_access_t params_access(const struct snd_pcm_hw_params *p) + { +- return (__force snd_pcm_access_t)snd_mask_min(hw_param_mask_c(p, +- SNDRV_PCM_HW_PARAM_ACCESS)); ++ return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_ACCESS)); + } + + /** +@@ -312,8 +311,7 @@ static inline snd_pcm_access_t params_access(const struct snd_pcm_hw_params *p) + */ + static inline snd_pcm_format_t params_format(const struct snd_pcm_hw_params *p) + { +- return (__force snd_pcm_format_t)snd_mask_min(hw_param_mask_c(p, +- SNDRV_PCM_HW_PARAM_FORMAT)); ++ return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_FORMAT)); + } + + /** +@@ -323,8 +321,7 @@ static inline snd_pcm_format_t params_format(const struct snd_pcm_hw_params *p) + static inline snd_pcm_subformat_t + params_subformat(const struct snd_pcm_hw_params *p) + { +- return (__force snd_pcm_subformat_t)snd_mask_min(hw_param_mask_c(p, +- SNDRV_PCM_HW_PARAM_SUBFORMAT)); ++ return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_SUBFORMAT)); + } + + /** +diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h +--- a/include/sound/rawmidi.h ++++ b/include/sound/rawmidi.h +@@ -176,8 +176,9 @@ int snd_rawmidi_proceed(struct snd_rawmidi_substream *substream); + /* main midi functions */ + + int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info); +-int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, +- int mode, struct snd_rawmidi_file *rfile); ++int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, ++ int mode, struct snd_rawmidi_file *rfile, ++ int depth); + int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile); + int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params); +@@ -191,6 +192,15 @@ long snd_rawmidi_kernel_read(struct snd_rawmidi_substream *substream, + long snd_rawmidi_kernel_write(struct snd_rawmidi_substream *substream, + const unsigned char *buf, long count); + ++/* non-nested version */ ++static inline int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, ++ int subdevice, ++ int mode, ++ struct snd_rawmidi_file *rfile) ++{ ++ return snd_rawmidi_kernel_open_nested(rmidi, subdevice, mode, rfile, 0); ++} ++ + /* set up the tied devices */ + static inline void snd_rawmidi_tie_devices(struct snd_rawmidi *r1, + struct snd_rawmidi *r2) +diff --git a/include/sound/sdca_asoc.h b/include/sound/sdca_asoc.h +--- a/include/sound/sdca_asoc.h ++++ b/include/sound/sdca_asoc.h +@@ -25,6 +25,8 @@ struct snd_soc_dai_driver; + struct snd_soc_dai_ops; + struct snd_soc_dapm_route; + struct snd_soc_dapm_widget; ++struct snd_soc_pcm_stream; ++struct sdca_entity; + + /* convenient macro to handle the mono volume in 7.8 fixed format representation */ + #define SDCA_SINGLE_Q78_TLV(xname, xreg, xmin, xmax, xstep, tlv_array) \ +@@ -82,6 +84,11 @@ int sdca_asoc_populate_component(struct device *dev, + struct snd_soc_dai_driver **dai_drv, int *num_dai_drv, + const struct snd_soc_dai_ops *ops); + ++int sdca_asoc_populate_rate_format(struct device *dev, ++ struct sdca_function_data *function, ++ struct sdca_entity *entity, ++ struct snd_soc_pcm_stream *stream); ++ + int sdca_asoc_set_constraints(struct device *dev, struct regmap *regmap, + struct sdca_function_data *function, + struct snd_pcm_substream *substream, +@@ -100,9 +107,9 @@ int sdca_asoc_q78_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + int sdca_asoc_q78_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +-int sdca_asoc_pde_poll_actual_ps(struct device *dev, struct regmap *regmap, ++int sdca_asoc_pde_poll_actual_ps(struct regmap *regmap, + int function_id, int entity_id, +- int from_ps, int to_ps, +- const struct sdca_pde_delay *pde_delays, +- int num_delays); ++ int from_ps, int to_ps, ++ const struct sdca_pde_delay *pde_delays, ++ int num_delays); + #endif // __SDCA_ASOC_H__ +diff --git a/include/sound/sdca_fdl.h b/include/sound/sdca_fdl.h +--- a/include/sound/sdca_fdl.h ++++ b/include/sound/sdca_fdl.h +@@ -83,6 +83,10 @@ static inline int sdca_fdl_alloc_state(struct sdca_interrupt *interrupt) + return 0; + } + ++static inline void sdca_fdl_free_state(struct sdca_interrupt *interrupt) ++{ ++} ++ + static inline int sdca_fdl_process(struct sdca_interrupt *interrupt) + { + return 0; +diff --git a/include/sound/sdca_function.h b/include/sound/sdca_function.h +--- a/include/sound/sdca_function.h ++++ b/include/sound/sdca_function.h +@@ -298,7 +298,7 @@ enum sdca_xu_controls { + }; + + /** +- * enum sdca_set_index_range - Column definitions UMP SetIndex ++ * enum sdca_fdl_set_index_range - Column definitions UMP SetIndex + */ + enum sdca_fdl_set_index_range { + SDCA_FDL_SET_INDEX_SET_NUMBER = 0, +@@ -803,6 +803,8 @@ struct sdca_control_range { + * @mode: Access mode of the Control. + * @layers: Bitmask of access layers of the Control. + * @deferrable: Indicates if the access to the Control can be deferred. ++ * @is_volatile: Indicates the Control registers are forced to be treated ++ * as volatile. + * @has_default: Indicates the Control has a default value to be written. + * @has_reset: Indicates the Control has a defined reset value. + * @has_fixed: Indicates the Control only supports a single value. +@@ -1116,7 +1118,6 @@ struct sdca_entity_ge { + + /** + * struct sdca_entity_hide - information specific to HIDE Entities +- * @hid: HID device structure + * @num_hidtx_ids: number of HIDTx Report ID + * @num_hidrx_ids: number of HIDRx Report ID + * @hidtx_ids: HIDTx Report ID +@@ -1127,11 +1128,8 @@ struct sdca_entity_ge { + * within this Device + * @max_delay: the maximum time in microseconds allowed for the Device + * to change the ownership from Device to Host +- * @hid_report_desc: HID Report Descriptor for the HIDE Entity +- * @hid_desc: HID descriptor for the HIDE Entity + */ + struct sdca_entity_hide { +- struct hid_device *hid; + unsigned int *hidtx_ids; + unsigned int *hidrx_ids; + int num_hidtx_ids; +@@ -1139,12 +1137,10 @@ struct sdca_entity_hide { + unsigned int af_number_list[SDCA_MAX_FUNCTION_COUNT]; + unsigned int hide_reside_function_num; + unsigned int max_delay; +- unsigned char *hid_report_desc; +- struct hid_descriptor hid_desc; + }; + + /** +- * enum sdca_xu_reset_machanism - SDCA FDL Resets ++ * enum sdca_xu_reset_mechanism - SDCA FDL Resets + */ + enum sdca_xu_reset_mechanism { + SDCA_XU_RESET_FUNCTION = 0x0, +@@ -1401,6 +1397,16 @@ struct sdca_fdl_data { + int num_sets; + }; + ++/** ++ * struct sdca_function_hid - information about a function's HID descriptors ++ * @report_desc: HID Report Descriptor for the HID Function ++ * @desc: HID descriptor for the HID Function ++ */ ++struct sdca_function_hid { ++ unsigned char *report_desc; ++ struct hid_descriptor desc; ++}; ++ + /** + * struct sdca_function_data - top-level information for one SDCA function + * @desc: Pointer to short descriptor from initial parsing. +@@ -1415,6 +1421,7 @@ struct sdca_fdl_data { + * @reset_max_delay: Maximum Function reset delay in microseconds, before an + * error should be reported. + * @fdl_data: FDL data for this Function, if available. ++ * @hid: HID data for this Function, if available. + */ + struct sdca_function_data { + struct sdca_function_desc *desc; +@@ -1430,6 +1437,10 @@ struct sdca_function_data { + unsigned int reset_max_delay; + + struct sdca_fdl_data fdl_data; ++ ++ union { ++ struct sdca_function_hid hid; ++ }; + }; + + static inline u32 sdca_range(struct sdca_control_range *range, +@@ -1451,8 +1462,7 @@ static inline u32 sdca_range_search(struct sdca_control_range *range, + return 0; + } + +-int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, +- struct sdca_function_data *function); ++int sdca_parse_function(struct device *dev, struct sdca_function_data *function); + + const char *sdca_find_terminal_name(enum sdca_terminal_type type); + +@@ -1469,5 +1479,7 @@ struct sdca_control_range *sdca_selector_find_range(struct device *dev, + struct sdca_cluster *sdca_id_find_cluster(struct device *dev, + struct sdca_function_data *function, + const int id); ++struct sdca_entity *sdca_find_entity_by_label(struct sdca_function_data *function, ++ const char *entity_label); + + #endif +diff --git a/include/sound/sdca_hid.h b/include/sound/sdca_hid.h +--- a/include/sound/sdca_hid.h ++++ b/include/sound/sdca_hid.h +@@ -16,18 +16,21 @@ struct sdca_interrupt; + + #if IS_ENABLED(CONFIG_SND_SOC_SDCA_HID) + +-int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, +- struct sdca_entity *entity); ++int sdca_add_hid_device(struct sdca_interrupt *interrupt); ++void sdca_destroy_hid_device(struct sdca_interrupt *interrupt); + int sdca_hid_process_report(struct sdca_interrupt *interrupt); + + #else + +-static inline int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, +- struct sdca_entity *entity) ++static inline int sdca_add_hid_device(struct sdca_interrupt *interrupt) + { + return 0; + } + ++static inline void sdca_destroy_hid_device(struct sdca_interrupt *interrupt) ++{ ++} ++ + static inline int sdca_hid_process_report(struct sdca_interrupt *interrupt) + { + return 0; +diff --git a/include/sound/seq_virmidi.h b/include/sound/seq_virmidi.h +--- a/include/sound/seq_virmidi.h ++++ b/include/sound/seq_virmidi.h +@@ -46,7 +46,6 @@ struct snd_virmidi_dev { + int client; /* created/attached client */ + int port; /* created/attached port */ + unsigned int flags; /* SNDRV_VIRMIDI_* */ +- rwlock_t filelist_lock; + struct rw_semaphore filelist_sem; + struct list_head filelist; + }; +diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h +--- a/include/sound/simple_card_utils.h ++++ b/include/sound/simple_card_utils.h +@@ -181,7 +181,7 @@ void simple_util_canonicalize_platform(struct snd_soc_dai_link_component *platfo + void simple_util_canonicalize_cpu(struct snd_soc_dai_link_component *cpus, + int is_single_links); + +-void simple_util_clean_reference(struct snd_soc_card *card); ++void simple_util_clean_reference(struct simple_util_priv *priv); + + void simple_util_parse_convert(struct device_node *np, char *prefix, + struct simple_util_data *data); +@@ -189,18 +189,37 @@ bool simple_util_is_convert_required(const struct simple_util_data *data); + + int simple_util_get_sample_fmt(struct simple_util_data *data); + +-int simple_util_parse_routing(struct snd_soc_card *card, +- char *prefix); +-int simple_util_parse_widgets(struct snd_soc_card *card, +- char *prefix); +-int simple_util_parse_pin_switches(struct snd_soc_card *card, +- char *prefix); ++int simple_util_parse_property(struct simple_util_priv *priv, ++ int (*func)(struct snd_soc_card *card, const char *propname), ++ char *prefix, char *property); ++static inline int simple_util_parse_routing(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_audio_routing, ++ prefix, "routing"); ++} ++ ++static inline int simple_util_parse_widgets(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_audio_simple_widgets, ++ prefix, "widgets"); ++} ++ ++static inline int simple_util_parse_pin_switches(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_pin_switches, ++ prefix, "pin-switches"); ++} ++ ++static inline int simple_util_parse_aux_devs(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_aux_devs, ++ prefix, "aux-devs"); ++} + + int simple_util_init_jack(struct snd_soc_card *card, + struct simple_util_jack *sjack, + int is_hp, char *prefix, char *pin); +-int simple_util_init_aux_jacks(struct simple_util_priv *priv, +- char *prefix); ++int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix); + int simple_util_init_priv(struct simple_util_priv *priv, + struct link_info *li); + void simple_util_remove(struct platform_device *pdev); +diff --git a/include/sound/soc-acpi.h b/include/sound/soc-acpi.h +--- a/include/sound/soc-acpi.h ++++ b/include/sound/soc-acpi.h +@@ -57,7 +57,7 @@ static inline struct snd_soc_acpi_mach *snd_soc_acpi_codec_list(void *arg) + #endif + + /** +- * snd_soc_acpi_mach_params: interface for machine driver configuration ++ * struct snd_soc_acpi_mach_params - interface for machine driver configuration + * + * @acpi_ipc_irq_index: used for BYT-CR detection + * @platform: string used for HDAudio codec support +@@ -93,7 +93,7 @@ struct snd_soc_acpi_mach_params { + }; + + /** +- * snd_soc_acpi_endpoint - endpoint descriptor ++ * struct snd_soc_acpi_endpoint - endpoint descriptor + * @num: endpoint number (mandatory, unique per device) + * @aggregated: 0 (independent) or 1 (logically grouped) + * @group_position: zero-based order (only when @aggregated is 1) +@@ -107,7 +107,7 @@ struct snd_soc_acpi_endpoint { + }; + + /** +- * snd_soc_acpi_adr_device - descriptor for _ADR-enumerated device ++ * struct snd_soc_acpi_adr_device - descriptor for _ADR-enumerated device + * @adr: 64 bit ACPI _ADR value + * @num_endpoints: number of endpoints for this device + * @endpoints: array of endpoints +@@ -121,7 +121,7 @@ struct snd_soc_acpi_adr_device { + }; + + /** +- * snd_soc_acpi_link_adr - ACPI-based list of _ADR enumerated devices ++ * struct snd_soc_acpi_link_adr - ACPI-based list of _ADR enumerated devices + * @mask: one bit set indicates the link this list applies to + * @num_adr: ARRAY_SIZE of devices + * @adr_d: array of devices +@@ -167,8 +167,8 @@ struct snd_soc_acpi_link_adr { + #define SND_SOC_ACPI_TPLG_INTEL_CODEC_NAME BIT(4) + + /** +- * snd_soc_acpi_mach: ACPI-based machine descriptor. Most of the fields are +- * related to the hardware, except for the firmware and topology file names. ++ * struct snd_soc_acpi_mach - ACPI-based machine descriptor. Most of the fields ++ * are related to the hardware, except for the firmware and topology file names. + * A platform supported by legacy and Sound Open Firmware (SOF) would expose + * all firmware/topology related fields. + * +@@ -192,6 +192,7 @@ struct snd_soc_acpi_link_adr { + * the initial selection in the snd_soc_acpi_mach table. + * @pdata: intended for platform data or machine specific-ops. This structure + * is not constant since this field may be updated at run-time ++ * @mach_params: machine driver configuration + * @sof_tplg_filename: Sound Open Firmware topology file name, if enabled + * @tplg_quirk_mask: quirks to select different topology files dynamically + * @get_function_tplg_files: This is an optional callback, if specified then instead of +@@ -199,11 +200,11 @@ struct snd_soc_acpi_link_adr { + * files to be loaded. + * Return value: The number of the files or negative ERRNO. 0 means that the single topology + * file should be used, no function topology split can be used on the machine. +- * @card: the pointer of the card +- * @mach: the pointer of the machine driver +- * @prefix: the prefix of the topology file name. Typically, it is the path. +- * @tplg_files: the pointer of the array of the topology file names. +- * @best_effort: ignore non supported links and try to build the card in best effort ++ * card: the pointer of the card ++ * mach: the pointer of the machine driver ++ * prefix: the prefix of the topology file name. Typically, it is the path. ++ * tplg_files: the pointer of the array of the topology file names. ++ * best_effort: ignore non supported links and try to build the card in best effort + * with supported links + */ + /* Descriptor for SST ASoC machine driver */ +diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h +--- a/include/sound/soc-component.h ++++ b/include/sound/soc-component.h +@@ -253,6 +253,9 @@ struct snd_soc_component { + void *mark_pm; + + struct dentry *debugfs_root; ++ ++ /* Component private data */ ++ void *priv; + }; + + #define for_each_component_dais(component, dai)\ +@@ -283,6 +286,14 @@ static inline int snd_soc_component_cache_sync( + return regcache_sync(component->regmap); + } + ++struct snd_soc_component *snd_soc_component_alloc(struct device *dev); ++ ++void snd_soc_component_set_name(struct snd_soc_component *component, const char *name); ++const char *snd_soc_component_name(struct snd_soc_component *component); ++ ++void snd_soc_component_set_priv(struct snd_soc_component *component, void *priv); ++void *snd_soc_component_to_priv(struct snd_soc_component *component); ++ + void snd_soc_component_set_aux(struct snd_soc_component *component, + struct snd_soc_aux_dev *aux); + int snd_soc_component_init(struct snd_soc_component *component); +diff --git a/include/sound/soc.h b/include/sound/soc.h +--- a/include/sound/soc.h ++++ b/include/sound/soc.h +@@ -447,15 +447,16 @@ static inline int snd_soc_resume(struct device *dev) + } + #endif + int snd_soc_poweroff(struct device *dev); +-int snd_soc_component_initialize(struct snd_soc_component *component, +- const struct snd_soc_component_driver *driver, +- struct device *dev); +-int snd_soc_add_component(struct snd_soc_component *component, +- struct snd_soc_dai_driver *dai_drv, +- int num_dai); +-int snd_soc_register_component(struct device *dev, ++int snd_soc_register_component_c(struct snd_soc_component *component, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, int num_dai); ++int snd_soc_register_component_d(struct device *dev, ++ const struct snd_soc_component_driver *component_driver, ++ struct snd_soc_dai_driver *dai_drv, int num_dai); ++#define snd_soc_register_component(x, ...) _Generic((x), \ ++struct device * : snd_soc_register_component_d, \ ++struct snd_soc_component * : snd_soc_register_component_c)(x, __VA_ARGS__) ++ + int devm_snd_soc_register_component(struct device *dev, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, int num_dai); +diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h +--- a/include/sound/soc_sdw_utils.h ++++ b/include/sound/soc_sdw_utils.h +@@ -44,6 +44,18 @@ + + struct asoc_sdw_codec_info; + ++struct asoc_sdw_mc_private { ++ struct snd_soc_card card; ++ struct snd_soc_jack sdw_headset; ++ struct device *headset_codec_dev; /* only one headset per card */ ++ struct device *amp_dev1, *amp_dev2; ++ bool append_dai_type; ++ bool ignore_internal_dmic; ++ void *private; ++ unsigned long mc_quirk; ++ int codec_info_list_count; ++}; ++ + struct asoc_sdw_dai_info { + const bool direction[2]; /* playback & capture support */ + const char *codec_name; +@@ -88,25 +100,13 @@ struct asoc_sdw_codec_info { + + int (*codec_card_late_probe)(struct snd_soc_card *card); + +- int (*count_sidecar)(struct snd_soc_card *card, ++ int (*count_sidecar)(struct asoc_sdw_mc_private *ctx, + int *num_dais, int *num_devs); + int (*add_sidecar)(struct snd_soc_card *card, + struct snd_soc_dai_link **dai_links, + struct snd_soc_codec_conf **codec_conf); + }; + +-struct asoc_sdw_mc_private { +- struct snd_soc_card card; +- struct snd_soc_jack sdw_headset; +- struct device *headset_codec_dev; /* only one headset per card */ +- struct device *amp_dev1, *amp_dev2; +- bool append_dai_type; +- bool ignore_internal_dmic; +- void *private; +- unsigned long mc_quirk; +- int codec_info_list_count; +-}; +- + struct asoc_sdw_endpoint { + struct list_head list; + +@@ -182,7 +182,8 @@ struct asoc_sdw_dailink *asoc_sdw_find_dailink(struct asoc_sdw_dailink *dailinks + const struct snd_soc_acpi_endpoint *new); + int asoc_sdw_get_dai_type(u32 type); + +-int asoc_sdw_parse_sdw_endpoints(struct snd_soc_card *card, ++int asoc_sdw_parse_sdw_endpoints(struct device *dev, ++ struct asoc_sdw_mc_private *ctx, + struct snd_soc_aux_dev *soc_aux, + struct asoc_sdw_dailink *soc_dais, + struct asoc_sdw_endpoint *soc_ends, +@@ -235,7 +236,7 @@ int asoc_sdw_es9356_amp_init(struct snd_soc_card *card, + int asoc_sdw_es9356_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); + + /* CS AMP support */ +-int asoc_sdw_bridge_cs35l56_count_sidecar(struct snd_soc_card *card, ++int asoc_sdw_bridge_cs35l56_count_sidecar(struct asoc_sdw_mc_private *ctx, + int *num_dais, int *num_devs); + int asoc_sdw_bridge_cs35l56_add_sidecar(struct snd_soc_card *card, + struct snd_soc_dai_link **dai_links, +diff --git a/include/sound/sof/dai-amd.h b/include/sound/sof/dai-amd.h +--- a/include/sound/sof/dai-amd.h ++++ b/include/sound/sof/dai-amd.h +@@ -18,6 +18,7 @@ struct sof_ipc_dai_acp_params { + uint32_t fsync_rate; /* FSYNC frequency in Hz */ + uint32_t tdm_slots; + uint32_t tdm_mode; ++ uint32_t format; + } __packed; + + /* ACPDMIC Configuration Request - SOF_IPC_DAI_AMD_CONFIG */ +diff --git a/include/sound/sof/dai.h b/include/sound/sof/dai.h +--- a/include/sound/sof/dai.h ++++ b/include/sound/sof/dai.h +@@ -90,6 +90,8 @@ enum sof_ipc_dai_type { + SOF_DAI_AMD_HS_VIRTUAL, /**< AMD ACP HS VIRTUAL */ + SOF_DAI_IMX_MICFIL, /** < i.MX MICFIL PDM */ + SOF_DAI_AMD_SDW, /**< AMD ACP SDW */ ++ SOF_DAI_INTEL_UAOL, /**< Intel UAOL */ ++ SOF_DAI_AMD_I2S, /**< AMD ACP I2S */ + }; + + /* general purpose DAI configuration */ +@@ -121,6 +123,7 @@ struct sof_ipc_dai_config { + struct sof_ipc_dai_mtk_afe_params afe; + struct sof_ipc_dai_micfil_params micfil; + struct sof_ipc_dai_acp_sdw_params acp_sdw; ++ struct sof_ipc_dai_acp_params acp_i2s; + }; + } __packed; + +diff --git a/include/sound/sof/header.h b/include/sound/sof/header.h +--- a/include/sound/sof/header.h ++++ b/include/sound/sof/header.h +@@ -179,7 +179,7 @@ struct sof_ipc_compound_hdr { + uint32_t count; /**< count of 0 means end of compound sequence */ + } __packed; + +-/** ++/* + * OOPS header architecture specific data. + */ + struct sof_ipc_dsp_oops_arch_hdr { +@@ -187,7 +187,7 @@ struct sof_ipc_dsp_oops_arch_hdr { + uint32_t totalsize; /* Total size of oops message */ + } __packed; + +-/** ++/* + * OOPS header platform specific data. + */ + struct sof_ipc_dsp_oops_plat_hdr { +diff --git a/include/sound/sof/ipc4/header.h b/include/sound/sof/ipc4/header.h +--- a/include/sound/sof/ipc4/header.h ++++ b/include/sound/sof/ipc4/header.h +@@ -187,6 +187,10 @@ enum sof_ipc4_pipeline_state { + #define SOF_IPC4_GLB_PIPE_EXT_CORE_ID_MASK GENMASK(23, 20) + #define SOF_IPC4_GLB_PIPE_EXT_CORE_ID(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_CORE_ID_SHIFT) + ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_SHIFT 29 ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_MASK BIT(29) ++#define SOF_IPC4_GLB_PIPE_PAYLOAD(x) ((x) << SOF_IPC4_GLB_PIPE_PAYLOAD_SHIFT) ++ + /* pipeline set state ipc msg */ + #define SOF_IPC4_GLB_PIPE_STATE_ID_SHIFT 16 + #define SOF_IPC4_GLB_PIPE_STATE_ID_MASK GENMASK(23, 16) +@@ -536,12 +540,63 @@ enum sof_ipc4_notification_type { + SOF_IPC4_NOTIFY_TYPE_LAST, + }; + ++enum sof_ipc4_resource_type { ++ SOF_IPC4_MODULE_INSTANCE, ++ SOF_IPC4_PIPELINE, ++ SOF_IPC4_GATEWAY, ++ SOF_IPC4_EDF_TASK, ++ SOF_IPC4_INVALID_RESOURCE_TYPE, ++}; ++ ++enum sof_ipc4_event_type { ++ /* Underrun detected by the Mixer */ ++ SOF_IPC4_MIXER_UNDERRUN_DETECTED = 1, ++ /* Error caught during data processing */ ++ SOF_IPC4_PROCESS_DATA_ERROR = 3, ++ /* Underrun detected by gateway. */ ++ SOF_IPC4_GATEWAY_UNDERRUN_DETECTED = 6, ++ /* Overrun detected by gateway */ ++ SOF_IPC4_GATEWAY_OVERRUN_DETECTED, ++}; ++ ++/** ++ * struct sof_ipc4_process_data_error_event_data - process data error event payload ++ * @error_code: Error code returned by data processing function ++ */ ++struct sof_ipc4_process_data_error_event_data { ++ uint32_t error_code; ++}; ++ ++/** ++ * struct sof_ipc4_mixer_underrun_event_data - mixer underrun event payload ++ * @eos_flag: Indicates EndOfStream ++ * @data_mixed: Data processed by module (in bytes) ++ * @expected_data_mixed: Expected data to be processed (in bytes) ++ */ ++struct sof_ipc4_mixer_underrun_event_data { ++ uint32_t eos_flag; ++ uint32_t data_mixed; ++ uint32_t expected_data_mixed; ++}; ++ ++/** ++ * union sof_ipc4_resource_event_data - resource event specific payload ++ * @dws: Raw event data payload as six dwords ++ * @process_data_error: SOF_IPC4_PROCESS_DATA_ERROR payload ++ * @mixer_underrun: SOF_IPC4_MIXER_UNDERRUN_DETECTED payload ++ */ ++union sof_ipc4_resource_event_data { ++ uint32_t dws[6]; ++ struct sof_ipc4_process_data_error_event_data process_data_error; ++ struct sof_ipc4_mixer_underrun_event_data mixer_underrun; ++}; ++ + struct sof_ipc4_notify_resource_data { + uint32_t resource_type; + uint32_t resource_id; + uint32_t event_type; + uint32_t reserved; +- uint32_t data[6]; ++ union sof_ipc4_resource_event_data data; + } __packed __aligned(4); + + #define SOF_IPC4_DEBUG_DESCRIPTOR_SIZE 12 /* 3 x u32 */ +@@ -654,11 +709,81 @@ enum sof_ipc4_mod_init_ext_obj_id { + SOF_IPC4_MOD_INIT_DATA_ID_MAX = SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA, + }; + +-/* DP module memory configuration data object for ext_init object array */ ++/* DP module memory configuration data object for object array */ + struct sof_ipc4_mod_init_ext_dp_memory_data { ++ u32 domain_id; /* userspace domain ID */ ++ u32 stack_bytes; /* required stack size in bytes */ ++ u32 heap_bytes; /* required heap size in bytes */ ++} __packed __aligned(4); ++ ++/* ++ * This set of macros are very similar to the set above, but these are ++ * for building payload to SOF_IPC4_GLB_CREATE_PIPELINE message. ++ * ++ * Macros for creating struct sof_ipc4_glb_pipe_payload payload with ++ * its associated data. struct sof_ipc4_glb_pipe_payload should be the ++ * first piece of payload following SOF_IPC4_GLB_CREATE_PIPELINE msg, ++ * and its existence is indicated with SOF_IPC4_GLB_PIPE_PAYLOAD bit. ++ * ++ * The macros below apply to sof_ipc4_glb_pipe_payload.word0 ++ */ ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS_SHIFT 0 ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS_MASK GENMASK(23, 0) ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS(x) ((x) << SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS_SHIFT) ++ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_SHIFT 24 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_MASK BIT(24) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_SHIFT) ++ ++struct sof_ipc4_glb_pipe_payload { ++ u32 word0; ++ u32 rsvd1; ++ u32 rsvd2; ++} __packed __aligned(4); ++ ++/* ++ * SOF_IPC4_GLB_CREATE_PIPELINE payload may be followed by arbitrary ++ * number of object array objects. SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY-bit ++ * indicates that an array object follows struct ++ * sof_ipc4_glb_pipe_payload. ++ * ++ * The object header's SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST-bit in struct ++ * sof_ipc4_glb_pipe_ext_object indicates if the array is continued ++ * with another object. The header has also fields to identify the ++ * object, SOF_IPC4_GLB_PIPE_EXT_OBJ_ID, and to indicate the object's ++ * size in 32-bit words, SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS, not ++ * including the header itself. ++ * ++ * The macros below apply to sof_ipc4_glb_pipe_ext_object.header ++ */ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_SHIFT 0 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_MASK BIT(0) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_SHIFT) ++ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ID_SHIFT 1 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ID_MASK GENMASK(15, 1) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ID(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_ID_SHIFT) ++ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS_SHIFT 16 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS_MASK GENMASK(31, 16) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS_SHIFT) ++ ++struct sof_ipc4_glb_pipe_ext_object { ++ u32 header; ++ u32 data[]; ++} __packed __aligned(4); ++ ++enum sof_ipc4_glb_pipe_ext_obj_id { ++ SOF_IPC4_GLB_PIPE_DATA_ID_INVALID = 0, ++ SOF_IPC4_GLB_PIPE_DATA_ID_MEM_DATA, ++ SOF_IPC4_GLB_PIPE_DATA_ID_MAX = SOF_IPC4_GLB_PIPE_DATA_ID_MEM_DATA, ++}; ++ ++/* Pipeline memory configuration data object for ext_init object array */ ++struct sof_ipc4_glb_pipe_ext_obj_memory_data { + u32 domain_id; /* userspace domain ID */ +- u32 stack_bytes; /* stack size in bytes, 0 means default size */ +- u32 heap_bytes; /* stack size in bytes, 0 means default size */ ++ u32 stack_bytes; /* stack size in bytes */ ++ u32 heap_bytes; /* heap size in bytes */ + } __packed __aligned(4); + + /** @}*/ +diff --git a/include/sound/tas2781-dsp.h b/include/sound/tas2781-dsp.h +--- a/include/sound/tas2781-dsp.h ++++ b/include/sound/tas2781-dsp.h +@@ -201,6 +201,11 @@ struct tasdevice_rca { + int ncfgs; + struct tasdevice_config_info **cfg_info; + int profile_cfg_id; ++ /* ++ * Used among SmartAMP for PDM microphone recording or IV data ++ * capture. ++ */ ++ int capture_profile_id; + /* + * Since version 0x105, the keyword 'init' was introduced into the + * profile, which is used for chip initialization, particularly to +@@ -222,7 +227,7 @@ void tasdevice_calbin_remove(void *context); + int tasdevice_select_tuningprm_cfg(void *context, int prm, + int cfg_no, int rca_conf_no); + int tasdevice_prmg_load(void *context, int prm_no); +-void tasdevice_tuning_switch(void *context, int state); ++void tasdevice_tuning_switch(void *context, int state, bool is_cap); + int tas2781_load_calibration(void *context, char *file_name, + unsigned short i); + +diff --git a/include/sound/tlv320aic32x4.h b/include/sound/tlv320aic32x4.h +deleted file mode 100644 +--- a/include/sound/tlv320aic32x4.h ++++ /dev/null +@@ -1,43 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-only */ +-/* +- * tlv320aic32x4.h -- TLV320AIC32X4 Soc Audio driver platform data +- * +- * Copyright 2011 Vista Silicon S.L. +- * +- * Author: Javier Martin +- */ +- +-#ifndef _AIC32X4_PDATA_H +-#define _AIC32X4_PDATA_H +- +-#define AIC32X4_PWR_MICBIAS_2075_LDOIN 0x00000001 +-#define AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE 0x00000002 +-#define AIC32X4_PWR_AIC32X4_LDO_ENABLE 0x00000004 +-#define AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36 0x00000008 +-#define AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED 0x00000010 +- +-#define AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K 0x00000001 +-#define AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K 0x00000002 +- +-/* GPIO API */ +-#define AIC32X4_MFPX_DEFAULT_VALUE 0xff +- +-#define AIC32X4_MFP1_DIN_DISABLED 0 +-#define AIC32X4_MFP1_DIN_ENABLED 0x2 +-#define AIC32X4_MFP1_GPIO_IN 0x4 +- +-#define AIC32X4_MFP2_GPIO_OUT_LOW 0x0 +-#define AIC32X4_MFP2_GPIO_OUT_HIGH 0x1 +- +-#define AIC32X4_MFP_GPIO_ENABLED 0x4 +- +-#define AIC32X4_MFP5_GPIO_DISABLED 0x0 +-#define AIC32X4_MFP5_GPIO_INPUT 0x8 +-#define AIC32X4_MFP5_GPIO_OUTPUT 0xc +-#define AIC32X4_MFP5_GPIO_OUT_LOW 0x0 +-#define AIC32X4_MFP5_GPIO_OUT_HIGH 0x1 +- +-struct aic32x4_setup_data { +- unsigned int gpio_func[5]; +-}; +-#endif +diff --git a/include/sound/wm8904.h b/include/sound/wm8904.h +--- a/include/sound/wm8904.h ++++ b/include/sound/wm8904.h +@@ -116,7 +116,7 @@ + #define WM8904_DRC_REGS 4 + #define WM8904_EQ_REGS 24 + +-/** ++/* + * DRC configurations are specified with a label and a set of register + * values to write (the enable bits will be ignored). At runtime an + * enumerated control will be presented for each DRC block allowing +@@ -131,7 +131,7 @@ struct wm8904_drc_cfg { + u16 regs[WM8904_DRC_REGS]; + }; + +-/** ++/* + * ReTune Mobile configurations are specified with a label, sample + * rate and set of values to write (the enable bits will be ignored). + * +diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h +--- a/include/uapi/sound/asequencer.h ++++ b/include/uapi/sound/asequencer.h +@@ -308,16 +308,6 @@ struct snd_seq_ump_event { + }; + }; + +-/* +- * bounce event - stored as variable size data +- */ +-struct snd_seq_event_bounce { +- int err; +- struct snd_seq_event event; +- /* external data follows here. */ +-}; +- +- + /* system information */ + struct snd_seq_system_info { + int queues; /* maximum queues count */ +@@ -348,10 +338,10 @@ struct snd_seq_running_info { + + + /* client types */ +-typedef int __bitwise snd_seq_client_type_t; +-#define NO_CLIENT ((__force snd_seq_client_type_t) 0) +-#define USER_CLIENT ((__force snd_seq_client_type_t) 1) +-#define KERNEL_CLIENT ((__force snd_seq_client_type_t) 2) ++typedef int snd_seq_client_type_t; ++#define NO_CLIENT 0 ++#define USER_CLIENT 1 ++#define KERNEL_CLIENT 2 + + /* event filter flags */ + #define SNDRV_SEQ_FILTER_BROADCAST (1U<<0) /* accept broadcast messages */ +diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h +--- a/include/uapi/sound/asound.h ++++ b/include/uapi/sound/asound.h +@@ -169,72 +169,72 @@ enum { + SNDRV_PCM_STREAM_LAST = SNDRV_PCM_STREAM_CAPTURE, + }; + +-typedef int __bitwise snd_pcm_access_t; +-#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED ((__force snd_pcm_access_t) 0) /* interleaved mmap */ +-#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED ((__force snd_pcm_access_t) 1) /* noninterleaved mmap */ +-#define SNDRV_PCM_ACCESS_MMAP_COMPLEX ((__force snd_pcm_access_t) 2) /* complex mmap */ +-#define SNDRV_PCM_ACCESS_RW_INTERLEAVED ((__force snd_pcm_access_t) 3) /* readi/writei */ +-#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED ((__force snd_pcm_access_t) 4) /* readn/writen */ ++typedef int snd_pcm_access_t; ++#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED 0 /* interleaved mmap */ ++#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED 1 /* noninterleaved mmap */ ++#define SNDRV_PCM_ACCESS_MMAP_COMPLEX 2 /* complex mmap */ ++#define SNDRV_PCM_ACCESS_RW_INTERLEAVED 3 /* readi/writei */ ++#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED 4 /* readn/writen */ + #define SNDRV_PCM_ACCESS_LAST SNDRV_PCM_ACCESS_RW_NONINTERLEAVED + +-typedef int __bitwise snd_pcm_format_t; +-#define SNDRV_PCM_FORMAT_S8 ((__force snd_pcm_format_t) 0) +-#define SNDRV_PCM_FORMAT_U8 ((__force snd_pcm_format_t) 1) +-#define SNDRV_PCM_FORMAT_S16_LE ((__force snd_pcm_format_t) 2) +-#define SNDRV_PCM_FORMAT_S16_BE ((__force snd_pcm_format_t) 3) +-#define SNDRV_PCM_FORMAT_U16_LE ((__force snd_pcm_format_t) 4) +-#define SNDRV_PCM_FORMAT_U16_BE ((__force snd_pcm_format_t) 5) +-#define SNDRV_PCM_FORMAT_S24_LE ((__force snd_pcm_format_t) 6) /* low three bytes */ +-#define SNDRV_PCM_FORMAT_S24_BE ((__force snd_pcm_format_t) 7) /* low three bytes */ +-#define SNDRV_PCM_FORMAT_U24_LE ((__force snd_pcm_format_t) 8) /* low three bytes */ +-#define SNDRV_PCM_FORMAT_U24_BE ((__force snd_pcm_format_t) 9) /* low three bytes */ ++typedef int snd_pcm_format_t; ++#define SNDRV_PCM_FORMAT_S8 0 ++#define SNDRV_PCM_FORMAT_U8 1 ++#define SNDRV_PCM_FORMAT_S16_LE 2 ++#define SNDRV_PCM_FORMAT_S16_BE 3 ++#define SNDRV_PCM_FORMAT_U16_LE 4 ++#define SNDRV_PCM_FORMAT_U16_BE 5 ++#define SNDRV_PCM_FORMAT_S24_LE 6 /* low three bytes */ ++#define SNDRV_PCM_FORMAT_S24_BE 7 /* low three bytes */ ++#define SNDRV_PCM_FORMAT_U24_LE 8 /* low three bytes */ ++#define SNDRV_PCM_FORMAT_U24_BE 9 /* low three bytes */ + /* + * For S32/U32 formats, 'msbits' hardware parameter is often used to deliver information about the + * available bit count in most significant bit. It's for the case of so-called 'left-justified' or + * `right-padding` sample which has less width than 32 bit. + */ +-#define SNDRV_PCM_FORMAT_S32_LE ((__force snd_pcm_format_t) 10) +-#define SNDRV_PCM_FORMAT_S32_BE ((__force snd_pcm_format_t) 11) +-#define SNDRV_PCM_FORMAT_U32_LE ((__force snd_pcm_format_t) 12) +-#define SNDRV_PCM_FORMAT_U32_BE ((__force snd_pcm_format_t) 13) +-#define SNDRV_PCM_FORMAT_FLOAT_LE ((__force snd_pcm_format_t) 14) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_FLOAT_BE ((__force snd_pcm_format_t) 15) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_FLOAT64_LE ((__force snd_pcm_format_t) 16) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_FLOAT64_BE ((__force snd_pcm_format_t) 17) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE ((__force snd_pcm_format_t) 18) /* IEC-958 subframe, Little Endian */ +-#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE ((__force snd_pcm_format_t) 19) /* IEC-958 subframe, Big Endian */ +-#define SNDRV_PCM_FORMAT_MU_LAW ((__force snd_pcm_format_t) 20) +-#define SNDRV_PCM_FORMAT_A_LAW ((__force snd_pcm_format_t) 21) +-#define SNDRV_PCM_FORMAT_IMA_ADPCM ((__force snd_pcm_format_t) 22) +-#define SNDRV_PCM_FORMAT_MPEG ((__force snd_pcm_format_t) 23) +-#define SNDRV_PCM_FORMAT_GSM ((__force snd_pcm_format_t) 24) +-#define SNDRV_PCM_FORMAT_S20_LE ((__force snd_pcm_format_t) 25) /* in four bytes, LSB justified */ +-#define SNDRV_PCM_FORMAT_S20_BE ((__force snd_pcm_format_t) 26) /* in four bytes, LSB justified */ +-#define SNDRV_PCM_FORMAT_U20_LE ((__force snd_pcm_format_t) 27) /* in four bytes, LSB justified */ +-#define SNDRV_PCM_FORMAT_U20_BE ((__force snd_pcm_format_t) 28) /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_S32_LE 10 ++#define SNDRV_PCM_FORMAT_S32_BE 11 ++#define SNDRV_PCM_FORMAT_U32_LE 12 ++#define SNDRV_PCM_FORMAT_U32_BE 13 ++#define SNDRV_PCM_FORMAT_FLOAT_LE 14 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_FLOAT_BE 15 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_FLOAT64_LE 16 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_FLOAT64_BE 17 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE 18 /* IEC-958 subframe, Little Endian */ ++#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE 19 /* IEC-958 subframe, Big Endian */ ++#define SNDRV_PCM_FORMAT_MU_LAW 20 ++#define SNDRV_PCM_FORMAT_A_LAW 21 ++#define SNDRV_PCM_FORMAT_IMA_ADPCM 22 ++#define SNDRV_PCM_FORMAT_MPEG 23 ++#define SNDRV_PCM_FORMAT_GSM 24 ++#define SNDRV_PCM_FORMAT_S20_LE 25 /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_S20_BE 26 /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_U20_LE 27 /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_U20_BE 28 /* in four bytes, LSB justified */ + /* gap in the numbering for a future standard linear format */ +-#define SNDRV_PCM_FORMAT_SPECIAL ((__force snd_pcm_format_t) 31) +-#define SNDRV_PCM_FORMAT_S24_3LE ((__force snd_pcm_format_t) 32) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S24_3BE ((__force snd_pcm_format_t) 33) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U24_3LE ((__force snd_pcm_format_t) 34) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U24_3BE ((__force snd_pcm_format_t) 35) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S20_3LE ((__force snd_pcm_format_t) 36) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S20_3BE ((__force snd_pcm_format_t) 37) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U20_3LE ((__force snd_pcm_format_t) 38) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U20_3BE ((__force snd_pcm_format_t) 39) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S18_3LE ((__force snd_pcm_format_t) 40) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S18_3BE ((__force snd_pcm_format_t) 41) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U18_3LE ((__force snd_pcm_format_t) 42) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U18_3BE ((__force snd_pcm_format_t) 43) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_G723_24 ((__force snd_pcm_format_t) 44) /* 8 samples in 3 bytes */ +-#define SNDRV_PCM_FORMAT_G723_24_1B ((__force snd_pcm_format_t) 45) /* 1 sample in 1 byte */ +-#define SNDRV_PCM_FORMAT_G723_40 ((__force snd_pcm_format_t) 46) /* 8 Samples in 5 bytes */ +-#define SNDRV_PCM_FORMAT_G723_40_1B ((__force snd_pcm_format_t) 47) /* 1 sample in 1 byte */ +-#define SNDRV_PCM_FORMAT_DSD_U8 ((__force snd_pcm_format_t) 48) /* DSD, 1-byte samples DSD (x8) */ +-#define SNDRV_PCM_FORMAT_DSD_U16_LE ((__force snd_pcm_format_t) 49) /* DSD, 2-byte samples DSD (x16), little endian */ +-#define SNDRV_PCM_FORMAT_DSD_U32_LE ((__force snd_pcm_format_t) 50) /* DSD, 4-byte samples DSD (x32), little endian */ +-#define SNDRV_PCM_FORMAT_DSD_U16_BE ((__force snd_pcm_format_t) 51) /* DSD, 2-byte samples DSD (x16), big endian */ +-#define SNDRV_PCM_FORMAT_DSD_U32_BE ((__force snd_pcm_format_t) 52) /* DSD, 4-byte samples DSD (x32), big endian */ ++#define SNDRV_PCM_FORMAT_SPECIAL 31 ++#define SNDRV_PCM_FORMAT_S24_3LE 32 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S24_3BE 33 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U24_3LE 34 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U24_3BE 35 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S20_3LE 36 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S20_3BE 37 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U20_3LE 38 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U20_3BE 39 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S18_3LE 40 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S18_3BE 41 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U18_3LE 42 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U18_3BE 43 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_G723_24 44 /* 8 samples in 3 bytes */ ++#define SNDRV_PCM_FORMAT_G723_24_1B 45 /* 1 sample in 1 byte */ ++#define SNDRV_PCM_FORMAT_G723_40 46 /* 8 Samples in 5 bytes */ ++#define SNDRV_PCM_FORMAT_G723_40_1B 47 /* 1 sample in 1 byte */ ++#define SNDRV_PCM_FORMAT_DSD_U8 48 /* DSD, 1-byte samples DSD (x8) */ ++#define SNDRV_PCM_FORMAT_DSD_U16_LE 49 /* DSD, 2-byte samples DSD (x16), little endian */ ++#define SNDRV_PCM_FORMAT_DSD_U32_LE 50 /* DSD, 4-byte samples DSD (x32), little endian */ ++#define SNDRV_PCM_FORMAT_DSD_U16_BE 51 /* DSD, 2-byte samples DSD (x16), big endian */ ++#define SNDRV_PCM_FORMAT_DSD_U32_BE 52 /* DSD, 4-byte samples DSD (x32), big endian */ + #define SNDRV_PCM_FORMAT_LAST SNDRV_PCM_FORMAT_DSD_U32_BE + #define SNDRV_PCM_FORMAT_FIRST SNDRV_PCM_FORMAT_S8 + +@@ -265,11 +265,11 @@ typedef int __bitwise snd_pcm_format_t; + #define SNDRV_PCM_FORMAT_U20 SNDRV_PCM_FORMAT_U20_BE + #endif + +-typedef int __bitwise snd_pcm_subformat_t; +-#define SNDRV_PCM_SUBFORMAT_STD ((__force snd_pcm_subformat_t) 0) +-#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX ((__force snd_pcm_subformat_t) 1) +-#define SNDRV_PCM_SUBFORMAT_MSBITS_20 ((__force snd_pcm_subformat_t) 2) +-#define SNDRV_PCM_SUBFORMAT_MSBITS_24 ((__force snd_pcm_subformat_t) 3) ++typedef int snd_pcm_subformat_t; ++#define SNDRV_PCM_SUBFORMAT_STD 0 ++#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX 1 ++#define SNDRV_PCM_SUBFORMAT_MSBITS_20 2 ++#define SNDRV_PCM_SUBFORMAT_MSBITS_24 3 + #define SNDRV_PCM_SUBFORMAT_LAST SNDRV_PCM_SUBFORMAT_MSBITS_24 + + #define SNDRV_PCM_INFO_MMAP 0x00000001 /* hardware supports mmap */ +@@ -303,16 +303,16 @@ typedef int __bitwise snd_pcm_subformat_t; + #define __SND_STRUCT_TIME64 + #endif + +-typedef int __bitwise snd_pcm_state_t; +-#define SNDRV_PCM_STATE_OPEN ((__force snd_pcm_state_t) 0) /* stream is open */ +-#define SNDRV_PCM_STATE_SETUP ((__force snd_pcm_state_t) 1) /* stream has a setup */ +-#define SNDRV_PCM_STATE_PREPARED ((__force snd_pcm_state_t) 2) /* stream is ready to start */ +-#define SNDRV_PCM_STATE_RUNNING ((__force snd_pcm_state_t) 3) /* stream is running */ +-#define SNDRV_PCM_STATE_XRUN ((__force snd_pcm_state_t) 4) /* stream reached an xrun */ +-#define SNDRV_PCM_STATE_DRAINING ((__force snd_pcm_state_t) 5) /* stream is draining */ +-#define SNDRV_PCM_STATE_PAUSED ((__force snd_pcm_state_t) 6) /* stream is paused */ +-#define SNDRV_PCM_STATE_SUSPENDED ((__force snd_pcm_state_t) 7) /* hardware is suspended */ +-#define SNDRV_PCM_STATE_DISCONNECTED ((__force snd_pcm_state_t) 8) /* hardware is disconnected */ ++typedef int snd_pcm_state_t; ++#define SNDRV_PCM_STATE_OPEN 0 /* stream is open */ ++#define SNDRV_PCM_STATE_SETUP 1 /* stream has a setup */ ++#define SNDRV_PCM_STATE_PREPARED 2 /* stream is ready to start */ ++#define SNDRV_PCM_STATE_RUNNING 3 /* stream is running */ ++#define SNDRV_PCM_STATE_XRUN 4 /* stream reached an xrun */ ++#define SNDRV_PCM_STATE_DRAINING 5 /* stream is draining */ ++#define SNDRV_PCM_STATE_PAUSED 6 /* stream is paused */ ++#define SNDRV_PCM_STATE_SUSPENDED 7 /* hardware is suspended */ ++#define SNDRV_PCM_STATE_DISCONNECTED 8 /* hardware is disconnected */ + #define SNDRV_PCM_STATE_LAST SNDRV_PCM_STATE_DISCONNECTED + + enum { +@@ -1058,7 +1058,7 @@ struct snd_timer_tread { + * * + ****************************************************************************/ + +-#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 9) ++#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 10) + + struct snd_ctl_card_info { + int card; /* card number */ +@@ -1072,24 +1072,43 @@ struct snd_ctl_card_info { + unsigned char components[128]; /* card components / fine identification, delimited with one space (AC97 etc..) */ + }; + +-typedef int __bitwise snd_ctl_elem_type_t; +-#define SNDRV_CTL_ELEM_TYPE_NONE ((__force snd_ctl_elem_type_t) 0) /* invalid */ +-#define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((__force snd_ctl_elem_type_t) 1) /* boolean type */ +-#define SNDRV_CTL_ELEM_TYPE_INTEGER ((__force snd_ctl_elem_type_t) 2) /* integer type */ +-#define SNDRV_CTL_ELEM_TYPE_ENUMERATED ((__force snd_ctl_elem_type_t) 3) /* enumerated type */ +-#define SNDRV_CTL_ELEM_TYPE_BYTES ((__force snd_ctl_elem_type_t) 4) /* byte array */ +-#define SNDRV_CTL_ELEM_TYPE_IEC958 ((__force snd_ctl_elem_type_t) 5) /* IEC958 (S/PDIF) setup */ +-#define SNDRV_CTL_ELEM_TYPE_INTEGER64 ((__force snd_ctl_elem_type_t) 6) /* 64-bit integer type */ ++/* ++ * Card components can exceed the fixed 128 bytes in snd_ctl_card_info. ++ * Use SNDRV_CTL_IOCTL_CARD_BYTES with type SND_CTL_CARD_BTYPE_COMPONENTS ++ * to retrieve the full string. ++ */ ++ ++/* Type values for struct snd_ctl_card_bytes::type */ ++enum { ++ SND_CTL_CARD_BTYPE_COMPONENTS = 1, /* full card components string */ ++}; ++ ++struct snd_ctl_card_bytes { ++ __u32 type; /* SND_CTL_CARD_BTYPE_* */ ++ __u32 data_allocated; /* size of @data buffer in bytes */ ++ __u32 data_len; /* in/out: actual data length in bytes */ ++ __u32 reserved; /* explicit pad */ ++ __u64 data; /* user buffer (pointer stored as __u64) */ ++}; ++ ++typedef int snd_ctl_elem_type_t; ++#define SNDRV_CTL_ELEM_TYPE_NONE 0 /* invalid */ ++#define SNDRV_CTL_ELEM_TYPE_BOOLEAN 1 /* boolean type */ ++#define SNDRV_CTL_ELEM_TYPE_INTEGER 2 /* integer type */ ++#define SNDRV_CTL_ELEM_TYPE_ENUMERATED 3 /* enumerated type */ ++#define SNDRV_CTL_ELEM_TYPE_BYTES 4 /* byte array */ ++#define SNDRV_CTL_ELEM_TYPE_IEC958 5 /* IEC958 (S/PDIF) setup */ ++#define SNDRV_CTL_ELEM_TYPE_INTEGER64 6 /* 64-bit integer type */ + #define SNDRV_CTL_ELEM_TYPE_LAST SNDRV_CTL_ELEM_TYPE_INTEGER64 + +-typedef int __bitwise snd_ctl_elem_iface_t; +-#define SNDRV_CTL_ELEM_IFACE_CARD ((__force snd_ctl_elem_iface_t) 0) /* global control */ +-#define SNDRV_CTL_ELEM_IFACE_HWDEP ((__force snd_ctl_elem_iface_t) 1) /* hardware dependent device */ +-#define SNDRV_CTL_ELEM_IFACE_MIXER ((__force snd_ctl_elem_iface_t) 2) /* virtual mixer device */ +-#define SNDRV_CTL_ELEM_IFACE_PCM ((__force snd_ctl_elem_iface_t) 3) /* PCM device */ +-#define SNDRV_CTL_ELEM_IFACE_RAWMIDI ((__force snd_ctl_elem_iface_t) 4) /* RawMidi device */ +-#define SNDRV_CTL_ELEM_IFACE_TIMER ((__force snd_ctl_elem_iface_t) 5) /* timer device */ +-#define SNDRV_CTL_ELEM_IFACE_SEQUENCER ((__force snd_ctl_elem_iface_t) 6) /* sequencer client */ ++typedef int snd_ctl_elem_iface_t; ++#define SNDRV_CTL_ELEM_IFACE_CARD 0 /* global control */ ++#define SNDRV_CTL_ELEM_IFACE_HWDEP 1 /* hardware dependent device */ ++#define SNDRV_CTL_ELEM_IFACE_MIXER 2 /* virtual mixer device */ ++#define SNDRV_CTL_ELEM_IFACE_PCM 3 /* PCM device */ ++#define SNDRV_CTL_ELEM_IFACE_RAWMIDI 4 /* RawMidi device */ ++#define SNDRV_CTL_ELEM_IFACE_TIMER 5 /* timer device */ ++#define SNDRV_CTL_ELEM_IFACE_SEQUENCER 6 /* sequencer client */ + #define SNDRV_CTL_ELEM_IFACE_LAST SNDRV_CTL_ELEM_IFACE_SEQUENCER + + #define SNDRV_CTL_ELEM_ACCESS_READ (1<<0) +@@ -1198,6 +1217,7 @@ struct snd_ctl_tlv { + + #define SNDRV_CTL_IOCTL_PVERSION _IOR('U', 0x00, int) + #define SNDRV_CTL_IOCTL_CARD_INFO _IOR('U', 0x01, struct snd_ctl_card_info) ++#define SNDRV_CTL_IOCTL_CARD_BYTES _IOWR('U', 0x02, struct snd_ctl_card_bytes) + #define SNDRV_CTL_IOCTL_ELEM_LIST _IOWR('U', 0x10, struct snd_ctl_elem_list) + #define SNDRV_CTL_IOCTL_ELEM_INFO _IOWR('U', 0x11, struct snd_ctl_elem_info) + #define SNDRV_CTL_IOCTL_ELEM_READ _IOWR('U', 0x12, struct snd_ctl_elem_value) +diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h +--- a/include/uapi/sound/firewire.h ++++ b/include/uapi/sound/firewire.h +@@ -79,11 +79,12 @@ struct snd_firewire_event_motu_register_dsp_change { + * + * @type: Fixed to SNDRV_FIREWIRE_EVENT_FF400_MESSAGE. + * @message_count: The number of messages. ++ * @messages: Array of @message_count messages + * @messages.message: The messages expressing hardware knob operation. + * @messages.tstamp: The isochronous cycle at which the request subaction of asynchronous +- * transaction was sent to deliver the message. It has 16 bit unsigned integer ++ * transaction was sent to deliver the message. It has 16-bit unsigned integer + * value. The higher 3 bits of value expresses the lower three bits of second +- * field in the format of CYCLE_TIME, up to 7. The rest 13 bits expresses cycle ++ * field in the format of CYCLE_TIME, up to 7. The remaining 13 bits express cycle + * field up to 7999. + * + * The structure expresses message transmitted by Fireface 400 when operating hardware knob. +@@ -191,8 +192,9 @@ struct snd_firewire_motu_register_dsp_meter { + #define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_ALIGNED_INPUT_COUNT (SNDRV_FIREWIRE_MOTU_REGISTER_DSP_INPUT_COUNT + 2) + + /** +- * snd_firewire_motu_register_dsp_parameter - the container for parameters of DSP controlled +- * by register access. ++ * struct snd_firewire_motu_register_dsp_parameter - the container for parameters ++ * of DSP controlled by register access. ++ * @mixer: aggregate of @mixer.source and @mixer.output + * @mixer.source.gain: The gain of source to mixer. + * @mixer.source.pan: The L/R balance of source to mixer. + * @mixer.source.flag: The flag of source to mixer, including mute, solo. +@@ -200,21 +202,25 @@ struct snd_firewire_motu_register_dsp_meter { + * Audio Express. + * @mixer.source.paired_width: The width of paired source to mixer, only for 4 pre and + * Audio Express. ++ * @mixer.output: FIXME + * @mixer.output.paired_volume: The volume of paired output from mixer. + * @mixer.output.paired_flag: The flag of paired output from mixer. ++ * @output: output parameters + * @output.main_paired_volume: The volume of paired main output. + * @output.hp_paired_volume: The volume of paired hp output. + * @output.hp_paired_assignment: The source assigned to paired hp output. +- * @output.reserved: Padding for 32 bit alignment for future extension. ++ * @output.reserved: Padding for 32-bit alignment for future extension. ++ * @line_input: line input parameters + * @line_input.boost_flag: The flags of boost for line inputs, only for 828mk2 and Traveler. + * @line_input.nominal_level_flag: The flags of nominal level for line inputs, only for 828mk2 and + * Traveler. +- * @line_input.reserved: Padding for 32 bit alignment for future extension. ++ * @line_input.reserved: Padding for 32-bit alignment for future extension. ++ * @input: input parameters + * @input.gain_and_invert: The value including gain and invert for input, only for Ultralite, 4 pre + * and Audio Express. + * @input.flag: The flag of input; e.g. jack detection, phantom power, and pad, only for Ultralite, + * 4 pre and Audio express. +- * @reserved: Padding so that the size of structure is kept to 512 byte, but for future extension. ++ * @reserved: Padding so that the size of structure is kept to 512 bytes, but for future extension. + * + * The structure expresses the set of parameters for DSP controlled by register access. + */ +@@ -272,8 +278,8 @@ struct snd_firewire_motu_register_dsp_parameter { + * controlled by command + * @data: Signal level meters. The mapping between position and signal channel is model-dependent. + * +- * The structure expresses the part of DSP status for hardware meter. The 32 bit storage is +- * estimated to include IEEE 764 32 bit single precision floating point (binary32) value. It is ++ * The structure expresses the part of DSP status for hardware meter. The 32-bit storage is ++ * estimated to include IEEE 764 32-bit single precision floating point (binary32) value. It is + * expected to be linear value (not logarithm) for audio signal level between 0.0 and +1.0. + */ + struct snd_firewire_motu_command_dsp_meter { +diff --git a/include/uapi/sound/skl-tplg-interface.h b/include/uapi/sound/skl-tplg-interface.h +deleted file mode 100644 +--- a/include/uapi/sound/skl-tplg-interface.h ++++ /dev/null +@@ -1,168 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +-/* +- * skl-tplg-interface.h - Intel DSP FW private data interface +- * +- * Copyright (C) 2015 Intel Corp +- * Author: Jeeja KP +- * Nilofer, Samreen +- */ +- +-#ifndef __HDA_TPLG_INTERFACE_H__ +-#define __HDA_TPLG_INTERFACE_H__ +- +-#include +- +-/* +- * Default types range from 0~12. type can range from 0 to 0xff +- * SST types start at higher to avoid any overlapping in future +- */ +-#define SKL_CONTROL_TYPE_BYTE_TLV 0x100 +-#define SKL_CONTROL_TYPE_MIC_SELECT 0x102 +-#define SKL_CONTROL_TYPE_MULTI_IO_SELECT 0x103 +-#define SKL_CONTROL_TYPE_MULTI_IO_SELECT_DMIC 0x104 +- +-#define HDA_SST_CFG_MAX 900 /* size of copier cfg*/ +-#define MAX_IN_QUEUE 8 +-#define MAX_OUT_QUEUE 8 +- +-#define SKL_UUID_STR_SZ 40 +-/* Event types goes here */ +-/* Reserve event type 0 for no event handlers */ +-enum skl_event_types { +- SKL_EVENT_NONE = 0, +- SKL_MIXER_EVENT, +- SKL_MUX_EVENT, +- SKL_VMIXER_EVENT, +- SKL_PGA_EVENT +-}; +- +-/** +- * enum skl_ch_cfg - channel configuration +- * +- * @SKL_CH_CFG_MONO: One channel only +- * @SKL_CH_CFG_STEREO: L & R +- * @SKL_CH_CFG_2_1: L, R & LFE +- * @SKL_CH_CFG_3_0: L, C & R +- * @SKL_CH_CFG_3_1: L, C, R & LFE +- * @SKL_CH_CFG_QUATRO: L, R, Ls & Rs +- * @SKL_CH_CFG_4_0: L, C, R & Cs +- * @SKL_CH_CFG_5_0: L, C, R, Ls & Rs +- * @SKL_CH_CFG_5_1: L, C, R, Ls, Rs & LFE +- * @SKL_CH_CFG_DUAL_MONO: One channel replicated in two +- * @SKL_CH_CFG_I2S_DUAL_STEREO_0: Stereo(L,R) in 4 slots, 1st stream:[ L, R, -, - ] +- * @SKL_CH_CFG_I2S_DUAL_STEREO_1: Stereo(L,R) in 4 slots, 2nd stream:[ -, -, L, R ] +- * @SKL_CH_CFG_INVALID: Invalid +- */ +-enum skl_ch_cfg { +- SKL_CH_CFG_MONO = 0, +- SKL_CH_CFG_STEREO = 1, +- SKL_CH_CFG_2_1 = 2, +- SKL_CH_CFG_3_0 = 3, +- SKL_CH_CFG_3_1 = 4, +- SKL_CH_CFG_QUATRO = 5, +- SKL_CH_CFG_4_0 = 6, +- SKL_CH_CFG_5_0 = 7, +- SKL_CH_CFG_5_1 = 8, +- SKL_CH_CFG_DUAL_MONO = 9, +- SKL_CH_CFG_I2S_DUAL_STEREO_0 = 10, +- SKL_CH_CFG_I2S_DUAL_STEREO_1 = 11, +- SKL_CH_CFG_7_1 = 12, +- SKL_CH_CFG_4_CHANNEL = SKL_CH_CFG_7_1, +- SKL_CH_CFG_INVALID +-}; +- +-enum skl_module_type { +- SKL_MODULE_TYPE_MIXER = 0, +- SKL_MODULE_TYPE_COPIER, +- SKL_MODULE_TYPE_UPDWMIX, +- SKL_MODULE_TYPE_SRCINT, +- SKL_MODULE_TYPE_ALGO, +- SKL_MODULE_TYPE_BASE_OUTFMT, +- SKL_MODULE_TYPE_KPB, +- SKL_MODULE_TYPE_MIC_SELECT, +-}; +- +-enum skl_core_affinity { +- SKL_AFFINITY_CORE_0 = 0, +- SKL_AFFINITY_CORE_1, +- SKL_AFFINITY_CORE_MAX +-}; +- +-enum skl_pipe_conn_type { +- SKL_PIPE_CONN_TYPE_NONE = 0, +- SKL_PIPE_CONN_TYPE_FE, +- SKL_PIPE_CONN_TYPE_BE +-}; +- +-enum skl_hw_conn_type { +- SKL_CONN_NONE = 0, +- SKL_CONN_SOURCE = 1, +- SKL_CONN_SINK = 2 +-}; +- +-enum skl_dev_type { +- SKL_DEVICE_BT = 0x0, +- SKL_DEVICE_DMIC = 0x1, +- SKL_DEVICE_I2S = 0x2, +- SKL_DEVICE_SLIMBUS = 0x3, +- SKL_DEVICE_HDALINK = 0x4, +- SKL_DEVICE_HDAHOST = 0x5, +- SKL_DEVICE_NONE +-}; +- +-/** +- * enum skl_interleaving - interleaving style +- * +- * @SKL_INTERLEAVING_PER_CHANNEL: [s1_ch1...s1_chN,...,sM_ch1...sM_chN] +- * @SKL_INTERLEAVING_PER_SAMPLE: [s1_ch1...sM_ch1,...,s1_chN...sM_chN] +- */ +-enum skl_interleaving { +- SKL_INTERLEAVING_PER_CHANNEL = 0, +- SKL_INTERLEAVING_PER_SAMPLE = 1, +-}; +- +-enum skl_sample_type { +- SKL_SAMPLE_TYPE_INT_MSB = 0, +- SKL_SAMPLE_TYPE_INT_LSB = 1, +- SKL_SAMPLE_TYPE_INT_SIGNED = 2, +- SKL_SAMPLE_TYPE_INT_UNSIGNED = 3, +- SKL_SAMPLE_TYPE_FLOAT = 4 +-}; +- +-enum module_pin_type { +- /* All pins of the module takes same PCM inputs or outputs +- * e.g. mixout +- */ +- SKL_PIN_TYPE_HOMOGENEOUS, +- /* All pins of the module takes different PCM inputs or outputs +- * e.g mux +- */ +- SKL_PIN_TYPE_HETEROGENEOUS, +-}; +- +-enum skl_module_param_type { +- SKL_PARAM_DEFAULT = 0, +- SKL_PARAM_INIT, +- SKL_PARAM_SET, +- SKL_PARAM_BIND +-}; +- +-struct skl_dfw_algo_data { +- __u32 set_params:2; +- __u32 rsvd:30; +- __u32 param_id; +- __u32 max; +- char params[]; +-} __packed; +- +-enum skl_tkn_dir { +- SKL_DIR_IN, +- SKL_DIR_OUT +-}; +- +-enum skl_tuple_type { +- SKL_TYPE_TUPLE, +- SKL_TYPE_DATA +-}; +- +-#endif +diff --git a/include/uapi/sound/snd_ar_tokens.h b/include/uapi/sound/snd_ar_tokens.h +--- a/include/uapi/sound/snd_ar_tokens.h ++++ b/include/uapi/sound/snd_ar_tokens.h +@@ -58,7 +58,7 @@ enum ar_event_types { + #define SND_SOC_AR_TPLG_FE_BE_GRAPH_CTL_MIX 256 + #define SND_SOC_AR_TPLG_VOL_CTL 257 + +-/** ++/* + * %AR_TKN_U32_SUB_GRAPH_INSTANCE_ID: Sub Graph Instance Id + * + * %AR_TKN_U32_SUB_GRAPH_PERF_MODE: Performance mode of subgraph +@@ -168,6 +168,60 @@ enum ar_event_types { + * LOG_WAIT = 0, + * LOG_IMMEDIATELY = 1 + * ++ * %AR_TKN_U16_MODULE_SYNC_SRC: Frame sync source ++ * AR_AUDIO_IF_SYNC_SRC_EXTERNAL = 0, ++ * AR_AUDIO_IF_SYNC_SRC_INTERNAL = 1 ++ * ++ * %AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE: Enable data-out tri-state control ++ * AR_AUDIO_IF_CTRL_DATA_OE_DISABLE = 0, ++ * AR_AUDIO_IF_CTRL_DATA_OE_ENABLE = 1 ++ * ++ * %AR_TKN_U32_MODULE_SLOT_MASK: Active TDM slot bitmask ++ * ++ * %AR_TKN_U16_MODULE_NSLOTS_PER_FRAME: Number of slots per TDM frame ++ * ++ * %AR_TKN_U16_MODULE_SLOT_WIDTH: Slot width in bits (16 or 32) ++ * ++ * %AR_TKN_U16_MODULE_SYNC_MODE: Frame sync mode ++ * AR_AUDIO_IF_FRAME_SYNC_MODE_SHORT = 0, ++ * AR_AUDIO_IF_FRAME_SYNC_MODE_ONE_SLOT = 1, ++ * AR_AUDIO_IF_FRAME_SYNC_MODE_LONG = 2 ++ * ++ * %AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE: Invert frame sync pulse polarity ++ * AR_AUDIO_IF_SYNC_NORMAL = 0, ++ * AR_AUDIO_IF_SYNC_INVERTED = 1 ++ * ++ * %AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY: Data delay relative to frame sync ++ * AR_AUDIO_IF_DATA_DELAY_NONE = 0, ++ * AR_AUDIO_IF_DATA_DELAY_1_CYCLE = 1, ++ * AR_AUDIO_IF_DATA_DELAY_2_CYCLE = 2 ++ * ++ * %AR_TKN_U16_MODULE_INTF_MODE: Audio IF interface mode ++ * AR_AUDIO_IF_INTF_MODE_TDM = 0, ++ * AR_AUDIO_IF_INTF_MODE_PCM = 1, ++ * AR_AUDIO_IF_INTF_MODE_I2S = 2 ++ * ++ * %AR_TKN_U16_MODULE_QAIF_TYPE: QAIF hardware port type index ++ * AR_AUDIO_IF_QAIF = 0, ++ * AR_AUDIO_IF_QAIF_VA = 1 ++ * ++ * %AR_TKN_U32_MODULE_ACTIVE_LANE_MASK: Active lane bitmask for multi-lane ++ * ++ * %AR_TKN_U32_MODULE_FRAME_SYNC_RATE: Frame sync rate in Hz ++ * ++ * %AR_TKN_U16_MODULE_BIT_CLK_TYPE: Bit clock type ++ * AR_AUDIO_IF_BIT_CLK_INTERNAL = 0, ++ * AR_AUDIO_IF_BIT_CLK_EXTERNAL = 1, ++ * AR_AUDIO_IF_BIT_CLK_SKIP = 2 ++ * ++ * %AR_TKN_U8_MODULE_INV_INT_BIT_CLK: Invert internal bit clock ++ * AR_AUDIO_IF_CLK_NORMAL = 0, ++ * AR_AUDIO_IF_CLK_INVERTED = 1 ++ * ++ * %AR_TKN_U8_MODULE_INV_EXT_BIT_CLK: Invert external bit clock ++ * AR_AUDIO_IF_CLK_NORMAL = 0, ++ * AR_AUDIO_IF_CLK_INVERTED = 1 ++ * + * %AR_TKN_DAI_INDEX: dai index + * + */ +@@ -240,6 +294,45 @@ enum ar_event_types { + #define AR_TKN_U32_MODULE_LOG_TAP_POINT_ID 260 + #define AR_TKN_U32_MODULE_LOG_MODE 261 + ++#define AR_TKN_U16_MODULE_SYNC_SRC 262 ++#define AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE 263 ++#define AR_TKN_U32_MODULE_SLOT_MASK 264 ++#define AR_TKN_U16_MODULE_NSLOTS_PER_FRAME 265 ++#define AR_TKN_U16_MODULE_SLOT_WIDTH 266 ++#define AR_TKN_U16_MODULE_SYNC_MODE 267 ++#define AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE 268 ++#define AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY 269 ++#define AR_TKN_U16_MODULE_INTF_MODE 270 ++#define AR_TKN_U16_MODULE_QAIF_TYPE 271 ++#define AR_TKN_U32_MODULE_ACTIVE_LANE_MASK 272 ++#define AR_TKN_U32_MODULE_FRAME_SYNC_RATE 273 ++#define AR_TKN_U16_MODULE_BIT_CLK_TYPE 274 ++#define AR_TKN_U8_MODULE_INV_INT_BIT_CLK 275 ++#define AR_TKN_U8_MODULE_INV_EXT_BIT_CLK 276 ++ ++#define AR_AUDIO_IF_SYNC_SRC_EXTERNAL 0 ++#define AR_AUDIO_IF_SYNC_SRC_INTERNAL 1 ++#define AR_AUDIO_IF_CTRL_DATA_OE_DISABLE 0 ++#define AR_AUDIO_IF_CTRL_DATA_OE_ENABLE 1 ++#define AR_AUDIO_IF_INTF_MODE_TDM 0 ++#define AR_AUDIO_IF_INTF_MODE_PCM 1 ++#define AR_AUDIO_IF_INTF_MODE_I2S 2 ++#define AR_AUDIO_IF_QAIF 0 ++#define AR_AUDIO_IF_QAIF_VA 1 ++#define AR_AUDIO_IF_FRAME_SYNC_MODE_SHORT 0 ++#define AR_AUDIO_IF_FRAME_SYNC_MODE_ONE_SLOT 1 ++#define AR_AUDIO_IF_FRAME_SYNC_MODE_LONG 2 ++#define AR_AUDIO_IF_SYNC_NORMAL 0 ++#define AR_AUDIO_IF_SYNC_INVERTED 1 ++#define AR_AUDIO_IF_DATA_DELAY_NONE 0 ++#define AR_AUDIO_IF_DATA_DELAY_1_CYCLE 1 ++#define AR_AUDIO_IF_DATA_DELAY_2_CYCLE 2 ++#define AR_AUDIO_IF_BIT_CLK_INTERNAL 0 ++#define AR_AUDIO_IF_BIT_CLK_EXTERNAL 1 ++#define AR_AUDIO_IF_BIT_CLK_SKIP 2 ++#define AR_AUDIO_IF_CLK_NORMAL 0 ++#define AR_AUDIO_IF_CLK_INVERTED 1 ++ + #define SND_SOC_AR_TPLG_MODULE_CFG_TYPE 0x01001006 + struct audioreach_module_priv_data { + __le32 size; /* size in bytes of the array, including all elements */ +diff --git a/include/uapi/sound/snd_sst_tokens.h b/include/uapi/sound/snd_sst_tokens.h +deleted file mode 100644 +--- a/include/uapi/sound/snd_sst_tokens.h ++++ /dev/null +@@ -1,324 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +-/* +- * snd_sst_tokens.h - Intel SST tokens definition +- * +- * Copyright (C) 2016 Intel Corp +- * Author: Shreyas NC +- */ +-#ifndef __SND_SST_TOKENS_H__ +-#define __SND_SST_TOKENS_H__ +- +-/** +- * %SKL_TKN_UUID: Module UUID +- * +- * %SKL_TKN_U8_BLOCK_TYPE: Type of the private data block.Can be: +- * tuples, bytes, short and words +- * +- * %SKL_TKN_U8_IN_PIN_TYPE: Input pin type, +- * homogenous=0, heterogenous=1 +- * +- * %SKL_TKN_U8_OUT_PIN_TYPE: Output pin type, +- * homogenous=0, heterogenous=1 +- * %SKL_TKN_U8_DYN_IN_PIN: Configure Input pin dynamically +- * if true +- * +- * %SKL_TKN_U8_DYN_OUT_PIN: Configure Output pin dynamically +- * if true +- * +- * %SKL_TKN_U8_IN_QUEUE_COUNT: Store the number of Input pins +- * +- * %SKL_TKN_U8_OUT_QUEUE_COUNT: Store the number of Output pins +- * +- * %SKL_TKN_U8_TIME_SLOT: TDM slot number +- * +- * %SKL_TKN_U8_CORE_ID: Stores module affinity value.Can take +- * the values: +- * SKL_AFFINITY_CORE_0 = 0, +- * SKL_AFFINITY_CORE_1, +- * SKL_AFFINITY_CORE_MAX +- * +- * %SKL_TKN_U8_MOD_TYPE: Module type value. +- * +- * %SKL_TKN_U8_CONN_TYPE: Module connection type can be a FE, +- * BE or NONE as defined : +- * SKL_PIPE_CONN_TYPE_NONE = 0, +- * SKL_PIPE_CONN_TYPE_FE = 1 (HOST_DMA) +- * SKL_PIPE_CONN_TYPE_BE = 2 (LINK_DMA) +- * +- * %SKL_TKN_U8_DEV_TYPE: Type of device to which the module is +- * connected +- * Can take the values: +- * SKL_DEVICE_BT = 0x0, +- * SKL_DEVICE_DMIC = 0x1, +- * SKL_DEVICE_I2S = 0x2, +- * SKL_DEVICE_SLIMBUS = 0x3, +- * SKL_DEVICE_HDALINK = 0x4, +- * SKL_DEVICE_HDAHOST = 0x5, +- * SKL_DEVICE_NONE +- * +- * %SKL_TKN_U8_HW_CONN_TYPE: Connection type of the HW to which the +- * module is connected +- * SKL_CONN_NONE = 0, +- * SKL_CONN_SOURCE = 1, +- * SKL_CONN_SINK = 2 +- * +- * %SKL_TKN_U16_PIN_INST_ID: Stores the pin instance id +- * +- * %SKL_TKN_U16_MOD_INST_ID: Stores the mdule instance id +- * +- * %SKL_TKN_U32_MAX_MCPS: Module max mcps value +- * +- * %SKL_TKN_U32_MEM_PAGES: Module resource pages +- * +- * %SKL_TKN_U32_OBS: Stores Output Buffer size +- * +- * %SKL_TKN_U32_IBS: Stores input buffer size +- * +- * %SKL_TKN_U32_VBUS_ID: Module VBUS_ID. PDM=0, SSP0=0, +- * SSP1=1,SSP2=2, +- * SSP3=3, SSP4=4, +- * SSP5=5, SSP6=6,INVALID +- * +- * %SKL_TKN_U32_PARAMS_FIXUP: Module Params fixup mask +- * %SKL_TKN_U32_CONVERTER: Module params converter mask +- * %SKL_TKN_U32_PIPE_ID: Stores the pipe id +- * +- * %SKL_TKN_U32_PIPE_CONN_TYPE: Type of the token to which the pipe is +- * connected to. It can be +- * SKL_PIPE_CONN_TYPE_NONE = 0, +- * SKL_PIPE_CONN_TYPE_FE = 1 (HOST_DMA), +- * SKL_PIPE_CONN_TYPE_BE = 2 (LINK_DMA), +- * +- * %SKL_TKN_U32_PIPE_PRIORITY: Pipe priority value +- * %SKL_TKN_U32_PIPE_MEM_PGS: Pipe resource pages +- * +- * %SKL_TKN_U32_DIR_PIN_COUNT: Value for the direction to set input/output +- * formats and the pin count. +- * The first 4 bits have the direction +- * value and the next 4 have +- * the pin count value. +- * SKL_DIR_IN = 0, SKL_DIR_OUT = 1. +- * The input and output formats +- * share the same set of tokens +- * with the distinction between input +- * and output made by reading direction +- * token. +- * +- * %SKL_TKN_U32_FMT_CH: Supported channel count +- * +- * %SKL_TKN_U32_FMT_FREQ: Supported frequency/sample rate +- * +- * %SKL_TKN_U32_FMT_BIT_DEPTH: Supported container size +- * +- * %SKL_TKN_U32_FMT_SAMPLE_SIZE:Number of samples in the container +- * +- * %SKL_TKN_U32_FMT_CH_CONFIG: Supported channel configurations for the +- * input/output. +- * +- * %SKL_TKN_U32_FMT_INTERLEAVE: Interleaving style which can be per +- * channel or per sample. The values can be : +- * SKL_INTERLEAVING_PER_CHANNEL = 0, +- * SKL_INTERLEAVING_PER_SAMPLE = 1, +- * +- * %SKL_TKN_U32_FMT_SAMPLE_TYPE: +- * Specifies the sample type. Can take the +- * values: SKL_SAMPLE_TYPE_INT_MSB = 0, +- * SKL_SAMPLE_TYPE_INT_LSB = 1, +- * SKL_SAMPLE_TYPE_INT_SIGNED = 2, +- * SKL_SAMPLE_TYPE_INT_UNSIGNED = 3, +- * SKL_SAMPLE_TYPE_FLOAT = 4 +- * +- * %SKL_TKN_U32_CH_MAP: Channel map values +- * %SKL_TKN_U32_MOD_SET_PARAMS: It can take these values: +- * SKL_PARAM_DEFAULT, SKL_PARAM_INIT, +- * SKL_PARAM_SET, SKL_PARAM_BIND +- * +- * %SKL_TKN_U32_MOD_PARAM_ID: ID of the module params +- * +- * %SKL_TKN_U32_CAPS_SET_PARAMS: +- * Set params value +- * +- * %SKL_TKN_U32_CAPS_PARAMS_ID: Params ID +- * +- * %SKL_TKN_U32_CAPS_SIZE: Caps size +- * +- * %SKL_TKN_U32_PROC_DOMAIN: Specify processing domain +- * +- * %SKL_TKN_U32_LIB_COUNT: Specifies the number of libraries +- * +- * %SKL_TKN_STR_LIB_NAME: Specifies the library name +- * +- * %SKL_TKN_U32_PMODE: Specifies the power mode for pipe +- * +- * %SKL_TKL_U32_D0I3_CAPS: Specifies the D0i3 capability for module +- * +- * %SKL_TKN_U32_DMA_BUF_SIZE: DMA buffer size in millisec +- * +- * %SKL_TKN_U32_PIPE_DIR: Specifies pipe direction. Can be +- * playback/capture. +- * +- * %SKL_TKN_U32_NUM_CONFIGS: Number of pipe configs +- * +- * %SKL_TKN_U32_PATH_MEM_PGS: Size of memory (in pages) required for pipeline +- * and its data +- * +- * %SKL_TKN_U32_PIPE_CONFIG_ID: Config id for the modules in the pipe +- * and PCM params supported by that pipe +- * config. This is used as index to fill +- * up the pipe config and module config +- * structure. +- * +- * %SKL_TKN_U32_CFG_FREQ: +- * %SKL_TKN_U8_CFG_CHAN: +- * %SKL_TKN_U8_CFG_BPS: PCM params (freq, channels, bits per sample) +- * supported for each of the pipe configs. +- * +- * %SKL_TKN_CFG_MOD_RES_ID: Module's resource index for each of the +- * pipe config +- * +- * %SKL_TKN_CFG_MOD_FMT_ID: Module's interface index for each of the +- * pipe config +- * +- * %SKL_TKN_U8_NUM_MOD: Number of modules in the manifest +- * +- * %SKL_TKN_MM_U8_MOD_IDX: Current index of the module in the manifest +- * +- * %SKL_TKN_MM_U8_NUM_RES: Number of resources for the module +- * +- * %SKL_TKN_MM_U8_NUM_INTF: Number of interfaces for the module +- * +- * %SKL_TKN_MM_U32_RES_ID: Resource index for the resource info to +- * be filled into. +- * A module can support multiple resource +- * configuration and is represnted as a +- * resource table. This index is used to +- * fill information into appropriate index. +- * +- * %SKL_TKN_MM_U32_CPS: DSP cycles per second +- * +- * %SKL_TKN_MM_U32_DMA_SIZE: Allocated buffer size for gateway DMA +- * +- * %SKL_TKN_MM_U32_CPC: DSP cycles allocated per frame +- * +- * %SKL_TKN_MM_U32_RES_PIN_ID: Resource pin index in the module +- * +- * %SKL_TKN_MM_U32_INTF_PIN_ID: Interface index in the module +- * +- * %SKL_TKN_MM_U32_PIN_BUF: Buffer size of the module pin +- * +- * %SKL_TKN_MM_U32_FMT_ID: Format index for each of the interface/ +- * format information to be filled into. +- * +- * %SKL_TKN_MM_U32_NUM_IN_FMT: Number of input formats +- * %SKL_TKN_MM_U32_NUM_OUT_FMT: Number of output formats +- * +- * %SKL_TKN_U32_ASTATE_IDX: Table Index for the A-State entry to be filled +- * with kcps and clock source +- * +- * %SKL_TKN_U32_ASTATE_COUNT: Number of valid entries in A-State table +- * +- * %SKL_TKN_U32_ASTATE_KCPS: Specifies the core load threshold (in kilo +- * cycles per second) below which DSP is clocked +- * from source specified by clock source. +- * +- * %SKL_TKN_U32_ASTATE_CLK_SRC: Clock source for A-State entry +- * +- * %SKL_TKN_U32_FMT_CFG_IDX: Format config index +- * +- * module_id and loadable flags dont have tokens as these values will be +- * read from the DSP FW manifest +- * +- * Tokens defined can be used either in the manifest or widget private data. +- * +- * SKL_TKN_MM is used as a suffix for all tokens that represent +- * module data in the manifest. +- */ +-enum SKL_TKNS { +- SKL_TKN_UUID = 1, +- SKL_TKN_U8_NUM_BLOCKS, +- SKL_TKN_U8_BLOCK_TYPE, +- SKL_TKN_U8_IN_PIN_TYPE, +- SKL_TKN_U8_OUT_PIN_TYPE, +- SKL_TKN_U8_DYN_IN_PIN, +- SKL_TKN_U8_DYN_OUT_PIN, +- SKL_TKN_U8_IN_QUEUE_COUNT, +- SKL_TKN_U8_OUT_QUEUE_COUNT, +- SKL_TKN_U8_TIME_SLOT, +- SKL_TKN_U8_CORE_ID, +- SKL_TKN_U8_MOD_TYPE, +- SKL_TKN_U8_CONN_TYPE, +- SKL_TKN_U8_DEV_TYPE, +- SKL_TKN_U8_HW_CONN_TYPE, +- SKL_TKN_U16_MOD_INST_ID, +- SKL_TKN_U16_BLOCK_SIZE, +- SKL_TKN_U32_MAX_MCPS, +- SKL_TKN_U32_MEM_PAGES, +- SKL_TKN_U32_OBS, +- SKL_TKN_U32_IBS, +- SKL_TKN_U32_VBUS_ID, +- SKL_TKN_U32_PARAMS_FIXUP, +- SKL_TKN_U32_CONVERTER, +- SKL_TKN_U32_PIPE_ID, +- SKL_TKN_U32_PIPE_CONN_TYPE, +- SKL_TKN_U32_PIPE_PRIORITY, +- SKL_TKN_U32_PIPE_MEM_PGS, +- SKL_TKN_U32_DIR_PIN_COUNT, +- SKL_TKN_U32_FMT_CH, +- SKL_TKN_U32_FMT_FREQ, +- SKL_TKN_U32_FMT_BIT_DEPTH, +- SKL_TKN_U32_FMT_SAMPLE_SIZE, +- SKL_TKN_U32_FMT_CH_CONFIG, +- SKL_TKN_U32_FMT_INTERLEAVE, +- SKL_TKN_U32_FMT_SAMPLE_TYPE, +- SKL_TKN_U32_FMT_CH_MAP, +- SKL_TKN_U32_PIN_MOD_ID, +- SKL_TKN_U32_PIN_INST_ID, +- SKL_TKN_U32_MOD_SET_PARAMS, +- SKL_TKN_U32_MOD_PARAM_ID, +- SKL_TKN_U32_CAPS_SET_PARAMS, +- SKL_TKN_U32_CAPS_PARAMS_ID, +- SKL_TKN_U32_CAPS_SIZE, +- SKL_TKN_U32_PROC_DOMAIN, +- SKL_TKN_U32_LIB_COUNT, +- SKL_TKN_STR_LIB_NAME, +- SKL_TKN_U32_PMODE, +- SKL_TKL_U32_D0I3_CAPS, /* Typo added at v4.10 */ +- SKL_TKN_U32_D0I3_CAPS = SKL_TKL_U32_D0I3_CAPS, +- SKL_TKN_U32_DMA_BUF_SIZE, +- +- SKL_TKN_U32_PIPE_DIRECTION, +- SKL_TKN_U32_PIPE_CONFIG_ID, +- SKL_TKN_U32_NUM_CONFIGS, +- SKL_TKN_U32_PATH_MEM_PGS, +- +- SKL_TKN_U32_CFG_FREQ, +- SKL_TKN_U8_CFG_CHAN, +- SKL_TKN_U8_CFG_BPS, +- SKL_TKN_CFG_MOD_RES_ID, +- SKL_TKN_CFG_MOD_FMT_ID, +- SKL_TKN_U8_NUM_MOD, +- +- SKL_TKN_MM_U8_MOD_IDX, +- SKL_TKN_MM_U8_NUM_RES, +- SKL_TKN_MM_U8_NUM_INTF, +- SKL_TKN_MM_U32_RES_ID, +- SKL_TKN_MM_U32_CPS, +- SKL_TKN_MM_U32_DMA_SIZE, +- SKL_TKN_MM_U32_CPC, +- SKL_TKN_MM_U32_RES_PIN_ID, +- SKL_TKN_MM_U32_INTF_PIN_ID, +- SKL_TKN_MM_U32_PIN_BUF, +- SKL_TKN_MM_U32_FMT_ID, +- SKL_TKN_MM_U32_NUM_IN_FMT, +- SKL_TKN_MM_U32_NUM_OUT_FMT, +- +- SKL_TKN_U32_ASTATE_IDX, +- SKL_TKN_U32_ASTATE_COUNT, +- SKL_TKN_U32_ASTATE_KCPS, +- SKL_TKN_U32_ASTATE_CLK_SRC, +- +- SKL_TKN_U32_FMT_CFG_IDX = 96, +- SKL_TKN_MAX = SKL_TKN_U32_FMT_CFG_IDX, +-}; +- +-#endif +diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h +--- a/include/uapi/sound/sof/tokens.h ++++ b/include/uapi/sound/sof/tokens.h +@@ -111,8 +111,8 @@ + + #define SOF_TKN_COMP_SCHED_DOMAIN 418 + #define SOF_TKN_COMP_DOMAIN_ID 419 +-#define SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT 420 +-#define SOF_TKN_COMP_STACK_BYTES_REQUIREMENT 421 ++#define SOF_TKN_COMP_STACK_BYTES_REQUIREMENT 420 ++#define SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT 421 + + /* SSP */ + #define SOF_TKN_INTEL_SSP_CLKS_CONTROL 500 +@@ -223,6 +223,7 @@ + #define SOF_TKN_AMD_ACPI2S_RATE 1700 + #define SOF_TKN_AMD_ACPI2S_CH 1701 + #define SOF_TKN_AMD_ACPI2S_TDM_MODE 1702 ++#define SOF_TKN_AMD_ACPI2S_FORMAT 1703 + + /* MICFIL PDM */ + #define SOF_TKN_IMX_MICFIL_RATE 2000 +diff --git a/sound/atmel/ac97c.c b/sound/atmel/ac97c.c +--- a/sound/atmel/ac97c.c ++++ b/sound/atmel/ac97c.c +@@ -693,7 +693,7 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + struct device *dev = &pdev->dev; + struct snd_card *card; + struct atmel_ac97c *chip; +- struct resource *regs; ++ void __iomem *regs; + struct clk *pclk; + static const struct snd_ac97_bus_ops ops = { + .write = atmel_ac97c_write, +@@ -702,42 +702,34 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + int retval; + int irq; + +- regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- if (!regs) { +- dev_dbg(&pdev->dev, "no memory resource\n"); +- return -ENXIO; +- } ++ regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); + + irq = platform_get_irq(pdev, 0); +- if (irq < 0) { +- dev_dbg(&pdev->dev, "could not get irq: %d\n", irq); ++ if (irq < 0) + return irq; +- } + +- pclk = clk_get(&pdev->dev, "ac97_clk"); ++ pclk = devm_clk_get_enabled(&pdev->dev, "ac97_clk"); + if (IS_ERR(pclk)) { + dev_dbg(&pdev->dev, "no peripheral clock\n"); + return PTR_ERR(pclk); + } +- retval = clk_prepare_enable(pclk); +- if (retval) +- goto err_prepare_enable; + +- retval = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, ++ retval = snd_devm_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, THIS_MODULE, + sizeof(struct atmel_ac97c), &card); + if (retval) { + dev_dbg(&pdev->dev, "could not create sound card device\n"); +- goto err_snd_card_new; ++ return retval; + } + + chip = get_chip(card); + +- retval = request_irq(irq, atmel_ac97c_interrupt, 0, "AC97C", chip); +- if (retval) { +- dev_dbg(&pdev->dev, "unable to request irq %d\n", irq); +- goto err_request_irq; +- } ++ retval = devm_request_irq(&pdev->dev, irq, atmel_ac97c_interrupt, 0, "AC97C", chip); ++ if (retval) ++ return retval; ++ + chip->irq = irq; + + spin_lock_init(&chip->lock); +@@ -749,13 +741,7 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + chip->card = card; + chip->pclk = pclk; + chip->pdev = pdev; +- chip->regs = ioremap(regs->start, resource_size(regs)); +- +- if (!chip->regs) { +- dev_dbg(&pdev->dev, "could not remap register memory\n"); +- retval = -ENOMEM; +- goto err_ioremap; +- } ++ chip->regs = regs; + + chip->reset_pin = devm_gpiod_get_index(dev, "ac97", 2, GPIOD_OUT_HIGH); + if (IS_ERR(chip->reset_pin)) +@@ -770,25 +756,25 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + retval = snd_ac97_bus(card, 0, &ops, chip, &chip->ac97_bus); + if (retval) { + dev_dbg(&pdev->dev, "could not register on ac97 bus\n"); +- goto err_ac97_bus; ++ return retval; + } + + retval = atmel_ac97c_mixer_new(chip); + if (retval) { + dev_dbg(&pdev->dev, "could not register ac97 mixer\n"); +- goto err_ac97_bus; ++ return retval; + } + + retval = atmel_ac97c_pcm_new(chip); + if (retval) { + dev_dbg(&pdev->dev, "could not register ac97 pcm device\n"); +- goto err_ac97_bus; ++ return retval; + } + + retval = snd_card_register(card); + if (retval) { + dev_dbg(&pdev->dev, "could not register sound card\n"); +- goto err_ac97_bus; ++ return retval; + } + + platform_set_drvdata(pdev, card); +@@ -797,18 +783,6 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + chip->regs, irq); + + return 0; +- +-err_ac97_bus: +- iounmap(chip->regs); +-err_ioremap: +- free_irq(irq, chip); +-err_request_irq: +- snd_card_free(card); +-err_snd_card_new: +- clk_disable_unprepare(pclk); +-err_prepare_enable: +- clk_put(pclk); +- return retval; + } + + static int atmel_ac97c_suspend(struct device *pdev) +@@ -839,13 +813,6 @@ static void atmel_ac97c_remove(struct platform_device *pdev) + ac97c_writel(chip, CAMR, 0); + ac97c_writel(chip, COMR, 0); + ac97c_writel(chip, MR, 0); +- +- clk_disable_unprepare(chip->pclk); +- clk_put(chip->pclk); +- iounmap(chip->regs); +- free_irq(chip->irq, chip); +- +- snd_card_free(card); + } + + static struct platform_driver atmel_ac97c_driver = { +diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c +--- a/sound/core/compress_offload.c ++++ b/sound/core/compress_offload.c +@@ -1393,17 +1393,17 @@ static long snd_compr_ioctl_compat(struct file *file, unsigned int cmd, + #endif + + static const struct file_operations snd_compr_file_ops = { +- .owner = THIS_MODULE, +- .open = snd_compr_open, +- .release = snd_compr_free, +- .write = snd_compr_write, +- .read = snd_compr_read, +- .unlocked_ioctl = snd_compr_ioctl, ++ .owner = THIS_MODULE, ++ .open = snd_compr_open, ++ .release = snd_compr_free, ++ .write = snd_compr_write, ++ .read = snd_compr_read, ++ .unlocked_ioctl = snd_compr_ioctl, + #ifdef CONFIG_COMPAT +- .compat_ioctl = snd_compr_ioctl_compat, ++ .compat_ioctl = snd_compr_ioctl_compat, + #endif +- .mmap = snd_compr_mmap, +- .poll = snd_compr_poll, ++ .mmap = snd_compr_mmap, ++ .poll = snd_compr_poll, + }; + + static int snd_compress_dev_register(struct snd_device *device) +diff --git a/sound/core/control.c b/sound/core/control.c +--- a/sound/core/control.c ++++ b/sound/core/control.c +@@ -38,7 +38,7 @@ struct snd_kctl_ioctl { + snd_kctl_ioctl_func_t fioctl; + }; + +-static DECLARE_RWSEM(snd_ioctl_rwsem); ++DECLARE_RWSEM(snd_ioctl_rwsem); + static DECLARE_RWSEM(snd_ctl_layer_rwsem); + static LIST_HEAD(snd_control_ioctls); + #ifdef CONFIG_COMPAT +@@ -55,9 +55,7 @@ static int snd_ctl_open(struct inode *inode, struct file *file) + struct snd_ctl_file *ctl; + int i, err; + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + card = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_CONTROL); + if (!card) { +@@ -65,12 +63,10 @@ static int snd_ctl_open(struct inode *inode, struct file *file) + goto __error1; + } + err = snd_card_file_add(card, file); +- if (err < 0) { +- err = -ENODEV; ++ if (err < 0) + goto __error1; +- } + if (!try_module_get(card->module)) { +- err = -EFAULT; ++ err = -ENODEV; + goto __error2; + } + ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); +@@ -876,23 +872,81 @@ static int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl, + { + struct snd_ctl_card_info *info __free(kfree) = + kzalloc(sizeof(*info), GFP_KERNEL); ++ ssize_t n; + + if (! info) + return -ENOMEM; ++ ++ static_assert(sizeof(info->components) >= 2); ++ + scoped_guard(rwsem_read, &snd_ioctl_rwsem) { ++ const char *components = card->components; ++ ++ if (!components) ++ components = ""; ++ + info->card = card->number; + strscpy(info->id, card->id, sizeof(info->id)); + strscpy(info->driver, card->driver, sizeof(info->driver)); + strscpy(info->name, card->shortname, sizeof(info->name)); + strscpy(info->longname, card->longname, sizeof(info->longname)); + strscpy(info->mixername, card->mixername, sizeof(info->mixername)); +- strscpy(info->components, card->components, sizeof(info->components)); ++ n = strscpy(info->components, components, sizeof(info->components)); ++ if (n < 0) // mark the truncation with '>' before NULL terminator ++ info->components[sizeof(info->components) - 2] = '>'; + } + if (copy_to_user(arg, info, sizeof(struct snd_ctl_card_info))) + return -EFAULT; + return 0; + } + ++static int snd_ctl_card_bytes(struct snd_card *card, ++ struct snd_ctl_card_bytes *info, ++ unsigned int __user *data_len_out) ++{ ++ unsigned int data_len; ++ ++ switch (info->type) { ++ case SND_CTL_CARD_BTYPE_COMPONENTS: ++ scoped_guard(rwsem_read, &snd_ioctl_rwsem) { ++ const char *components = card->components; ++ ++ if (!components) ++ components = ""; ++ ++ data_len = strlen(components) + 1; ++ ++ if (!info->data || info->data_allocated == 0) ++ break; ++ ++ if (info->data_allocated < data_len) ++ return -ENOMEM; ++ ++ if (copy_to_user(u64_to_user_ptr(info->data), components, data_len)) ++ return -EFAULT; ++ } ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ if (put_user(data_len, data_len_out)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static int snd_ctl_card_bytes_user(struct snd_card *card, ++ struct snd_ctl_card_bytes __user *_info) ++{ ++ struct snd_ctl_card_bytes info; ++ ++ if (copy_from_user(&info, _info, sizeof(info))) ++ return -EFAULT; ++ ++ return snd_ctl_card_bytes(card, &info, &_info->data_len); ++} ++ + static int snd_ctl_elem_list(struct snd_card *card, + struct snd_ctl_elem_list *list) + { +@@ -1990,6 +2044,8 @@ static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg + return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0; + case SNDRV_CTL_IOCTL_CARD_INFO: + return snd_ctl_card_info(card, ctl, cmd, argp); ++ case SNDRV_CTL_IOCTL_CARD_BYTES: ++ return snd_ctl_card_bytes_user(card, argp); + case SNDRV_CTL_IOCTL_ELEM_LIST: + return snd_ctl_elem_list_user(card, argp); + case SNDRV_CTL_IOCTL_ELEM_INFO: +@@ -2339,16 +2395,15 @@ EXPORT_SYMBOL_GPL(snd_ctl_disconnect_layer); + * INIT PART + */ + +-static const struct file_operations snd_ctl_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = snd_ctl_read, +- .open = snd_ctl_open, +- .release = snd_ctl_release, +- .poll = snd_ctl_poll, +- .unlocked_ioctl = snd_ctl_ioctl, +- .compat_ioctl = snd_ctl_ioctl_compat, +- .fasync = snd_ctl_fasync, ++static const struct file_operations snd_ctl_f_ops = { ++ .owner = THIS_MODULE, ++ .read = snd_ctl_read, ++ .open = snd_ctl_open, ++ .release = snd_ctl_release, ++ .poll = snd_ctl_poll, ++ .unlocked_ioctl = snd_ctl_ioctl, ++ .compat_ioctl = snd_ctl_ioctl_compat, ++ .fasync = snd_ctl_fasync, + }; + + /* call lops under rwsems; called from snd_ctl_dev_*() below() */ +diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c +--- a/sound/core/control_compat.c ++++ b/sound/core/control_compat.c +@@ -226,8 +226,8 @@ static int copy_ctl_value_from_user(struct snd_card *card, + if (type < 0) + return type; + +- if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || +- type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { ++ if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || ++ type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + s32 __user *intp = valuep; + int val; +@@ -236,7 +236,7 @@ static int copy_ctl_value_from_user(struct snd_card *card, + data->value.integer.value[i] = val; + } + } else { +- size = get_elem_size((__force snd_ctl_elem_type_t)type, count); ++ size = get_elem_size(type, count); + if (size < 0) { + dev_err(card->dev, "snd_ioctl32_ctl_elem_value: unknown type %d\n", type); + return -EINVAL; +@@ -259,8 +259,8 @@ static int copy_ctl_value_to_user(void __user *userdata, + struct snd_ctl_elem_value32 __user *data32 = userdata; + int i, size; + +- if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || +- type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { ++ if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || ++ type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + s32 __user *intp = valuep; + int val; +@@ -269,7 +269,7 @@ static int copy_ctl_value_to_user(void __user *userdata, + return -EFAULT; + } + } else { +- size = get_elem_size((__force snd_ctl_elem_type_t)type, count); ++ size = get_elem_size(type, count); + if (copy_to_user(valuep, data->value.bytes.data, size)) + return -EFAULT; + } +@@ -417,7 +417,7 @@ static int snd_ctl_elem_add_compat(struct snd_ctl_file *file, + break; + } + return snd_ctl_elem_add(file, data, replace); +-} ++} + + enum { + SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct snd_ctl_elem_list32), +@@ -446,6 +446,7 @@ static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, uns + switch (cmd) { + case SNDRV_CTL_IOCTL_PVERSION: + case SNDRV_CTL_IOCTL_CARD_INFO: ++ case SNDRV_CTL_IOCTL_CARD_BYTES: + case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: + case SNDRV_CTL_IOCTL_POWER: + case SNDRV_CTL_IOCTL_POWER_STATE: +diff --git a/sound/core/hwdep.c b/sound/core/hwdep.c +--- a/sound/core/hwdep.c ++++ b/sound/core/hwdep.c +@@ -87,7 +87,7 @@ static int snd_hwdep_open(struct inode *inode, struct file * file) + + if (!try_module_get(hw->card->module)) { + snd_card_unref(hw->card); +- return -EFAULT; ++ return -ENODEV; + } + + init_waitqueue_entry(&wait, current); +@@ -323,18 +323,17 @@ static int snd_hwdep_control_ioctl(struct snd_card *card, + + */ + +-static const struct file_operations snd_hwdep_f_ops = +-{ +- .owner = THIS_MODULE, +- .llseek = snd_hwdep_llseek, +- .read = snd_hwdep_read, +- .write = snd_hwdep_write, +- .open = snd_hwdep_open, +- .release = snd_hwdep_release, +- .poll = snd_hwdep_poll, +- .unlocked_ioctl = snd_hwdep_ioctl, +- .compat_ioctl = snd_hwdep_ioctl_compat, +- .mmap = snd_hwdep_mmap, ++static const struct file_operations snd_hwdep_f_ops = { ++ .owner = THIS_MODULE, ++ .llseek = snd_hwdep_llseek, ++ .read = snd_hwdep_read, ++ .write = snd_hwdep_write, ++ .open = snd_hwdep_open, ++ .release = snd_hwdep_release, ++ .poll = snd_hwdep_poll, ++ .unlocked_ioctl = snd_hwdep_ioctl, ++ .compat_ioctl = snd_hwdep_ioctl_compat, ++ .mmap = snd_hwdep_mmap, + }; + + static void snd_hwdep_free(struct snd_hwdep *hwdep) +diff --git a/sound/core/info.c b/sound/core/info.c +--- a/sound/core/info.c ++++ b/sound/core/info.c +@@ -78,7 +78,7 @@ static int alloc_info_private(struct snd_info_entry *entry, + if (!entry || !entry->p) + return -ENODEV; + if (!try_module_get(entry->module)) +- return -EFAULT; ++ return -ENODEV; + data = kzalloc_obj(*data); + if (!data) { + module_put(entry->module); +diff --git a/sound/core/init.c b/sound/core/init.c +--- a/sound/core/init.c ++++ b/sound/core/init.c +@@ -466,20 +466,19 @@ static int snd_disconnect_fasync(int fd, struct file *file, int on) + return -ENODEV; + } + +-static const struct file_operations snd_shutdown_f_ops = +-{ +- .owner = THIS_MODULE, +- .llseek = snd_disconnect_llseek, +- .read = snd_disconnect_read, +- .write = snd_disconnect_write, +- .release = snd_disconnect_release, +- .poll = snd_disconnect_poll, +- .unlocked_ioctl = snd_disconnect_ioctl, ++static const struct file_operations snd_shutdown_f_ops = { ++ .owner = THIS_MODULE, ++ .llseek = snd_disconnect_llseek, ++ .read = snd_disconnect_read, ++ .write = snd_disconnect_write, ++ .release = snd_disconnect_release, ++ .poll = snd_disconnect_poll, ++ .unlocked_ioctl = snd_disconnect_ioctl, + #ifdef CONFIG_COMPAT +- .compat_ioctl = snd_disconnect_ioctl, ++ .compat_ioctl = snd_disconnect_ioctl, + #endif +- .mmap = snd_disconnect_mmap, +- .fasync = snd_disconnect_fasync ++ .mmap = snd_disconnect_mmap, ++ .fasync = snd_disconnect_fasync + }; + + /** +@@ -584,12 +583,17 @@ EXPORT_SYMBOL_GPL(snd_card_disconnect_sync); + + static int snd_card_do_free(struct snd_card *card) + { ++ bool managed = card->managed; ++ + card->releasing = true; + #if IS_ENABLED(CONFIG_SND_MIXER_OSS) + if (snd_mixer_oss_notify_callback) + snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); + #endif + snd_device_free_all(card); ++ kfree(card->components); ++ card->components = NULL; ++ card->components_alloc_size = 0; + if (card->private_free) + card->private_free(card); + #ifdef CONFIG_SND_CTL_DEBUG +@@ -601,7 +605,7 @@ static int snd_card_do_free(struct snd_card *card) + } + if (card->release_completion) + complete(card->release_completion); +- if (!card->managed) ++ if (!managed) + kfree(card); + return 0; + } +@@ -722,7 +726,7 @@ static void snd_card_set_id_no_lock(struct snd_card *card, const char *src, + int len, loops; + bool is_default = false; + char *id; +- ++ + copy_valid_id_string(card, src, nid); + id = card->id; + +@@ -1031,21 +1035,44 @@ int __init snd_card_info_init(void) + * + * Return: Zero otherwise a negative error code. + */ +- ++ + int snd_component_add(struct snd_card *card, const char *component) + { + char *ptr; + int len = strlen(component); ++ unsigned int cur_len, need_len; + +- ptr = strstr(card->components, component); +- if (ptr != NULL) { +- if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */ +- return 1; ++ guard(rwsem_write)(&snd_ioctl_rwsem); ++ ++ if (card->components) { ++ ptr = strstr(card->components, component); ++ if (ptr) { ++ if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */ ++ return 1; ++ } ++ cur_len = strlen(card->components) + 1; ++ } else { ++ cur_len = 0; + } +- if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) { ++ ++ need_len = cur_len + len + 1; ++ if (need_len > 512) { + snd_BUG(); + return -ENOMEM; + } ++ ++ if (need_len > card->components_alloc_size) { ++ unsigned int new_alloc = roundup(need_len, 32); ++ ++ ptr = krealloc(card->components, new_alloc, GFP_KERNEL); ++ if (!ptr) ++ return -ENOMEM; ++ if (!card->components) ++ ptr[0] = '\0'; ++ card->components = ptr; ++ card->components_alloc_size = new_alloc; ++ } ++ + if (card->components[0] != '\0') + strcat(card->components, " "); + strcat(card->components, component); +diff --git a/sound/core/jack.c b/sound/core/jack.c +--- a/sound/core/jack.c ++++ b/sound/core/jack.c +@@ -303,41 +303,41 @@ static ssize_t jack_type_read(struct file *file, + } + + static const struct file_operations jack_type_fops = { +- .open = simple_open, +- .read = jack_type_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_type_read, ++ .llseek = default_llseek, + }; + #endif + + static const struct file_operations sw_inject_enable_fops = { +- .open = simple_open, +- .read = sw_inject_enable_read, +- .write = sw_inject_enable_write, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = sw_inject_enable_read, ++ .write = sw_inject_enable_write, ++ .llseek = default_llseek, + }; + + static const struct file_operations jackin_inject_fops = { +- .open = simple_open, +- .write = jackin_inject_write, +- .llseek = default_llseek, ++ .open = simple_open, ++ .write = jackin_inject_write, ++ .llseek = default_llseek, + }; + + static const struct file_operations jack_kctl_id_fops = { +- .open = simple_open, +- .read = jack_kctl_id_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_kctl_id_read, ++ .llseek = default_llseek, + }; + + static const struct file_operations jack_kctl_mask_bits_fops = { +- .open = simple_open, +- .read = jack_kctl_mask_bits_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_kctl_mask_bits_read, ++ .llseek = default_llseek, + }; + + static const struct file_operations jack_kctl_status_fops = { +- .open = simple_open, +- .read = jack_kctl_status_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_kctl_status_read, ++ .llseek = default_llseek, + }; + + static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, +diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c +--- a/sound/core/oss/mixer_oss.c ++++ b/sound/core/oss/mixer_oss.c +@@ -30,9 +30,7 @@ static int snd_mixer_oss_open(struct inode *inode, struct file *file) + struct snd_mixer_oss_file *fmixer; + int err; + +- err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ nonseekable_open(inode, file); + + card = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_MIXER); +@@ -60,7 +58,7 @@ static int snd_mixer_oss_open(struct inode *inode, struct file *file) + kfree(fmixer); + snd_card_file_remove(card, file); + snd_card_unref(card); +- return -EFAULT; ++ return -ENODEV; + } + snd_card_unref(card); + return 0; +@@ -407,13 +405,12 @@ static long snd_mixer_oss_ioctl_compat(struct file *file, unsigned int cmd, + * REGISTRATION PART + */ + +-static const struct file_operations snd_mixer_oss_f_ops = +-{ +- .owner = THIS_MODULE, +- .open = snd_mixer_oss_open, +- .release = snd_mixer_oss_release, +- .unlocked_ioctl = snd_mixer_oss_ioctl, +- .compat_ioctl = snd_mixer_oss_ioctl_compat, ++static const struct file_operations snd_mixer_oss_f_ops = { ++ .owner = THIS_MODULE, ++ .open = snd_mixer_oss_open, ++ .release = snd_mixer_oss_release, ++ .unlocked_ioctl = snd_mixer_oss_ioctl, ++ .compat_ioctl = snd_mixer_oss_ioctl_compat, + }; + + /* +diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c +--- a/sound/core/oss/pcm_oss.c ++++ b/sound/core/oss/pcm_oss.c +@@ -879,11 +879,11 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + _snd_pcm_hw_param_min(sparams, SNDRV_PCM_HW_PARAM_PERIODS, 2, 0); + snd_mask_none(&mask); + if (atomic_read(&substream->mmap_count)) +- snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); ++ snd_mask_set(&mask, SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); + else { +- snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED); ++ snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_INTERLEAVED); + if (!direct) +- snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); ++ snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + } + err = snd_pcm_hw_param_mask(substream, sparams, SNDRV_PCM_HW_PARAM_ACCESS, &mask); + if (err < 0) { +@@ -909,7 +909,7 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + else + sformat = snd_pcm_plug_slave_format(format, sformat_mask); + +- if ((__force int)sformat < 0 || ++ if (sformat < 0 || + !snd_mask_test_format(sformat_mask, sformat)) { + pcm_for_each_format(sformat) { + if (snd_mask_test_format(sformat_mask, sformat) && +@@ -921,7 +921,7 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + goto failure; + } + format_found: +- err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, (__force int)sformat, 0); ++ err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, sformat, 0); + if (err < 0) + goto failure; + +@@ -930,9 +930,9 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + } else { + _snd_pcm_hw_params_any(params); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS, +- (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0); ++ SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT, +- (__force int)snd_pcm_oss_format_from(runtime->oss.format), 0); ++ snd_pcm_oss_format_from(runtime->oss.format), 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS, + runtime->oss.channels, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE, +@@ -1875,7 +1875,7 @@ static int snd_pcm_oss_get_formats(struct snd_pcm_oss_file *pcm_oss_file) + format_mask = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + for (fmt = 0; fmt < 32; ++fmt) { + if (snd_mask_test(format_mask, fmt)) { +- int f = snd_pcm_oss_format_to((__force snd_pcm_format_t)fmt); ++ int f = snd_pcm_oss_format_to(fmt); + if (f >= 0) + formats |= f; + } +@@ -2495,9 +2495,7 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) + int nonblock; + wait_queue_entry_t wait; + +- err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ nonseekable_open(inode, file); + + pcm = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_PCM); +@@ -2509,7 +2507,7 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { +- err = -EFAULT; ++ err = -ENODEV; + goto __error2; + } + if (snd_task_name(current, task_name, sizeof(task_name)) < 0) { +@@ -3129,17 +3127,16 @@ static inline void snd_pcm_oss_proc_done(struct snd_pcm *pcm) + * ENTRY functions + */ + +-static const struct file_operations snd_pcm_oss_f_reg = +-{ +- .owner = THIS_MODULE, +- .read = snd_pcm_oss_read, +- .write = snd_pcm_oss_write, +- .open = snd_pcm_oss_open, +- .release = snd_pcm_oss_release, +- .poll = snd_pcm_oss_poll, +- .unlocked_ioctl = snd_pcm_oss_ioctl, +- .compat_ioctl = snd_pcm_oss_ioctl_compat, +- .mmap = snd_pcm_oss_mmap, ++static const struct file_operations snd_pcm_oss_f_reg = { ++ .owner = THIS_MODULE, ++ .read = snd_pcm_oss_read, ++ .write = snd_pcm_oss_write, ++ .open = snd_pcm_oss_open, ++ .release = snd_pcm_oss_release, ++ .poll = snd_pcm_oss_poll, ++ .unlocked_ioctl = snd_pcm_oss_ioctl, ++ .compat_ioctl = snd_pcm_oss_ioctl_compat, ++ .mmap = snd_pcm_oss_mmap, + }; + + static void register_oss_dsp(struct snd_pcm *pcm, int index) +diff --git a/sound/core/oss/pcm_plugin.c b/sound/core/oss/pcm_plugin.c +--- a/sound/core/oss/pcm_plugin.c ++++ b/sound/core/oss/pcm_plugin.c +@@ -272,13 +272,13 @@ static int snd_pcm_plug_formats(const struct snd_mask *mask, + SNDRV_PCM_FMTBIT_U24_3BE | SNDRV_PCM_FMTBIT_S24_3BE | + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE); +- snd_mask_set(&formats, (__force int)SNDRV_PCM_FORMAT_MU_LAW); ++ snd_mask_set(&formats, SNDRV_PCM_FORMAT_MU_LAW); + + if (formats.bits[0] & lower_32_bits(linfmts)) + formats.bits[0] |= lower_32_bits(linfmts); + if (formats.bits[1] & upper_32_bits(linfmts)) + formats.bits[1] |= upper_32_bits(linfmts); +- return snd_mask_test(&formats, (__force int)format); ++ return snd_mask_test(&formats, format); + } + + static const snd_pcm_format_t preferred_formats[] = { +@@ -307,20 +307,20 @@ snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, + { + int i; + +- if (snd_mask_test(format_mask, (__force int)format)) ++ if (snd_mask_test(format_mask, format)) + return format; + if (!snd_pcm_plug_formats(format_mask, format)) +- return (__force snd_pcm_format_t)-EINVAL; ++ return -EINVAL; + if (snd_pcm_format_linear(format)) { + unsigned int width = snd_pcm_format_width(format); + int unsignd = snd_pcm_format_unsigned(format) > 0; + int big = snd_pcm_format_big_endian(format) > 0; + unsigned int badness, best = -1; +- snd_pcm_format_t best_format = (__force snd_pcm_format_t)-1; ++ snd_pcm_format_t best_format = -1; + for (i = 0; i < ARRAY_SIZE(preferred_formats); i++) { + snd_pcm_format_t f = preferred_formats[i]; + unsigned int w; +- if (!snd_mask_test(format_mask, (__force int)f)) ++ if (!snd_mask_test(format_mask, f)) + continue; + w = snd_pcm_format_width(f); + if (w >= width) +@@ -334,21 +334,21 @@ snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, + best = badness; + } + } +- if ((__force int)best_format >= 0) ++ if (best_format >= 0) + return best_format; + else +- return (__force snd_pcm_format_t)-EINVAL; ++ return -EINVAL; + } else { + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: + for (i = 0; i < ARRAY_SIZE(preferred_formats); ++i) { + snd_pcm_format_t format1 = preferred_formats[i]; +- if (snd_mask_test(format_mask, (__force int)format1)) ++ if (snd_mask_test(format_mask, format1)) + return format1; + } + fallthrough; + default: +- return (__force snd_pcm_format_t)-EINVAL; ++ return -EINVAL; + } + } + } +diff --git a/sound/core/pcm.c b/sound/core/pcm.c +--- a/sound/core/pcm.c ++++ b/sound/core/pcm.c +@@ -211,11 +211,9 @@ static const char * const snd_pcm_format_names[] = { + */ + const char *snd_pcm_format_name(snd_pcm_format_t format) + { +- unsigned int format_num = (__force unsigned int)format; +- +- if (format_num >= ARRAY_SIZE(snd_pcm_format_names) || !snd_pcm_format_names[format_num]) ++ if (format >= ARRAY_SIZE(snd_pcm_format_names) || !snd_pcm_format_names[format]) + return "Unknown"; +- return snd_pcm_format_names[format_num]; ++ return snd_pcm_format_names[format]; + } + EXPORT_SYMBOL_GPL(snd_pcm_format_name); + +@@ -275,12 +273,12 @@ static const char *snd_pcm_stream_name(int stream) + + static const char *snd_pcm_access_name(snd_pcm_access_t access) + { +- return snd_pcm_access_names[(__force int)access]; ++ return snd_pcm_access_names[access]; + } + + static const char *snd_pcm_subformat_name(snd_pcm_subformat_t subformat) + { +- return snd_pcm_subformat_names[(__force int)subformat]; ++ return snd_pcm_subformat_names[subformat]; + } + + static const char *snd_pcm_tstamp_mode_name(int mode) +@@ -290,7 +288,7 @@ static const char *snd_pcm_tstamp_mode_name(int mode) + + static const char *snd_pcm_state_name(snd_pcm_state_t state) + { +- return snd_pcm_state_names[(__force int)state]; ++ return snd_pcm_state_names[state]; + } + + #if IS_ENABLED(CONFIG_SND_PCM_OSS) +diff --git a/sound/core/pcm_misc.c b/sound/core/pcm_misc.c +--- a/sound/core/pcm_misc.c ++++ b/sound/core/pcm_misc.c +@@ -24,15 +24,12 @@ struct pcm_format_data { + unsigned char silence[8]; /* silence data to fill */ + }; + +-/* we do lots of calculations on snd_pcm_format_t; shut up sparse */ +-#define INT __force int +- + static bool valid_format(snd_pcm_format_t format) + { +- return (INT)format >= 0 && (INT)format <= (INT)SNDRV_PCM_FORMAT_LAST; ++ return format >= 0 && format <= SNDRV_PCM_FORMAT_LAST; + } + +-static const struct pcm_format_data pcm_formats[(INT)SNDRV_PCM_FORMAT_LAST+1] = { ++static const struct pcm_format_data pcm_formats[SNDRV_PCM_FORMAT_LAST+1] = { + [SNDRV_PCM_FORMAT_S8] = { + .width = 8, .phys = 8, .le = -1, .signd = 1, + .silence = {}, +@@ -251,7 +248,7 @@ int snd_pcm_format_signed(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].signd; ++ val = pcm_formats[format].signd; + if (val < 0) + return -EINVAL; + return val; +@@ -300,7 +297,7 @@ int snd_pcm_format_little_endian(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].le; ++ val = pcm_formats[format].le; + if (val < 0) + return -EINVAL; + return val; +@@ -337,7 +334,7 @@ int snd_pcm_format_width(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].width; ++ val = pcm_formats[format].width; + if (!val) + return -EINVAL; + return val; +@@ -356,7 +353,7 @@ int snd_pcm_format_physical_width(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].phys; ++ val = pcm_formats[format].phys; + if (!val) + return -EINVAL; + return val; +@@ -390,9 +387,9 @@ const unsigned char *snd_pcm_format_silence_64(snd_pcm_format_t format) + { + if (!valid_format(format)) + return NULL; +- if (! pcm_formats[(INT)format].phys) ++ if (! pcm_formats[format].phys) + return NULL; +- return pcm_formats[(INT)format].silence; ++ return pcm_formats[format].silence; + } + EXPORT_SYMBOL(snd_pcm_format_silence_64); + +@@ -416,12 +413,12 @@ int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int + return -EINVAL; + if (samples == 0) + return 0; +- width = pcm_formats[(INT)format].phys; /* physical width */ ++ width = pcm_formats[format].phys; /* physical width */ + if (!width) + return -EINVAL; +- pat = pcm_formats[(INT)format].silence; ++ pat = pcm_formats[format].silence; + /* signed or 1 byte data */ +- if (pcm_formats[(INT)format].signd == 1 || width <= 8) { ++ if (pcm_formats[format].signd == 1 || width <= 8) { + unsigned int bytes = samples * width / 8; + memset(data, *pat, bytes); + return 0; +diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c +--- a/sound/core/pcm_native.c ++++ b/sound/core/pcm_native.c +@@ -257,7 +257,7 @@ int snd_pcm_info_user(struct snd_pcm_substream *substream, + } + + /* macro for simplified cast */ +-#define PARAM_MASK_BIT(b) (1U << (__force int)(b)) ++#define PARAM_MASK_BIT(b) (1U << (b)) + + static bool hw_support_mmap(struct snd_pcm_substream *substream) + { +@@ -489,7 +489,7 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream, + params->msbits = snd_interval_value(i); + m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + if (snd_mask_single(m)) { +- snd_pcm_format_t format = (__force snd_pcm_format_t)snd_mask_min(m); ++ snd_pcm_format_t format = snd_mask_min(m); + params->msbits = snd_pcm_format_width(format); + } + } +@@ -497,13 +497,13 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream, + if (params->msbits) { + m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + if (snd_mask_single(m)) { +- snd_pcm_format_t format = (__force snd_pcm_format_t)snd_mask_min(m); ++ snd_pcm_format_t format = snd_mask_min(m); + + if (snd_pcm_format_linear(format) && + snd_pcm_format_width(format) != params->msbits) { + m_rw = hw_param_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT); + snd_mask_reset(m_rw, +- (__force unsigned)SNDRV_PCM_SUBFORMAT_MSBITS_MAX); ++ SNDRV_PCM_SUBFORMAT_MSBITS_MAX); + if (snd_mask_empty(m_rw)) + return -EINVAL; + } +@@ -1252,7 +1252,7 @@ static void snd_pcm_trigger_tstamp(struct snd_pcm_substream *substream) + runtime->trigger_master = NULL; + } + +-#define ACTION_ARG_IGNORE (__force snd_pcm_state_t)0 ++#define ACTION_ARG_IGNORE 0 + + struct action_ops { + int (*pre_action)(struct snd_pcm_substream *substream, +@@ -1637,7 +1637,7 @@ EXPORT_SYMBOL_GPL(snd_pcm_stop_xrun); + /* + * pause callbacks: pass boolean (to start pause or resume) as state argument + */ +-#define pause_pushed(state) (__force bool)(state) ++#define pause_pushed(state) (bool)(state) + + static int snd_pcm_pre_pause(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +@@ -1709,14 +1709,14 @@ static const struct action_ops snd_pcm_action_pause = { + static int snd_pcm_pause(struct snd_pcm_substream *substream, bool push) + { + return snd_pcm_action(&snd_pcm_action_pause, substream, +- (__force snd_pcm_state_t)push); ++ (snd_pcm_state_t)push); + } + + static int snd_pcm_pause_lock_irq(struct snd_pcm_substream *substream, + bool push) + { + return snd_pcm_action_lock_irq(&snd_pcm_action_pause, substream, +- (__force snd_pcm_state_t)push); ++ (snd_pcm_state_t)push); + } + + #ifdef CONFIG_PM +@@ -1984,7 +1984,7 @@ static int snd_pcm_pre_prepare(struct snd_pcm_substream *substream, + snd_pcm_state_t state) + { + snd_pcm_state_t cur_state = snd_pcm_get_state(substream); +- int f_flags = (__force int)state; ++ int f_flags = state; + + if (cur_state == SNDRV_PCM_STATE_OPEN || + cur_state == SNDRV_PCM_STATE_DISCONNECTED) +@@ -2052,7 +2052,7 @@ static int snd_pcm_prepare(struct snd_pcm_substream *substream, + + return snd_pcm_action_nonatomic(&snd_pcm_action_prepare, + substream, +- (__force snd_pcm_state_t)f_flags); ++ (snd_pcm_state_t)f_flags); + } + + /* +@@ -2463,7 +2463,7 @@ static int snd_pcm_hw_rule_format(struct snd_pcm_hw_params *params, + if (bits <= 0) + continue; /* ignore invalid formats */ + if ((unsigned)bits < i->min || (unsigned)bits > i->max) +- snd_mask_reset(&m, (__force unsigned)k); ++ snd_mask_reset(&m, k); + } + return snd_mask_refine(mask, &m); + } +@@ -2545,16 +2545,16 @@ static int snd_pcm_hw_rule_subformats(struct snd_pcm_hw_params *params, + + snd_mask_none(&m); + /* All PCMs support at least the default STD subformat. */ +- snd_mask_set(&m, (__force unsigned)SNDRV_PCM_SUBFORMAT_STD); ++ snd_mask_set(&m, SNDRV_PCM_SUBFORMAT_STD); + + pcm_for_each_format(f) { +- if (!snd_mask_test(fmask, (__force unsigned)f)) ++ if (!snd_mask_test(fmask, f)) + continue; + + if (f == SNDRV_PCM_FORMAT_S32_LE && *subformats) + m.bits[0] |= *subformats; + else if (snd_pcm_format_linear(f)) +- snd_mask_set(&m, (__force unsigned)SNDRV_PCM_SUBFORMAT_MSBITS_MAX); ++ snd_mask_set(&m, SNDRV_PCM_SUBFORMAT_MSBITS_MAX); + } + + return snd_mask_refine(sfmask, &m); +@@ -2882,9 +2882,10 @@ static int snd_pcm_open_file(struct file *file, + static int snd_pcm_playback_open(struct inode *inode, struct file *file) + { + struct snd_pcm *pcm; +- int err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ int err; ++ ++ nonseekable_open(inode, file); ++ + pcm = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_PLAYBACK); + err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK); +@@ -2896,9 +2897,10 @@ static int snd_pcm_playback_open(struct inode *inode, struct file *file) + static int snd_pcm_capture_open(struct inode *inode, struct file *file) + { + struct snd_pcm *pcm; +- int err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ int err; ++ ++ nonseekable_open(inode, file); ++ + pcm = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_CAPTURE); + err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE); +@@ -2920,7 +2922,7 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream) + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { +- err = -EFAULT; ++ err = -ENODEV; + goto __error2; + } + init_waitqueue_entry(&wait, current); +@@ -3916,6 +3918,8 @@ static vm_fault_t snd_pcm_mmap_data_fault(struct vm_fault *vmf) + if (substream == NULL) + return VM_FAULT_SIGBUS; + runtime = substream->runtime; ++ if (runtime->state == SNDRV_PCM_STATE_DISCONNECTED) ++ return VM_FAULT_SIGBUS; + offset = vmf->pgoff << PAGE_SHIFT; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); + if (offset > dma_bytes - PAGE_SIZE) +@@ -4241,29 +4245,29 @@ static unsigned long snd_pcm_get_unmapped_area(struct file *file, + + const struct file_operations snd_pcm_f_ops[2] = { + { +- .owner = THIS_MODULE, +- .write = snd_pcm_write, +- .write_iter = snd_pcm_writev, +- .open = snd_pcm_playback_open, +- .release = snd_pcm_release, +- .poll = snd_pcm_poll, +- .unlocked_ioctl = snd_pcm_ioctl, +- .compat_ioctl = snd_pcm_ioctl_compat, +- .mmap = snd_pcm_mmap, +- .fasync = snd_pcm_fasync, +- .get_unmapped_area = snd_pcm_get_unmapped_area, ++ .owner = THIS_MODULE, ++ .write = snd_pcm_write, ++ .write_iter = snd_pcm_writev, ++ .open = snd_pcm_playback_open, ++ .release = snd_pcm_release, ++ .poll = snd_pcm_poll, ++ .unlocked_ioctl = snd_pcm_ioctl, ++ .compat_ioctl = snd_pcm_ioctl_compat, ++ .mmap = snd_pcm_mmap, ++ .fasync = snd_pcm_fasync, ++ .get_unmapped_area = snd_pcm_get_unmapped_area, + }, + { +- .owner = THIS_MODULE, +- .read = snd_pcm_read, +- .read_iter = snd_pcm_readv, +- .open = snd_pcm_capture_open, +- .release = snd_pcm_release, +- .poll = snd_pcm_poll, +- .unlocked_ioctl = snd_pcm_ioctl, +- .compat_ioctl = snd_pcm_ioctl_compat, +- .mmap = snd_pcm_mmap, +- .fasync = snd_pcm_fasync, +- .get_unmapped_area = snd_pcm_get_unmapped_area, ++ .owner = THIS_MODULE, ++ .read = snd_pcm_read, ++ .read_iter = snd_pcm_readv, ++ .open = snd_pcm_capture_open, ++ .release = snd_pcm_release, ++ .poll = snd_pcm_poll, ++ .unlocked_ioctl = snd_pcm_ioctl, ++ .compat_ioctl = snd_pcm_ioctl_compat, ++ .mmap = snd_pcm_mmap, ++ .fasync = snd_pcm_fasync, ++ .get_unmapped_area = snd_pcm_get_unmapped_area, + } + }; +diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c +--- a/sound/core/rawmidi.c ++++ b/sound/core/rawmidi.c +@@ -408,9 +408,10 @@ static int rawmidi_open_priv(struct snd_rawmidi *rmidi, int subdevice, int mode, + return 0; + } + +-/* called from sound/core/seq/seq_midi.c */ +-int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, +- int mode, struct snd_rawmidi_file *rfile) ++/* called from sound/core/seq/seq_midi.c and sound/core/ump.c */ ++int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, ++ int mode, struct snd_rawmidi_file *rfile, ++ int depth) + { + int err; + +@@ -419,13 +420,14 @@ int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, + if (!try_module_get(rmidi->card->module)) + return -ENXIO; + +- guard(mutex)(&rmidi->open_mutex); ++ mutex_lock_nested(&rmidi->open_mutex, depth); + err = rawmidi_open_priv(rmidi, subdevice, mode, rfile); + if (err < 0) + module_put(rmidi->card->module); ++ mutex_unlock(&rmidi->open_mutex); + return err; + } +-EXPORT_SYMBOL(snd_rawmidi_kernel_open); ++EXPORT_SYMBOL(snd_rawmidi_kernel_open_nested); + + static int snd_rawmidi_open(struct inode *inode, struct file *file) + { +@@ -441,9 +443,7 @@ static int snd_rawmidi_open(struct inode *inode, struct file *file) + if ((file->f_flags & O_APPEND) && !(file->f_flags & O_NONBLOCK)) + return -EINVAL; /* invalid combination */ + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + if (maj == snd_major) { + rmidi = snd_lookup_minor_data(iminor(inode), +@@ -1784,14 +1784,14 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, + */ + + static const struct file_operations snd_rawmidi_f_ops = { +- .owner = THIS_MODULE, +- .read = snd_rawmidi_read, +- .write = snd_rawmidi_write, +- .open = snd_rawmidi_open, +- .release = snd_rawmidi_release, +- .poll = snd_rawmidi_poll, +- .unlocked_ioctl = snd_rawmidi_ioctl, +- .compat_ioctl = snd_rawmidi_ioctl_compat, ++ .owner = THIS_MODULE, ++ .read = snd_rawmidi_read, ++ .write = snd_rawmidi_write, ++ .open = snd_rawmidi_open, ++ .release = snd_rawmidi_release, ++ .poll = snd_rawmidi_poll, ++ .unlocked_ioctl = snd_rawmidi_ioctl, ++ .compat_ioctl = snd_rawmidi_ioctl_compat, + }; + + static int snd_rawmidi_alloc_substreams(struct snd_rawmidi *rmidi, +diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c +--- a/sound/core/seq/oss/seq_oss.c ++++ b/sound/core/seq/oss/seq_oss.c +@@ -206,17 +206,16 @@ odev_poll(struct file *file, poll_table * wait) + * registration of sequencer minor device + */ + +-static const struct file_operations seq_oss_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = odev_read, +- .write = odev_write, +- .open = odev_open, +- .release = odev_release, +- .poll = odev_poll, +- .unlocked_ioctl = odev_ioctl, +- .compat_ioctl = odev_ioctl_compat, +- .llseek = noop_llseek, ++static const struct file_operations seq_oss_f_ops = { ++ .owner = THIS_MODULE, ++ .read = odev_read, ++ .write = odev_write, ++ .open = odev_open, ++ .release = odev_release, ++ .poll = odev_poll, ++ .unlocked_ioctl = odev_ioctl, ++ .compat_ioctl = odev_ioctl_compat, ++ .llseek = noop_llseek, + }; + + static int __init +diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c +--- a/sound/core/seq/seq_clientmgr.c ++++ b/sound/core/seq/seq_clientmgr.c +@@ -59,7 +59,7 @@ static DEFINE_MUTEX(register_mutex); + * client table + */ + static char clienttablock[SNDRV_SEQ_MAX_CLIENTS]; +-static struct snd_seq_client *clienttab[SNDRV_SEQ_MAX_CLIENTS]; ++static struct snd_seq_client __rcu *clienttab[SNDRV_SEQ_MAX_CLIENTS]; + static struct snd_seq_usage client_usage; + + /* +@@ -95,15 +95,23 @@ static inline int snd_seq_write_pool_allocated(struct snd_seq_client *client) + return snd_seq_total_cells(client->pool) > 0; + } + +-/* return pointer to client structure for specified id */ +-static struct snd_seq_client *clientptr(int clientid) ++/* return pointer to client structure for specified id; call under RCU read-lock */ ++static struct snd_seq_client *__clientptr(int clientid) + { + if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { + pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n", + clientid); + return NULL; + } +- return clienttab[clientid]; ++ return rcu_dereference_check(clienttab[clientid], ++ lockdep_is_held(&clients_lock)); ++} ++ ++/* return pointer to client structure for specified id */ ++static struct snd_seq_client *clientptr(int clientid) ++{ ++ guard(rcu)(); ++ return __clientptr(clientid); + } + + static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) +@@ -115,8 +123,8 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) + clientid); + return NULL; + } +- scoped_guard(spinlock_irqsave, &clients_lock) { +- client = clientptr(clientid); ++ scoped_guard(rcu) { ++ client = __clientptr(clientid); + if (client) + return snd_seq_client_ref(client); + if (clienttablock[clientid]) +@@ -150,8 +158,8 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) + snd_seq_device_load_drivers(); + } + } +- scoped_guard(spinlock_irqsave, &clients_lock) { +- client = clientptr(clientid); ++ scoped_guard(rcu) { ++ client = __clientptr(clientid); + if (client) + return snd_seq_client_ref(client); + } +@@ -212,7 +220,6 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) + } + client->type = NO_CLIENT; + snd_use_lock_init(&client->use_lock); +- rwlock_init(&client->ports_lock); + mutex_init(&client->ports_mutex); + INIT_LIST_HEAD(&client->ports_list_head); + mutex_init(&client->ioctl_mutex); +@@ -224,14 +231,17 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) + for (c = SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN; + c < SNDRV_SEQ_MAX_CLIENTS; + c++) { +- if (clienttab[c] || clienttablock[c]) ++ if (rcu_access_pointer(clienttab[c]) || clienttablock[c]) + continue; +- clienttab[client->number = c] = client; ++ client->number = c; ++ rcu_assign_pointer(clienttab[c], client); + return client; + } + } else { +- if (clienttab[client_index] == NULL && !clienttablock[client_index]) { +- clienttab[client->number = client_index] = client; ++ if (rcu_access_pointer(clienttab[client_index]) == NULL && ++ !clienttablock[client_index]) { ++ client->number = client_index; ++ rcu_assign_pointer(clienttab[client_index], client); + return client; + } + } +@@ -249,10 +259,16 @@ static int seq_free_client1(struct snd_seq_client *client) + return 0; + scoped_guard(spinlock_irq, &clients_lock) { + clienttablock[client->number] = 1; +- clienttab[client->number] = NULL; ++ rcu_assign_pointer(clienttab[client->number], NULL); + } + snd_seq_delete_all_ports(client); + snd_seq_queue_client_leave(client->number); ++ /* the client has been unpublished from the table; wait for a grace ++ * period so that lockless readers (snd_seq_client_use_ptr()) that ++ * observed the old pointer can no longer take a new use_lock ++ * reference, then drain the outstanding references before freeing ++ */ ++ synchronize_rcu(); + snd_use_lock_sync(&client->use_lock); + if (client->pool) + snd_seq_pool_delete(&client->pool); +@@ -296,11 +312,8 @@ static int snd_seq_open(struct inode *inode, struct file *file) + int c, mode; /* client id */ + struct snd_seq_client *client; + struct snd_seq_user_client *user; +- int err; + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + scoped_guard(mutex, ®ister_mutex) { + client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS); +@@ -517,9 +530,8 @@ static struct snd_seq_client *get_event_dest_client(struct snd_seq_event *event) + * Return the error event. + * + * If the receiver client is a user client, the original event is +- * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. If +- * the original event is also variable length, the external data is +- * copied after the event record. ++ * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. The ++ * external data of a variable length event is not copied along. + * If the receiver client is a kernel client, the original event is + * quoted in SNDRV_SEQ_EVENT_KERNEL_ERROR, since this requires no extra + * kmalloc. +@@ -528,7 +540,7 @@ static int bounce_error_event(struct snd_seq_client *client, + struct snd_seq_event *event, + int err, int atomic, int hop) + { +- struct snd_seq_event bounce_ev; ++ struct snd_seq_event bounce_ev, quoted; + int result; + + if (client == NULL || +@@ -548,15 +560,19 @@ static int bounce_error_event(struct snd_seq_client *client, + * For user clients, send SNDRV_SEQ_EVENT_BOUNCE with the + * original event embedded as variable-length data. This + * avoids exposing data.quote.event (a kernel pointer) to +- * userspace. The variable-length path in snd_seq_event_dup() +- * copies the event data from data.ext.ptr into chained cells, +- * and snd_seq_expand_var_event() copies only the data content +- * -- never the pointer -- to userspace. ++ * userspace. Sanitise the embedded copy too - a queued ++ * variable-length event carries the address of its own ++ * extension cell, and the payload goes out verbatim. + */ ++ quoted = *event; ++ if (snd_seq_ev_is_variable("ed)) { ++ quoted.data.ext.len &= ~SNDRV_SEQ_EXT_MASK; ++ quoted.data.ext.ptr = NULL; ++ } + bounce_ev.type = SNDRV_SEQ_EVENT_BOUNCE; + bounce_ev.flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + bounce_ev.data.ext.len = sizeof(struct snd_seq_event); +- bounce_ev.data.ext.ptr = (char *)event; ++ bounce_ev.data.ext.ptr = (char *)"ed; + } else { + /* + * For kernel clients, quote the event pointer directly. +@@ -720,10 +736,11 @@ static int __deliver_to_subscribers(struct snd_seq_client *client, + + /* lock list */ + if (atomic) +- read_lock(&grp->list_lock); ++ rcu_read_lock(); + else + down_read_nested(&grp->list_mutex, hop); +- list_for_each_entry(subs, &grp->list_head, src_list) { ++ hlist_for_each_entry_rcu(subs, &grp->list_head, src_list, ++ lockdep_is_held(&grp->list_mutex)) { + /* both ports ready? */ + if (atomic_read(&subs->ref_count) != 2) + continue; +@@ -744,7 +761,7 @@ static int __deliver_to_subscribers(struct snd_seq_client *client, + memcpy(event, &event_saved, saved_size); + } + if (atomic) +- read_unlock(&grp->list_lock); ++ rcu_read_unlock(); + else + up_read(&grp->list_mutex); + memcpy(event, &event_saved, saved_size); +@@ -1941,7 +1958,7 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) + { + struct snd_seq_query_subs *subs = arg; + struct snd_seq_port_subs_info *group; +- struct list_head *p; ++ struct hlist_node *p; + int i; + + struct snd_seq_client *cptr __free(snd_seq_client) = +@@ -1968,15 +1985,15 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) + /* search for the subscriber */ + subs->num_subs = group->count; + i = 0; +- list_for_each(p, &group->list_head) { ++ hlist_for_each(p, &group->list_head) { + if (i++ == subs->index) { + /* found! */ + struct snd_seq_subscribers *s; + if (subs->type == SNDRV_SEQ_QUERY_SUBS_READ) { +- s = list_entry(p, struct snd_seq_subscribers, src_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, src_list); + subs->addr = s->info.dest; + } else { +- s = list_entry(p, struct snd_seq_subscribers, dest_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, dest_list); + subs->addr = s->info.sender; + } + subs->flags = s->info.flags; +@@ -2532,19 +2549,19 @@ static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer, + struct snd_seq_port_subs_info *group, + int is_src, char *msg) + { +- struct list_head *p; ++ struct hlist_node *p; + struct snd_seq_subscribers *s; + int count = 0; + + guard(rwsem_read)(&group->list_mutex); +- if (list_empty(&group->list_head)) ++ if (hlist_empty(&group->list_head)) + return; + snd_iprintf(buffer, msg); +- list_for_each(p, &group->list_head) { ++ hlist_for_each(p, &group->list_head) { + if (is_src) +- s = list_entry(p, struct snd_seq_subscribers, src_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, src_list); + else +- s = list_entry(p, struct snd_seq_subscribers, dest_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, dest_list); + if (count++) + snd_iprintf(buffer, ", "); + snd_iprintf(buffer, "%d:%d", +@@ -2666,16 +2683,15 @@ void snd_seq_info_clients_read(struct snd_info_entry *entry, + * REGISTRATION PART + */ + +-static const struct file_operations snd_seq_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = snd_seq_read, +- .write = snd_seq_write, +- .open = snd_seq_open, +- .release = snd_seq_release, +- .poll = snd_seq_poll, +- .unlocked_ioctl = snd_seq_ioctl, +- .compat_ioctl = snd_seq_ioctl_compat, ++static const struct file_operations snd_seq_f_ops = { ++ .owner = THIS_MODULE, ++ .read = snd_seq_read, ++ .write = snd_seq_write, ++ .open = snd_seq_open, ++ .release = snd_seq_release, ++ .poll = snd_seq_poll, ++ .unlocked_ioctl = snd_seq_ioctl, ++ .compat_ioctl = snd_seq_ioctl_compat, + }; + + static struct device *seq_dev; +diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h +--- a/sound/core/seq/seq_clientmgr.h ++++ b/sound/core/seq/seq_clientmgr.h +@@ -49,7 +49,6 @@ struct snd_seq_client { + /* ports */ + int num_ports; /* number of ports */ + struct list_head ports_list_head; +- rwlock_t ports_lock; + struct mutex ports_mutex; + struct mutex ioctl_mutex; + int convert32; /* convert 32->64bit */ +diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c +--- a/sound/core/seq/seq_midi.c ++++ b/sound/core/seq/seq_midi.c +@@ -42,9 +42,11 @@ struct seq_midisynth { + struct snd_rawmidi *rmidi; + int device; + int subdevice; ++ struct snd_rawmidi_substream __rcu *input_substream; ++ snd_use_lock_t input_use_lock; /* in-flight event_input users */ + struct snd_rawmidi_file input_rfile; +- spinlock_t output_lock; /* protects output_rfile publication */ + snd_use_lock_t output_use_lock; /* in-flight event_input users */ ++ struct snd_rawmidi_substream __rcu *output_substream; + struct snd_rawmidi_file output_rfile; + int seq_client; + int seq_port; +@@ -76,6 +78,14 @@ static void snd_midi_input_event(struct snd_rawmidi_substream *substream) + msynth = runtime->private_data; + if (msynth == NULL) + return; ++ ++ scoped_guard(rcu) { ++ if (rcu_dereference(msynth->input_substream) != substream) ++ return; ++ ++ snd_use_lock_use(&msynth->input_use_lock); ++ } ++ + memset(&ev, 0, sizeof(ev)); + while (runtime->avail > 0) { + res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf)); +@@ -95,6 +105,8 @@ static void snd_midi_input_event(struct snd_rawmidi_substream *substream) + memset(&ev, 0, sizeof(ev)); + } + } ++ ++ snd_use_lock_free(&msynth->input_use_lock); + } + + static int dump_midi(struct snd_rawmidi_substream *substream, const char *buf, int count) +@@ -134,8 +146,8 @@ static int event_process_midi(struct snd_seq_event *ev, int direct, + if (snd_BUG_ON(!msynth)) + return -EINVAL; + +- scoped_guard(spinlock_irqsave, &msynth->output_lock) { +- substream = msynth->output_rfile.output; ++ scoped_guard(rcu) { ++ substream = rcu_dereference(msynth->output_substream); + if (!substream) + return -ENODEV; + snd_use_lock_use(&msynth->output_use_lock); +@@ -177,7 +189,7 @@ static int snd_seq_midisynth_new(struct seq_midisynth *msynth, + msynth->card = card; + msynth->device = device; + msynth->subdevice = subdevice; +- spin_lock_init(&msynth->output_lock); ++ snd_use_lock_init(&msynth->input_use_lock); + snd_use_lock_init(&msynth->output_use_lock); + return 0; + } +@@ -188,28 +200,31 @@ static int midisynth_subscribe(void *private_data, struct snd_seq_port_subscribe + int err; + struct seq_midisynth *msynth = private_data; + struct snd_rawmidi_runtime *runtime; ++ struct snd_rawmidi_file rfile = {}; + struct snd_rawmidi_params params; + + /* open midi port */ + err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice, + SNDRV_RAWMIDI_LFLG_INPUT, +- &msynth->input_rfile); ++ &rfile); + if (err < 0) { + pr_debug("ALSA: seq_midi: midi input open failed!!!\n"); + return err; + } +- runtime = msynth->input_rfile.input->runtime; ++ runtime = rfile.input->runtime; + memset(¶ms, 0, sizeof(params)); + params.avail_min = 1; + params.buffer_size = input_buffer_size; +- err = snd_rawmidi_input_params(msynth->input_rfile.input, ¶ms); ++ err = snd_rawmidi_input_params(rfile.input, ¶ms); + if (err < 0) { +- snd_rawmidi_kernel_release(&msynth->input_rfile); ++ snd_rawmidi_kernel_release(&rfile); + return err; + } + snd_midi_event_reset_encode(msynth->parser); + runtime->event = snd_midi_input_event; + runtime->private_data = msynth; ++ msynth->input_rfile = rfile; ++ rcu_assign_pointer(msynth->input_substream, rfile.input); + snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0); + return 0; + } +@@ -219,10 +234,19 @@ static int midisynth_unsubscribe(void *private_data, struct snd_seq_port_subscri + { + int err; + struct seq_midisynth *msynth = private_data; ++ struct snd_rawmidi_file rfile; + +- if (snd_BUG_ON(!msynth->input_rfile.input)) ++ rcu_assign_pointer(msynth->input_substream, NULL); ++ synchronize_rcu(); ++ snd_use_lock_sync(&msynth->input_use_lock); ++ ++ rfile = msynth->input_rfile; ++ msynth->input_rfile = (struct snd_rawmidi_file){}; ++ ++ if (snd_BUG_ON(!rfile.input)) + return -EINVAL; +- err = snd_rawmidi_kernel_release(&msynth->input_rfile); ++ ++ err = snd_rawmidi_kernel_release(&rfile); + return err; + } + +@@ -252,8 +276,8 @@ static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info + return err; + } + snd_midi_event_reset_decode(msynth->parser); +- scoped_guard(spinlock_irqsave, &msynth->output_lock) +- msynth->output_rfile = rfile; ++ msynth->output_rfile = rfile; ++ rcu_assign_pointer(msynth->output_substream, rfile.output); + return 0; + } + +@@ -261,17 +285,16 @@ static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info + static int midisynth_unuse(void *private_data, struct snd_seq_port_subscribe *info) + { + struct seq_midisynth *msynth = private_data; +- struct snd_rawmidi_file rfile = {}; ++ struct snd_rawmidi_file rfile; + +- scoped_guard(spinlock_irqsave, &msynth->output_lock) { +- rfile = msynth->output_rfile; +- msynth->output_rfile = (struct snd_rawmidi_file){}; +- } ++ rcu_assign_pointer(msynth->output_substream, NULL); ++ synchronize_rcu(); ++ snd_use_lock_sync(&msynth->output_use_lock); ++ rfile = msynth->output_rfile; ++ msynth->output_rfile = (struct snd_rawmidi_file){}; + + if (snd_BUG_ON(!rfile.output)) + return -EINVAL; +- +- snd_use_lock_sync(&msynth->output_use_lock); + snd_rawmidi_drain_output(rfile.output); + return snd_rawmidi_kernel_release(&rfile); + } +diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c +--- a/sound/core/seq/seq_ports.c ++++ b/sound/core/seq/seq_ports.c +@@ -48,8 +48,8 @@ struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, + + if (client == NULL) + return NULL; +- guard(read_lock)(&client->ports_lock); +- list_for_each_entry(port, &client->ports_list_head, list) { ++ guard(rcu)(); ++ list_for_each_entry_rcu(port, &client->ports_list_head, list) { + if (port->addr.port == num) { + if (port->closing) + break; /* deleting now */ +@@ -71,8 +71,8 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl + + num = pinfo->addr.port; + found = NULL; +- guard(read_lock)(&client->ports_lock); +- list_for_each_entry(port, &client->ports_list_head, list) { ++ guard(rcu)(); ++ list_for_each_entry_rcu(port, &client->ports_list_head, list) { + if ((port->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) && + !check_inactive) + continue; /* skip inactive ports */ +@@ -98,10 +98,9 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl + /* initialize snd_seq_port_subs_info */ + static void port_subs_info_init(struct snd_seq_port_subs_info *grp) + { +- INIT_LIST_HEAD(&grp->list_head); ++ INIT_HLIST_HEAD(&grp->list_head); + grp->count = 0; + grp->exclusive = 0; +- rwlock_init(&grp->list_lock); + init_rwsem(&grp->list_mutex); + grp->open = NULL; + grp->close = NULL; +@@ -154,7 +153,6 @@ int snd_seq_insert_port(struct snd_seq_client *client, int port, + + num = max(port, 0); + guard(mutex)(&client->ports_mutex); +- guard(write_lock_irq)(&client->ports_lock); + struct list_head *insert_before = &client->ports_list_head; + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) +@@ -166,12 +164,13 @@ int snd_seq_insert_port(struct snd_seq_client *client, int port, + if (port < 0) /* auto-probe mode */ + num = p->addr.port + 1; + } +- /* insert the new port */ +- list_add_tail(&new_port->list, insert_before); +- client->num_ports++; ++ /* finish initializing the port before publishing it to RCU readers */ + new_port->addr.port = num; /* store the port number in the port */ + if (!new_port->name[0]) + sprintf(new_port->name, "port-%d", num); ++ /* insert the new port */ ++ list_add_tail_rcu(&new_port->list, insert_before); ++ client->num_ports++; + + return num; + } +@@ -202,12 +201,12 @@ static void delete_and_unsubscribe_port(struct snd_seq_client *client, + bool is_src, bool ack); + + static inline struct snd_seq_subscribers * +-get_subscriber(struct list_head *p, bool is_src) ++get_subscriber(struct hlist_node *p, bool is_src) + { + if (is_src) +- return list_entry(p, struct snd_seq_subscribers, src_list); ++ return hlist_entry(p, struct snd_seq_subscribers, src_list); + else +- return list_entry(p, struct snd_seq_subscribers, dest_list); ++ return hlist_entry(p, struct snd_seq_subscribers, dest_list); + } + + /* +@@ -219,9 +218,9 @@ static void clear_subscriber_list(struct snd_seq_client *client, + struct snd_seq_port_subs_info *grp, + int is_src) + { +- struct list_head *p, *n; ++ struct hlist_node *p, *n; + +- list_for_each_safe(p, n, &grp->list_head) { ++ hlist_for_each_safe(p, n, &grp->list_head) { + struct snd_seq_subscribers *subs; + + subs = get_subscriber(p, is_src); +@@ -238,13 +237,13 @@ static void clear_subscriber_list(struct snd_seq_client *client, + * remove the subscriber info + */ + if (atomic_dec_and_test(&subs->ref_count)) +- kfree(subs); ++ kfree_rcu(subs, rcu); + continue; + } + + /* ok we got the connected port */ + delete_and_unsubscribe_port(c, aport, subs, !is_src, true); +- kfree(subs); ++ kfree_rcu(subs, rcu); + } + } + +@@ -254,7 +253,13 @@ static int port_delete(struct snd_seq_client *client, + { + /* set closing flag and wait for all port access are gone */ + port->closing = 1; +- snd_use_lock_sync(&port->use_lock); ++ /* the port has already been unlinked from the client's port list; ++ * wait for a grace period so that RCU readers still traversing the ++ * list can no longer take a new use_lock reference, then drain the ++ * outstanding references before freeing ++ */ ++ synchronize_rcu(); ++ snd_use_lock_sync(&port->use_lock); + + /* clear subscribers info */ + clear_subscriber_list(client, port, &port->c_src, true); +@@ -277,11 +282,10 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) + struct snd_seq_client_port *found = NULL, *p; + + scoped_guard(mutex, &client->ports_mutex) { +- guard(write_lock_irq)(&client->ports_lock); + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) { + /* ok found. delete from the list at first */ +- list_del(&p->list); ++ list_del_rcu(&p->list); + client->num_ports--; + found = p; + break; +@@ -297,26 +301,16 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) + /* delete the all ports belonging to the given client */ + int snd_seq_delete_all_ports(struct snd_seq_client *client) + { +- struct list_head deleted_list; + struct snd_seq_client_port *port, *tmp; +- +- /* move the port list to deleted_list, and +- * clear the port list in the client data. ++ ++ /* unlink and delete each port; port_delete() waits for an RCU grace ++ * period before draining the port, so concurrent lockless readers can ++ * no longer take a new use_lock reference on it + */ + guard(mutex)(&client->ports_mutex); +- scoped_guard(write_lock_irq, &client->ports_lock) { +- if (!list_empty(&client->ports_list_head)) { +- list_add(&deleted_list, &client->ports_list_head); +- list_del_init(&client->ports_list_head); +- } else { +- INIT_LIST_HEAD(&deleted_list); +- } +- client->num_ports = 0; +- } +- +- /* remove each port in deleted_list */ +- list_for_each_entry_safe(port, tmp, &deleted_list, list) { +- list_del(&port->list); ++ list_for_each_entry_safe(port, tmp, &client->ports_list_head, list) { ++ list_del_rcu(&port->list); ++ client->num_ports--; + snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port); + port_delete(client, port); + } +@@ -499,20 +493,20 @@ static int check_and_subscribe_port(struct snd_seq_client *client, + bool is_src, bool exclusive, bool ack) + { + struct snd_seq_port_subs_info *grp; +- struct list_head *p; ++ struct hlist_node *p; + struct snd_seq_subscribers *s; + int err; + + grp = is_src ? &port->c_src : &port->c_dest; + guard(rwsem_write)(&grp->list_mutex); + if (exclusive) { +- if (!list_empty(&grp->list_head)) ++ if (!hlist_empty(&grp->list_head)) + return -EBUSY; + } else { + if (grp->exclusive) + return -EBUSY; + /* check whether already exists */ +- list_for_each(p, &grp->list_head) { ++ hlist_for_each(p, &grp->list_head) { + s = get_subscriber(p, is_src); + if (match_subs_info(&subs->info, &s->info)) + return -EBUSY; +@@ -526,11 +520,10 @@ static int check_and_subscribe_port(struct snd_seq_client *client, + } + + /* add to list */ +- guard(write_lock_irq)(&grp->list_lock); + if (is_src) +- list_add_tail(&subs->src_list, &grp->list_head); ++ hlist_add_tail_rcu(&subs->src_list, &grp->list_head); + else +- list_add_tail(&subs->dest_list, &grp->list_head); ++ hlist_add_tail_rcu(&subs->dest_list, &grp->list_head); + grp->exclusive = exclusive; + atomic_inc(&subs->ref_count); + +@@ -544,17 +537,15 @@ static void __delete_and_unsubscribe_port(struct snd_seq_client *client, + bool is_src, bool ack) + { + struct snd_seq_port_subs_info *grp; +- struct list_head *list; ++ struct hlist_node *list; + bool empty; + + grp = is_src ? &port->c_src : &port->c_dest; + list = is_src ? &subs->src_list : &subs->dest_list; +- scoped_guard(write_lock_irq, &grp->list_lock) { +- empty = list_empty(list); +- if (!empty) +- list_del_init(list); +- grp->exclusive = 0; +- } ++ empty = hlist_unhashed(list); ++ if (!empty) ++ hlist_del_init_rcu(list); ++ grp->exclusive = 0; + + if (!empty) + unsubscribe_port(client, port, grp, &subs->info, ack); +@@ -590,8 +581,8 @@ int snd_seq_port_connect(struct snd_seq_client *connector, + + subs->info = *info; + atomic_set(&subs->ref_count, 0); +- INIT_LIST_HEAD(&subs->src_list); +- INIT_LIST_HEAD(&subs->dest_list); ++ INIT_HLIST_NODE(&subs->src_list); ++ INIT_HLIST_NODE(&subs->dest_list); + + exclusive = !!(info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE); + +@@ -612,7 +603,7 @@ int snd_seq_port_connect(struct snd_seq_client *connector, + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); + error: +- kfree(subs); ++ kfree_rcu(subs, rcu); + return err; + } + +@@ -633,7 +624,7 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector, + */ + scoped_guard(rwsem_write, &dest->list_mutex) { + /* look for the connection */ +- list_for_each_entry(subs, &dest->list_head, dest_list) { ++ hlist_for_each_entry(subs, &dest->list_head, dest_list) { + if (match_subs_info(info, &subs->info)) { + __delete_and_unsubscribe_port(dest_client, dest_port, + subs, false, +@@ -648,7 +639,7 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector, + + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); +- kfree(subs); ++ kfree_rcu(subs, rcu); + return 0; + } + +@@ -662,7 +653,7 @@ int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp, + int err = -ENOENT; + + guard(rwsem_read)(&src_grp->list_mutex); +- list_for_each_entry(s, &src_grp->list_head, src_list) { ++ hlist_for_each_entry(s, &src_grp->list_head, src_list) { + if (addr_match(dest_addr, &s->info.dest)) { + *subs = s->info; + err = 0; +diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h +--- a/sound/core/seq/seq_ports.h ++++ b/sound/core/seq/seq_ports.h +@@ -28,17 +28,17 @@ + + struct snd_seq_subscribers { + struct snd_seq_port_subscribe info; /* additional info */ +- struct list_head src_list; /* link of sources */ +- struct list_head dest_list; /* link of destinations */ ++ struct hlist_node src_list; /* link of sources */ ++ struct hlist_node dest_list; /* link of destinations */ + atomic_t ref_count; ++ struct rcu_head rcu; /* for deferred free */ + }; + + struct snd_seq_port_subs_info { +- struct list_head list_head; /* list of subscribed ports */ ++ struct hlist_head list_head; /* list of subscribed ports */ + unsigned int count; /* count of subscribers */ + unsigned int exclusive: 1; /* exclusive mode */ + struct rw_semaphore list_mutex; +- rwlock_t list_lock; + int (*open)(void *private_data, struct snd_seq_port_subscribe *info); + int (*close)(void *private_data, struct snd_seq_port_subscribe *info); + }; +diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c +--- a/sound/core/seq/seq_ump_client.c ++++ b/sound/core/seq/seq_ump_client.c +@@ -37,8 +37,11 @@ struct seq_ump_client { + struct snd_ump_endpoint *ump; /* assigned endpoint */ + int seq_client; /* sequencer client id */ + int opened[2]; /* current opens for each direction */ +- rwlock_t output_lock; /* protects out_rfile output access */ + struct snd_rawmidi_file out_rfile; /* rawmidi for output */ ++ /* RCU-protected shadow of out_rfile.output for the delivery hot path; ++ * out_rfile itself is only touched by open/close under open_mutex ++ */ ++ struct snd_rawmidi_substream __rcu *out_substream; + struct seq_ump_input_buffer input; /* input parser context */ + void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */ + struct work_struct group_notify_work; /* FB change notification */ +@@ -89,8 +92,8 @@ static int seq_ump_process_event(struct snd_seq_event *ev, int direct, + unsigned char type; + int len; + +- guard(read_lock_irqsave)(&client->output_lock); +- substream = client->out_rfile.output; ++ guard(rcu)(); ++ substream = rcu_dereference(client->out_substream); + if (!substream) + return -ENODEV; + if (!snd_seq_ev_is_ump(ev)) +@@ -108,19 +111,22 @@ static int seq_ump_process_event(struct snd_seq_event *ev, int direct, + static int seq_ump_client_open(struct seq_ump_client *client, int dir) + { + struct snd_ump_endpoint *ump = client->ump; +- struct snd_rawmidi_file rfile = {}; + int err; + + guard(mutex)(&ump->open_mutex); + if (dir == STR_OUT && !client->opened[dir]) { ++ /* out_rfile is only accessed under open_mutex; the delivery ++ * path reads out_substream via RCU, so open into out_rfile ++ * directly and publish the substream afterwards ++ */ + err = snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT | + SNDRV_RAWMIDI_LFLG_APPEND, +- &rfile); ++ &client->out_rfile); + if (err < 0) + return err; +- scoped_guard(write_lock_irqsave, &client->output_lock) +- client->out_rfile = rfile; ++ rcu_assign_pointer(client->out_substream, ++ client->out_rfile.output); + } + client->opened[dir]++; + return 0; +@@ -130,17 +136,18 @@ static int seq_ump_client_open(struct seq_ump_client *client, int dir) + static int seq_ump_client_close(struct seq_ump_client *client, int dir) + { + struct snd_ump_endpoint *ump = client->ump; +- struct snd_rawmidi_file rfile = {}; + + guard(mutex)(&ump->open_mutex); + if (!--client->opened[dir]) { +- if (dir == STR_OUT) { +- scoped_guard(write_lock_irqsave, &client->output_lock) { +- rfile = client->out_rfile; +- client->out_rfile = (struct snd_rawmidi_file){}; +- } +- if (rfile.rmidi) +- snd_rawmidi_kernel_release(&rfile); ++ if (dir == STR_OUT && client->out_rfile.rmidi) { ++ rcu_assign_pointer(client->out_substream, NULL); ++ /* wait for a grace period so that no reader in the ++ * delivery path is still writing to the substream ++ * before it is released ++ */ ++ synchronize_rcu(); ++ snd_rawmidi_kernel_release(&client->out_rfile); ++ client->out_rfile = (struct snd_rawmidi_file){}; + } + } + return 0; +@@ -480,7 +487,6 @@ static int snd_seq_ump_probe(struct snd_seq_device *dev) + + INIT_WORK(&client->group_notify_work, handle_group_notify); + client->ump = ump; +- rwlock_init(&client->output_lock); + + client->seq_client = + snd_seq_create_kernel_client(card, ump->core.device, +diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c +--- a/sound/core/seq/seq_virmidi.c ++++ b/sound/core/seq/seq_virmidi.c +@@ -78,10 +78,11 @@ static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, + int len; + + if (atomic) +- read_lock(&rdev->filelist_lock); ++ rcu_read_lock(); + else + down_read(&rdev->filelist_sem); +- list_for_each_entry(vmidi, &rdev->filelist, list) { ++ list_for_each_entry_rcu(vmidi, &rdev->filelist, list, ++ lockdep_is_held(&rdev->filelist_sem)) { + if (!READ_ONCE(vmidi->trigger)) + continue; + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { +@@ -96,7 +97,7 @@ static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, + } + } + if (atomic) +- read_unlock(&rdev->filelist_lock); ++ rcu_read_unlock(); + else + up_read(&rdev->filelist_sem); + +@@ -200,8 +201,7 @@ static int snd_virmidi_input_open(struct snd_rawmidi_substream *substream) + vmidi->port = rdev->port; + runtime->private_data = vmidi; + scoped_guard(rwsem_write, &rdev->filelist_sem) { +- guard(write_lock_irq)(&rdev->filelist_lock); +- list_add_tail(&vmidi->list, &rdev->filelist); ++ list_add_tail_rcu(&vmidi->list, &rdev->filelist); + } + vmidi->rdev = rdev; + return 0; +@@ -243,9 +243,13 @@ static int snd_virmidi_input_close(struct snd_rawmidi_substream *substream) + struct snd_virmidi *vmidi = substream->runtime->private_data; + + scoped_guard(rwsem_write, &rdev->filelist_sem) { +- guard(write_lock_irq)(&rdev->filelist_lock); +- list_del(&vmidi->list); ++ list_del_rcu(&vmidi->list); + } ++ /* wait for a grace period so that lockless readers in the atomic ++ * delivery path (snd_virmidi_dev_receive_event()) are no longer ++ * traversing this entry before its parser and memory are freed ++ */ ++ synchronize_rcu(); + snd_midi_event_free(vmidi->parser); + substream->runtime->private_data = NULL; + kfree(vmidi); +@@ -508,7 +512,6 @@ int snd_virmidi_new(struct snd_card *card, int device, struct snd_rawmidi **rrmi + rdev->device = device; + rdev->client = -1; + init_rwsem(&rdev->filelist_sem); +- rwlock_init(&rdev->filelist_lock); + INIT_LIST_HEAD(&rdev->filelist); + rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH; + rmidi->private_data = rdev; +diff --git a/sound/core/sound.c b/sound/core/sound.c +--- a/sound/core/sound.c ++++ b/sound/core/sound.c +@@ -167,11 +167,10 @@ static int snd_open(struct inode *inode, struct file *file) + return err; + } + +-static const struct file_operations snd_fops = +-{ +- .owner = THIS_MODULE, +- .open = snd_open, +- .llseek = noop_llseek, ++static const struct file_operations snd_fops = { ++ .owner = THIS_MODULE, ++ .open = snd_open, ++ .llseek = noop_llseek, + }; + + #ifdef CONFIG_SND_DYNAMIC_MINORS +@@ -358,7 +357,7 @@ static void snd_minor_info_read(struct snd_info_entry *entry, struct snd_info_bu + struct snd_minor *mptr; + + guard(mutex)(&sound_mutex); +- for (minor = 0; minor < SNDRV_OS_MINORS; ++minor) { ++ for (minor = 0; minor < ARRAY_SIZE(snd_minors); ++minor) { + mptr = snd_minors[minor]; + if (!mptr) + continue; +@@ -395,15 +394,21 @@ int __init snd_minor_info_init(void) + + static int __init alsa_sound_init(void) + { ++ int err; ++ + snd_major = major; + snd_ecards_limit = cards_limit; +- if (register_chrdev(major, "alsa", &snd_fops)) { ++ ++ err = register_chrdev(major, "alsa", &snd_fops); ++ if (err < 0) { + pr_err("ALSA core: unable to register native major device number %d\n", major); +- return -EIO; ++ return err; + } +- if (snd_info_init() < 0) { ++ ++ err = snd_info_init(); ++ if (err < 0) { + unregister_chrdev(major, "alsa"); +- return -ENOMEM; ++ return err; + } + + #ifdef CONFIG_SND_DEBUG +diff --git a/sound/core/sound_kunit.c b/sound/core/sound_kunit.c +--- a/sound/core/sound_kunit.c ++++ b/sound/core/sound_kunit.c +@@ -17,8 +17,8 @@ + .name = #fmt, \ + } + +-#define WRONG_FORMAT_1 (__force snd_pcm_format_t)((__force int)SNDRV_PCM_FORMAT_LAST + 1) +-#define WRONG_FORMAT_2 (__force snd_pcm_format_t)-1 ++#define WRONG_FORMAT_1 (SNDRV_PCM_FORMAT_LAST + 1) ++#define WRONG_FORMAT_2 -1 + + #define VALID_NAME "ValidName" + #define NAME_W_SPEC_CHARS "In%v@1id name" +diff --git a/sound/core/timer.c b/sound/core/timer.c +--- a/sound/core/timer.c ++++ b/sound/core/timer.c +@@ -1546,11 +1546,8 @@ static int realloc_user_queue(struct snd_timer_user *tu, int size) + static int snd_timer_user_open(struct inode *inode, struct file *file) + { + struct snd_timer_user *tu; +- int err; + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + tu = kzalloc_obj(*tu); + if (tu == NULL) +@@ -2176,9 +2173,9 @@ static long snd_utimer_ioctl(struct file *file, unsigned int ioctl, unsigned lon + } + + static const struct file_operations snd_utimer_fops = { +- .llseek = noop_llseek, +- .release = snd_utimer_release, +- .unlocked_ioctl = snd_utimer_ioctl, ++ .llseek = noop_llseek, ++ .release = snd_utimer_release, ++ .unlocked_ioctl = snd_utimer_ioctl, + }; + + static int snd_utimer_start(struct snd_timer *t) +@@ -2530,16 +2527,15 @@ static __poll_t snd_timer_user_poll(struct file *file, poll_table * wait) + #define snd_timer_user_ioctl_compat NULL + #endif + +-static const struct file_operations snd_timer_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = snd_timer_user_read, +- .open = snd_timer_user_open, +- .release = snd_timer_user_release, +- .poll = snd_timer_user_poll, +- .unlocked_ioctl = snd_timer_user_ioctl, +- .compat_ioctl = snd_timer_user_ioctl_compat, +- .fasync = snd_timer_user_fasync, ++static const struct file_operations snd_timer_f_ops = { ++ .owner = THIS_MODULE, ++ .read = snd_timer_user_read, ++ .open = snd_timer_user_open, ++ .release = snd_timer_user_release, ++ .poll = snd_timer_user_poll, ++ .unlocked_ioctl = snd_timer_user_ioctl, ++ .compat_ioctl = snd_timer_user_ioctl_compat, ++ .fasync = snd_timer_user_fasync, + }; + + /* unregister the system timer */ +diff --git a/sound/core/ump.c b/sound/core/ump.c +--- a/sound/core/ump.c ++++ b/sound/core/ump.c +@@ -1157,10 +1157,11 @@ static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream) + return -ENODEV; + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!ump->legacy_out_opens) { +- err = snd_rawmidi_kernel_open(&ump->core, 0, +- SNDRV_RAWMIDI_LFLG_OUTPUT | +- SNDRV_RAWMIDI_LFLG_APPEND, +- &ump->legacy_out_rfile); ++ err = snd_rawmidi_kernel_open_nested(&ump->core, 0, ++ SNDRV_RAWMIDI_LFLG_OUTPUT | ++ SNDRV_RAWMIDI_LFLG_APPEND, ++ &ump->legacy_out_rfile, ++ SINGLE_DEPTH_NESTING); + if (err < 0) + return err; + } +diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c +--- a/sound/drivers/aloop.c ++++ b/sound/drivers/aloop.c +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -55,7 +56,7 @@ MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver."); + module_param_array(pcm_notify, int, NULL, 0444); + MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels changes."); + module_param_array(timer_source, charp, NULL, 0444); +-MODULE_PARM_DESC(timer_source, "Sound card name or number and device/subdevice number of timer to be used. Empty string for jiffies timer [default]."); ++MODULE_PARM_DESC(timer_source, "Sound card name or number and device/subdevice number of timer to be used. Empty string for jiffies timer [default], 'hrtimer' for high-resolution timer."); + + #define NO_PITCH 100000 + +@@ -161,8 +162,11 @@ struct loopback_pcm { + unsigned int period_size_frac; /* period size in jiffies ticks */ + unsigned int last_drift; + unsigned long last_jiffies; +- /* If jiffies timer is used */ ++ /* If jiffies / hrtimer is used */ + struct timer_list timer; ++#ifdef CONFIG_HIGH_RES_TIMERS ++ struct hrtimer hrtimer; ++#endif + + /* size of per channel buffer in case of non-interleaved access */ + unsigned int channel_buf_n; +@@ -232,6 +236,31 @@ static int loopback_jiffies_timer_start(struct loopback_pcm *dpcm) + return 0; + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++/* call in cable->lock */ ++static int loopback_hrtimer_start(struct loopback_pcm *dpcm) ++{ ++ unsigned long tick; ++ unsigned int rate_shift = get_rate_shift(dpcm); ++ ++ if (rate_shift != dpcm->pcm_rate_shift) { ++ dpcm->pcm_rate_shift = rate_shift; ++ dpcm->period_size_frac = frac_pos(dpcm, dpcm->pcm_period_size); ++ } ++ if (dpcm->period_size_frac <= dpcm->irq_pos) { ++ dpcm->irq_pos %= dpcm->period_size_frac; ++ dpcm->period_update_pending = 1; ++ } ++ tick = dpcm->period_size_frac - dpcm->irq_pos; ++ tick = DIV_ROUND_UP(tick, dpcm->pcm_bps); ++ hrtimer_start(&dpcm->hrtimer, ++ ns_to_ktime(div_u64((u64)tick * NSEC_PER_SEC, HZ)), ++ HRTIMER_MODE_REL_SOFT); ++ ++ return 0; ++} ++#endif ++ + /* call in cable->lock */ + static int loopback_snd_timer_start(struct loopback_pcm *dpcm) + { +@@ -270,6 +299,16 @@ static inline int loopback_jiffies_timer_stop(struct loopback_pcm *dpcm) + return 0; + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++/* call in cable->lock */ ++static inline int loopback_hrtimer_stop(struct loopback_pcm *dpcm) ++{ ++ hrtimer_try_to_cancel(&dpcm->hrtimer); ++ ++ return 0; ++} ++#endif ++ + /* call in cable->lock */ + static int loopback_snd_timer_stop(struct loopback_pcm *dpcm) + { +@@ -300,6 +339,15 @@ static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm) + return 0; + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static inline int loopback_hrtimer_stop_sync(struct loopback_pcm *dpcm) ++{ ++ hrtimer_cancel(&dpcm->hrtimer); ++ ++ return 0; ++} ++#endif ++ + /* call in loopback->cable_lock */ + static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm) + { +@@ -734,6 +782,30 @@ static void loopback_jiffies_timer_function(struct timer_list *t) + snd_pcm_period_elapsed(dpcm->substream); + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static enum hrtimer_restart loopback_hrtimer_function(struct hrtimer *t) ++{ ++ struct loopback_pcm *dpcm = container_of(t, struct loopback_pcm, hrtimer); ++ bool period_elapsed = false; ++ ++ scoped_guard(spinlock_irqsave, &dpcm->cable->lock) { ++ if (loopback_jiffies_timer_pos_update(dpcm->cable) & ++ (1 << dpcm->substream->stream)) { ++ loopback_hrtimer_start(dpcm); ++ if (dpcm->period_update_pending) { ++ dpcm->period_update_pending = 0; ++ period_elapsed = true; ++ } ++ } ++ } ++ ++ if (period_elapsed) ++ snd_pcm_period_elapsed(dpcm->substream); ++ ++ return HRTIMER_NORESTART; ++} ++#endif ++ + /* call in cable->lock */ + static int loopback_snd_timer_check_resolution(struct snd_pcm_runtime *runtime, + unsigned long resolution) +@@ -907,6 +979,21 @@ static void loopback_jiffies_timer_dpcm_info(struct loopback_pcm *dpcm, + snd_iprintf(buffer, " timer_expires:\t%lu\n", dpcm->timer.expires); + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static void loopback_hrtimer_dpcm_info(struct loopback_pcm *dpcm, ++ struct snd_info_buffer *buffer) ++{ ++ snd_iprintf(buffer, " update_pending:\t%u\n", ++ dpcm->period_update_pending); ++ snd_iprintf(buffer, " irq_pos:\t\t%u\n", dpcm->irq_pos); ++ snd_iprintf(buffer, " period_frac:\t%u\n", dpcm->period_size_frac); ++ snd_iprintf(buffer, " last_jiffies:\t%lu (%lu)\n", ++ dpcm->last_jiffies, jiffies); ++ snd_iprintf(buffer, " timer_expires:\t%llu\n", ++ ktime_to_ns(hrtimer_get_expires(&dpcm->hrtimer))); ++} ++#endif ++ + static void loopback_snd_timer_dpcm_info(struct loopback_pcm *dpcm, + struct snd_info_buffer *buffer) + { +@@ -1097,6 +1184,26 @@ static const struct loopback_ops loopback_jiffies_timer_ops = { + .dpcm_info = loopback_jiffies_timer_dpcm_info, + }; + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static int loopback_hrtimer_open(struct loopback_pcm *dpcm) ++{ ++ hrtimer_setup(&dpcm->hrtimer, loopback_hrtimer_function, ++ CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); ++ ++ return 0; ++} ++ ++static const struct loopback_ops loopback_hrtimer_ops = { ++ .open = loopback_hrtimer_open, ++ .start = loopback_hrtimer_start, ++ .stop = loopback_hrtimer_stop, ++ .stop_sync = loopback_hrtimer_stop_sync, ++ .close_substream = loopback_hrtimer_stop_sync, ++ .pos_update = loopback_jiffies_timer_pos_update, ++ .dpcm_info = loopback_hrtimer_dpcm_info, ++}; ++#endif ++ + static int loopback_parse_timer_id(const char *str, + struct snd_timer_id *tid) + { +@@ -1274,7 +1381,12 @@ static int loopback_open(struct snd_pcm_substream *substream) + spin_lock_init(&cable->lock); + snd_refcount_init(&cable->stop_count); + cable->hw = loopback_pcm_hardware; +- if (loopback->timer_source) ++#ifdef CONFIG_HIGH_RES_TIMERS ++ if (loopback->timer_source && !strcmp(loopback->timer_source, "hrtimer")) ++ cable->ops = &loopback_hrtimer_ops; ++ else ++#endif ++ if (loopback->timer_source && loopback->timer_source[0]) + cable->ops = &loopback_snd_timer_ops; + else + cable->ops = &loopback_jiffies_timer_ops; +@@ -1491,7 +1603,7 @@ static int loopback_format_info(struct snd_kcontrol *kcontrol, + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; +- uinfo->value.integer.max = (__force int)SNDRV_PCM_FORMAT_LAST; ++ uinfo->value.integer.max = SNDRV_PCM_FORMAT_LAST; + uinfo->value.integer.step = 1; + return 0; + } +@@ -1502,7 +1614,7 @@ static int loopback_format_get(struct snd_kcontrol *kcontrol, + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = +- (__force int)loopback->setup[kcontrol->id.subdevice] ++ loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].format; + return 0; + } +@@ -1556,7 +1668,7 @@ static int loopback_channels_get(struct snd_kcontrol *kcontrol, + static int loopback_access_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) + { +- const char * const texts[] = {"Interleaved", "Non-interleaved"}; ++ static const char * const texts[] = {"Interleaved", "Non-interleaved"}; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); + } +diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c +--- a/sound/drivers/dummy.c ++++ b/sound/drivers/dummy.c +@@ -100,6 +100,7 @@ struct dummy_timer_ops { + int (*prepare)(struct snd_pcm_substream *); + int (*start)(struct snd_pcm_substream *); + int (*stop)(struct snd_pcm_substream *); ++ int (*sync_stop)(struct snd_pcm_substream *); + snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *); + }; + +@@ -285,6 +286,14 @@ static int dummy_systimer_stop(struct snd_pcm_substream *substream) + return 0; + } + ++static int dummy_systimer_sync_stop(struct snd_pcm_substream *substream) ++{ ++ struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; ++ ++ timer_delete_sync(&dpcm->timer); ++ return 0; ++} ++ + static int dummy_systimer_prepare(struct snd_pcm_substream *substream) + { + struct snd_pcm_runtime *runtime = substream->runtime; +@@ -341,7 +350,10 @@ static int dummy_systimer_create(struct snd_pcm_substream *substream) + + static void dummy_systimer_free(struct snd_pcm_substream *substream) + { +- kfree(substream->runtime->private_data); ++ struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; ++ ++ timer_shutdown_sync(&dpcm->timer); ++ kfree(dpcm); + } + + static const struct dummy_timer_ops dummy_systimer_ops = { +@@ -350,6 +362,7 @@ static const struct dummy_timer_ops dummy_systimer_ops = { + .prepare = dummy_systimer_prepare, + .start = dummy_systimer_start, + .stop = dummy_systimer_stop, ++ .sync_stop = dummy_systimer_sync_stop, + .pointer = dummy_systimer_pointer, + }; + +@@ -407,9 +420,12 @@ static int dummy_hrtimer_stop(struct snd_pcm_substream *substream) + return 0; + } + +-static inline void dummy_hrtimer_sync(struct dummy_hrtimer_pcm *dpcm) ++static int dummy_hrtimer_sync_stop(struct snd_pcm_substream *substream) + { ++ struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; ++ + hrtimer_cancel(&dpcm->timer); ++ return 0; + } + + static snd_pcm_uframes_t +@@ -435,7 +451,6 @@ static int dummy_hrtimer_prepare(struct snd_pcm_substream *substream) + long sec; + unsigned long nsecs; + +- dummy_hrtimer_sync(dpcm); + period = runtime->period_size; + rate = runtime->rate; + sec = period / rate; +@@ -463,7 +478,7 @@ static int dummy_hrtimer_create(struct snd_pcm_substream *substream) + static void dummy_hrtimer_free(struct snd_pcm_substream *substream) + { + struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; +- dummy_hrtimer_sync(dpcm); ++ + kfree(dpcm); + } + +@@ -473,6 +488,7 @@ static const struct dummy_timer_ops dummy_hrtimer_ops = { + .prepare = dummy_hrtimer_prepare, + .start = dummy_hrtimer_start, + .stop = dummy_hrtimer_stop, ++ .sync_stop = dummy_hrtimer_sync_stop, + .pointer = dummy_hrtimer_pointer, + }; + +@@ -495,6 +511,13 @@ static int dummy_pcm_trigger(struct snd_pcm_substream *substream, int cmd) + return -EINVAL; + } + ++static int dummy_pcm_sync_stop(struct snd_pcm_substream *substream) ++{ ++ if (get_dummy_ops(substream)->sync_stop) ++ return get_dummy_ops(substream)->sync_stop(substream); ++ return 0; ++} ++ + static int dummy_pcm_prepare(struct snd_pcm_substream *substream) + { + return get_dummy_ops(substream)->prepare(substream); +@@ -646,6 +669,7 @@ static const struct snd_pcm_ops dummy_pcm_ops = { + .hw_params = dummy_pcm_hw_params, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, ++ .sync_stop = dummy_pcm_sync_stop, + .pointer = dummy_pcm_pointer, + }; + +@@ -655,6 +679,7 @@ static const struct snd_pcm_ops dummy_pcm_ops_no_buf = { + .hw_params = dummy_pcm_hw_params, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, ++ .sync_stop = dummy_pcm_sync_stop, + .pointer = dummy_pcm_pointer, + .copy = dummy_pcm_copy, + .fill_silence = dummy_pcm_silence, +diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c +--- a/sound/hda/codecs/ca0132.c ++++ b/sound/hda/codecs/ca0132.c +@@ -5788,7 +5788,8 @@ static int ca0132_alt_mic_boost_info(struct snd_kcontrol *kcontrol, + uinfo->value.enumerated.items = MIC_BOOST_NUM_OF_STEPS; + if (uinfo->value.enumerated.item >= MIC_BOOST_NUM_OF_STEPS) + uinfo->value.enumerated.item = MIC_BOOST_NUM_OF_STEPS - 1; +- sprintf(namestr, "%d %s", (uinfo->value.enumerated.item * 10), sfx); ++ snprintf(namestr, sizeof(namestr), "%d %s", ++ (uinfo->value.enumerated.item * 10), sfx); + strscpy(uinfo->value.enumerated.name, namestr); + return 0; + } +@@ -5840,9 +5841,9 @@ static int ae5_headphone_gain_info(struct snd_kcontrol *kcontrol, + uinfo->value.enumerated.items = AE5_HEADPHONE_GAIN_MAX; + if (uinfo->value.enumerated.item >= AE5_HEADPHONE_GAIN_MAX) + uinfo->value.enumerated.item = AE5_HEADPHONE_GAIN_MAX - 1; +- sprintf(namestr, "%s %s", +- ae5_headphone_gain_presets[uinfo->value.enumerated.item].name, +- sfx); ++ snprintf(namestr, sizeof(namestr), "%s %s", ++ ae5_headphone_gain_presets[uinfo->value.enumerated.item].name, ++ sfx); + strscpy(uinfo->value.enumerated.name, namestr); + return 0; + } +@@ -5894,8 +5895,8 @@ static int ae5_sound_filter_info(struct snd_kcontrol *kcontrol, + uinfo->value.enumerated.items = AE5_SOUND_FILTER_MAX; + if (uinfo->value.enumerated.item >= AE5_SOUND_FILTER_MAX) + uinfo->value.enumerated.item = AE5_SOUND_FILTER_MAX - 1; +- sprintf(namestr, "%s", +- ae5_filter_presets[uinfo->value.enumerated.item].name); ++ snprintf(namestr, sizeof(namestr), "%s", ++ ae5_filter_presets[uinfo->value.enumerated.item].name); + strscpy(uinfo->value.enumerated.name, namestr); + return 0; + } +@@ -6632,7 +6633,7 @@ static int ca0132_alt_add_effect_slider(struct hda_codec *codec, hda_nid_t nid, + struct snd_kcontrol_new knew = + HDA_CODEC_VOLUME_MONO(namestr, nid, 1, 0, type); + +- sprintf(namestr, "FX: %s %s Volume", pfx, dirstr[dir]); ++ snprintf(namestr, sizeof(namestr), "FX: %s %s Volume", pfx, dirstr[dir]); + + knew.tlv.c = NULL; + +@@ -6671,9 +6672,9 @@ static int add_fx_switch(struct hda_codec *codec, hda_nid_t nid, + * prefix to OutFX or InFX enable controls. + */ + if (ca0132_use_alt_controls(spec) && (nid <= IN_EFFECT_END_NID)) +- sprintf(namestr, "FX: %s %s Switch", pfx, dirstr[dir]); ++ snprintf(namestr, sizeof(namestr), "FX: %s %s Switch", pfx, dirstr[dir]); + else +- sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]); ++ snprintf(namestr, sizeof(namestr), "%s %s Switch", pfx, dirstr[dir]); + + return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); + } +@@ -8530,10 +8531,9 @@ static void ca0132_set_dsp_msr(struct hda_codec *codec, bool is96k) + + static bool ca0132_download_dsp_images(struct hda_codec *codec) + { +- bool dsp_loaded = false; + struct ca0132_spec *spec = codec->spec; + const struct dsp_image_seg *dsp_os_image; +- const struct firmware *fw_entry = NULL; ++ const struct firmware *fw_entry __free(firmware) = NULL; + /* + * Alternate firmwares for different variants. The Recon3Di apparently + * can use the default firmware, but I'll leave the option in case +@@ -8573,15 +8573,10 @@ static bool ca0132_download_dsp_images(struct hda_codec *codec) + dsp_os_image = (struct dsp_image_seg *)(fw_entry->data); + if (dspload_image(codec, dsp_os_image, 0, 0, true, 0)) { + codec_err(codec, "ca0132 DSP load image failed\n"); +- goto exit_download; ++ return false; + } + +- dsp_loaded = dspload_wait_loaded(codec); +- +-exit_download: +- release_firmware(fw_entry); +- +- return dsp_loaded; ++ return dspload_wait_loaded(codec); + } + + static void ca0132_download_dsp(struct hda_codec *codec) +@@ -9634,6 +9629,7 @@ static void ca0132_free(struct hda_codec *codec) + #endif + kfree(spec->spec_init_verbs); + kfree(codec->spec); ++ codec->spec = NULL; + } + + static void dbpro_free(struct hda_codec *codec) +@@ -9644,6 +9640,7 @@ static void dbpro_free(struct hda_codec *codec) + + kfree(spec->spec_init_verbs); + kfree(codec->spec); ++ codec->spec = NULL; + } + + static void ca0132_config(struct hda_codec *codec) +diff --git a/sound/hda/codecs/cm9825.c b/sound/hda/codecs/cm9825.c +--- a/sound/hda/codecs/cm9825.c ++++ b/sound/hda/codecs/cm9825.c +@@ -15,7 +15,8 @@ + + enum { + QUIRK_CM_STD = 0x0, +- QUIRK_GENE_TWL7_SSID = 0x160dc000 ++ QUIRK_GENE_TWL7_SSID = 0x160dc000, ++ QUIRK_IBP_SSID = 0x15bd3275 + }; + + /* CM9825 Offset Definitions */ +@@ -40,13 +41,19 @@ enum { + #define CM9825_VERB_SET_GAD 0x7b4 + #define CM9825_VERB_SET_TMOD 0x7b5 + #define CM9825_VERB_SET_SNR 0x7b6 ++#define CM9825_VERB_SET_OMTP 0x7ef ++#define CM9825_VERB_READ_OMTP 0xfec + + struct cmi_spec { + struct hda_gen_spec gen; + const struct hda_verb *chip_d0_verbs; + const struct hda_verb *chip_d3_verbs; ++ const struct hda_verb *chip_playback_start_verbs; ++ const struct hda_verb *chip_playback_stop_verbs; + const struct hda_verb *chip_hp_present_verbs; + const struct hda_verb *chip_hp_remove_verbs; ++ const struct hda_verb *chip_lineout_present_verbs; ++ const struct hda_verb *chip_lineout_remove_verbs; + struct hda_codec *codec; + struct delayed_work unsol_inputs_work; + struct delayed_work unsol_lineout_work; +@@ -193,6 +200,94 @@ static const struct hda_verb cm9825_gene_twl7_playback_stop_verbs[] = { + {} + }; + ++/* ++ * To save power, AD/CLK is turned off. ++ */ ++static const struct hda_verb cm9825_ibp_d3_verbs[] = { ++ {0x43, CM9825_VERB_SET_D2S, 0x62}, ++ {0x43, CM9825_VERB_SET_PLL, 0x01}, ++ {0x43, CM9825_VERB_SET_NEG, 0xc2}, ++ {0x43, CM9825_VERB_SET_ADCL, 0x00}, ++ {0x43, CM9825_VERB_SET_DACL, 0x02}, ++ {0x43, CM9825_VERB_SET_VNEG, 0x50}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x00}, ++ {0x43, CM9825_VERB_SET_PDNEG, 0x04}, ++ {0x43, CM9825_VERB_SET_CDALR, 0xf6}, ++ {0x43, CM9825_VERB_SET_OTP, 0xcd}, ++ {} ++}; ++ ++/* ++ * D0 configuration: enable PLL/CLK/ADC/DAC and optimize performance ++ */ ++static const struct hda_verb cm9825_ibp_d0_verbs[] = { ++ {0x34, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, ++ {0x43, CM9825_VERB_SET_SNR, 0x38}, ++ {0x43, CM9825_VERB_SET_PLL, 0x00}, ++ {0x43, CM9825_VERB_SET_ADCL, 0x00}, ++ {0x43, CM9825_VERB_SET_DACL, 0x02}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x00}, ++ {0x43, CM9825_VERB_SET_VNEG, 0x56}, ++ {0x43, CM9825_VERB_SET_D2S, 0x62}, ++ {0x43, CM9825_VERB_SET_DACTRL, 0x00}, ++ {0x43, CM9825_VERB_SET_PDNEG, 0x0c}, ++ {0x43, CM9825_VERB_SET_CDALR, 0xf4}, ++ {0x43, CM9825_VERB_SET_OTP, 0xcd}, ++ {0x43, CM9825_VERB_SET_MTCBA, 0x61}, ++ {0x43, CM9825_VERB_SET_OCP, 0x33}, ++ {0x43, CM9825_VERB_SET_GAD, 0x07}, ++ {0x43, CM9825_VERB_SET_TMOD, 0x26}, ++ {0x3c, AC_VERB_SET_AMP_GAIN_MUTE | 0xa0, 0x2d}, ++ {0x3c, AC_VERB_SET_AMP_GAIN_MUTE | 0x90, 0x2d}, ++ {0x43, CM9825_VERB_SET_HPF_1, 0x40}, ++ {0x43, CM9825_VERB_SET_HPF_2, 0x40}, ++ {} ++}; ++ ++/* ++ * Enable mbias, ADC. ++ */ ++static const struct hda_verb cm9825_ibp_lineout_present_verbs[] = { ++ {0x43, CM9825_VERB_SET_ADCL, 0x8c}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x10}, ++ {} ++}; ++ ++/* ++ * Disable mbias, ADC. ++ */ ++static const struct hda_verb cm9825_ibp_lineout_remove_verbs[] = { ++ {0x43, CM9825_VERB_SET_ADCL, 0x00}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x00}, ++ {} ++}; ++ ++/* ++ * Turn on the DAC (widget NID 0x43) in the playback path by writing ++ * a sequence of vendor-specific verbs. ++ */ ++static const struct hda_verb cm9825_ibp_playback_start_verbs[] = { ++ {0x43, CM9825_VERB_SET_DACL, 0xaa}, ++ {0x43, CM9825_VERB_SET_D2S, 0xf2}, ++ {0x43, CM9825_VERB_SET_VDO, 0xc4}, ++ {0x43, CM9825_VERB_SET_SNR, 0x30}, ++ {} ++}; ++ ++/* ++ * Shut down the playback path. The order differs slightly from the ++ * enable sequence, likely to avoid audible pop noise when powering ++ * down the output stage. ++ */ ++static const struct hda_verb cm9825_ibp_playback_stop_verbs[] = { ++ {0x43, CM9825_VERB_SET_VDO, 0xc0}, ++ {0x43, CM9825_VERB_SET_DACL, 0x02}, ++ {0x43, CM9825_VERB_SET_D2S, 0x62}, ++ {0x43, CM9825_VERB_SET_VDO, 0x80}, ++ {0x43, CM9825_VERB_SET_SNR, 0x38}, ++ {} ++}; ++ + static void cm9825_update_jk_plug_status(struct hda_codec *codec, hda_nid_t nid) + { + struct cmi_spec *spec = codec->spec; +@@ -232,6 +327,23 @@ static void cm9825_unsol_lineout_delayed(struct work_struct *work) + struct cmi_spec *spec = + container_of(to_delayed_work(work), struct cmi_spec, + unsol_lineout_work); ++ hda_nid_t line_out_pin = spec->gen.autocfg.line_out_pins[0]; ++ bool line_out_jack_plugin = false; ++ ++ line_out_jack_plugin = snd_hda_jack_detect(spec->codec, line_out_pin); ++ ++ codec_dbg(spec->codec, "lineout_jack_plugin %d, lineout_pin 0x%X\n", ++ (int)line_out_jack_plugin, line_out_pin); ++ ++ if (!line_out_jack_plugin) { ++ /* Jack plugout */ ++ snd_hda_sequence_write(spec->codec, ++ spec->chip_lineout_remove_verbs); ++ } else { ++ /* Jack plugin */ ++ snd_hda_sequence_write(spec->codec, ++ spec->chip_lineout_present_verbs); ++ } + + cm9825_update_jk_plug_status(spec->codec, + spec->gen.autocfg.line_out_pins[0]); +@@ -362,12 +474,10 @@ static void cm9825_playback_pcm_hook(struct hda_pcm_stream *hinfo, + + switch (action) { + case HDA_GEN_PCM_ACT_PREPARE: +- snd_hda_sequence_write(spec->codec, +- cm9825_gene_twl7_playback_start_verbs); ++ snd_hda_sequence_write(codec, spec->chip_playback_start_verbs); + break; + case HDA_GEN_PCM_ACT_CLEANUP: +- snd_hda_sequence_write(spec->codec, +- cm9825_gene_twl7_playback_stop_verbs); ++ snd_hda_sequence_write(codec, spec->chip_playback_stop_verbs); + break; + default: + return; +@@ -471,7 +581,8 @@ static int cm9825_resume(struct hda_codec *codec) + + if (codec->core.subsystem_id == QUIRK_CM_STD) + cm9825_cm_std_resume(codec); +- else if (codec->core.subsystem_id == QUIRK_GENE_TWL7_SSID) { ++ else if (codec->core.subsystem_id == QUIRK_GENE_TWL7_SSID || ++ codec->core.subsystem_id == QUIRK_IBP_SSID) { + snd_hda_codec_init(codec); + snd_hda_sequence_write(codec, spec->chip_d0_verbs); + } +@@ -487,6 +598,7 @@ static int cm9825_probe(struct hda_codec *codec, const struct hda_device_id *id) + struct cmi_spec *spec; + struct auto_pin_cfg *cfg; + int err = 0; ++ unsigned int val; + + spec = kzalloc_obj(*spec); + if (spec == NULL) +@@ -523,9 +635,33 @@ static int cm9825_probe(struct hda_codec *codec, const struct hda_device_id *id) + spec->chip_d0_verbs = cm9825_gene_twl7_d0_verbs; + spec->chip_d3_verbs = cm9825_gene_twl7_d3_verbs; + spec->gen.pcm_playback_hook = cm9825_playback_pcm_hook; ++ spec->chip_playback_start_verbs = ++ cm9825_gene_twl7_playback_start_verbs; ++ spec->chip_playback_stop_verbs = ++ cm9825_gene_twl7_playback_stop_verbs; + /* Internal fixed device, Rear, Mic-in, 3.5mm */ + snd_hda_codec_set_pincfg(codec, 0x37, 0x24A70100); + break; ++ case QUIRK_IBP_SSID: ++ snd_hda_codec_set_name(codec, "CM9825 IBP"); ++ spec->chip_d0_verbs = cm9825_ibp_d0_verbs; ++ spec->chip_d3_verbs = cm9825_ibp_d3_verbs; ++ spec->gen.pcm_playback_hook = cm9825_playback_pcm_hook; ++ spec->chip_lineout_present_verbs = ++ cm9825_ibp_lineout_present_verbs; ++ spec->chip_lineout_remove_verbs = ++ cm9825_ibp_lineout_remove_verbs; ++ spec->chip_playback_start_verbs = ++ cm9825_ibp_playback_start_verbs; ++ spec->chip_playback_stop_verbs = cm9825_ibp_playback_stop_verbs; ++ ++ /* OMTP */ ++ val = ++ snd_hda_codec_read(codec, 0x46, 0, CM9825_VERB_READ_OMTP, ++ 0x0); ++ snd_hda_codec_write(codec, 0x46, 0, CM9825_VERB_SET_OMTP, ++ (val >> 24) & 0x7f); ++ break; + default: + err = -ENXIO; + break; +diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c +--- a/sound/hda/codecs/conexant.c ++++ b/sound/hda/codecs/conexant.c +@@ -292,6 +292,7 @@ enum { + CXT_PINCFG_TOP_SPEAKER, + CXT_FIXUP_HP_A_U, + CXT_FIXUP_ACER_SWIFT_HP, ++ CXT_FIXUP_HUAWEI_MATEBOOK_HP, + }; + + /* for hda_fixup_thinkpad_acpi() */ +@@ -451,7 +452,8 @@ static void olpc_xo_update_mic_pins(struct hda_codec *codec) + if (!spec->dc_enable) { + /* disable DC bias path and pin for port F */ + update_mic_pin(codec, 0x1e, 0); +- snd_hda_activate_path(codec, spec->dc_mode_path, false, false); ++ if (spec->dc_mode_path) ++ snd_hda_activate_path(codec, spec->dc_mode_path, false, false); + + /* update port B (ext mic) and C (int mic) */ + /* OLPC defers mic widget control until when capture is +@@ -487,7 +489,8 @@ static void olpc_xo_update_mic_pins(struct hda_codec *codec) + update_mic_pin(codec, 0x1b, 0); + /* enable DC bias path and pin */ + update_mic_pin(codec, 0x1e, spec->recording ? PIN_IN : 0); +- snd_hda_activate_path(codec, spec->dc_mode_path, true, false); ++ if (spec->dc_mode_path) ++ snd_hda_activate_path(codec, spec->dc_mode_path, true, false); + } + } + +@@ -1033,6 +1036,13 @@ static const struct hda_fixup cxt_fixups[] = { + { } + }, + }, ++ [CXT_FIXUP_HUAWEI_MATEBOOK_HP] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x18, 0x03211020 }, /* Headphone */ ++ { } ++ }, ++ }, + }; + + static const struct hda_quirk cxt5045_fixups[] = { +@@ -1097,6 +1107,7 @@ static const struct hda_quirk cxt5066_fixups[] = { + SND_PCI_QUIRK(0x103c, 0x829a, "HP 800 G3 DM", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x82b4, "HP ProDesk 600 G3", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x836e, "HP ProBook 455 G5", CXT_FIXUP_MUTE_LED_GPIO), ++ SND_PCI_QUIRK(0x103c, 0x837b, "HP ProBook 440 G5", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x837f, "HP ProBook 470 G5", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x83b2, "HP EliteBook 840 G5", CXT_FIXUP_HP_DOCK), + SND_PCI_QUIRK(0x103c, 0x83b3, "HP EliteBook 830 G5", CXT_FIXUP_HP_DOCK), +@@ -1133,6 +1144,7 @@ static const struct hda_quirk cxt5066_fixups[] = { + SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo G50-70", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad/Ideapad", CXT_FIXUP_LENOVO_XPAD_ACPI), ++ SND_PCI_QUIRK(0x19e5, 0x3289, "Huawei Matebook", CXT_FIXUP_HUAWEI_MATEBOOK_HP), + SND_PCI_QUIRK(0x1c06, 0x2011, "Lemote A1004", CXT_PINCFG_LEMOTE_A1004), + SND_PCI_QUIRK(0x1c06, 0x2012, "Lemote A1205", CXT_PINCFG_LEMOTE_A1205), + SND_PCI_QUIRK(0x1d05, 0x3012, "MECHREVO Wujie 15X Pro", CXT_FIXUP_HEADSET_MIC), +diff --git a/sound/hda/codecs/generic.c b/sound/hda/codecs/generic.c +--- a/sound/hda/codecs/generic.c ++++ b/sound/hda/codecs/generic.c +@@ -2695,30 +2695,13 @@ static const struct snd_kcontrol_new out_jack_mode_enum = { + .put = out_jack_mode_put, + }; + +-static bool find_kctl_name(struct hda_codec *codec, const char *name, int idx) +-{ +- struct hda_gen_spec *spec = codec->spec; +- const struct snd_kcontrol_new *kctl; +- int i; +- +- snd_array_for_each(&spec->kctls, i, kctl) { +- if (!strcmp(kctl->name, name) && kctl->index == idx) +- return true; +- } +- return false; +-} +- + static void get_jack_mode_name(struct hda_codec *codec, hda_nid_t pin, + char *name, size_t name_len) + { + struct hda_gen_spec *spec = codec->spec; +- int idx = 0; + +- snd_hda_get_pin_label(codec, pin, &spec->autocfg, name, name_len, &idx); +- strlcat(name, " Jack Mode", name_len); +- +- for (; find_kctl_name(codec, name, idx); idx++) +- ; ++ snd_hda_get_pin_label(codec, pin, &spec->autocfg, name, name_len); ++ hda_append_suffix(name, " Jack Mode", name_len); + } + + static int get_out_jack_num_items(struct hda_codec *codec, hda_nid_t pin) +@@ -5695,7 +5678,7 @@ static void fill_pcm_stream_name(char *str, size_t len, const char *sfx, + break; + } + } +- strlcat(str, sfx, len); ++ hda_append_suffix(str, sfx, len); + } + + /* copy PCM stream info from @default_str, and override non-NULL entries +diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c +--- a/sound/hda/codecs/hdmi/hdmi.c ++++ b/sound/hda/codecs/hdmi/hdmi.c +@@ -2236,8 +2236,10 @@ void snd_hda_hdmi_acomp_pin_eld_notify(void *audio_ptr, int port, int dev_id) + /* skip notification during system suspend (but not in runtime PM); + * the state will be updated at resume + */ +- if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND) ++ if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND) { ++ codec->acomp_requested_resume = 1; + return; ++ } + + snd_hda_hdmi_check_presence_and_report(codec, pin_nid, dev_id); + } +@@ -2346,6 +2348,7 @@ static const struct hda_device_id snd_hda_id_generichdmi[] = { + HDA_CODEC_ID_MODEL(0x1d179f8e, "KX-7000 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x1d179f8f, "KX-7000 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x1d179f90, "KX-7000 HDMI/DP", MODEL_GF), ++ HDA_CODEC_ID_MODEL(0x4c545020, "Lisuan HDMI/DP", MODEL_GENERIC), + HDA_CODEC_ID_MODEL(0x67663d82, "Arise 82 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x67663d83, "Arise 83 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x67663d84, "Arise 84 HDMI/DP", MODEL_GF), +diff --git a/sound/hda/codecs/helpers/razer_blade16_2025.c b/sound/hda/codecs/helpers/razer_blade16_2025.c +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/helpers/razer_blade16_2025.c +@@ -0,0 +1,820 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Razer Blade 16 (2025) external smart-amplifier programming and fixup, ++ * to be included from the codec driver. ++ * ++ * The internal speakers are driven by an external smart amplifier whose ++ * crossover, voicing and protection live in a DSP reached over a vendor ++ * coef mailbox on NID 0x20. These tables hold the programming captured ++ * from the Windows driver, reduced to the NID-0x20 traffic this machine ++ * needs (everything else the HDA parser rebuilds). ++ * ++ * Each entry is either a plain codec coef write or a mailbox write: ++ * strobe == 0: coef[addr] = lo ++ * strobe != 0: amp[addr] = (hi << 16) | lo, with the bank selected on ++ * port 0x89 and the write committed by the strobe opcode. ++ */ ++ ++struct alc298_razer_blade16_2025_op { ++ u16 bank; /* mailbox bank (port 0x89); unused for a plain coef */ ++ u16 addr; /* mailbox register, or plain coef index */ ++ u16 hi; /* mailbox high word; unused for a plain coef */ ++ u16 lo; /* mailbox low word, or plain coef value */ ++ u16 strobe; /* mailbox commit opcode; 0 marks a plain coef write */ ++}; ++ ++/* program the external DSP at init; the amp is woken/parked separately */ ++static const struct alc298_razer_blade16_2025_op alc298_razer_blade16_2025_amp_init[] = { ++ { 0x0000, 0xf102, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xf103, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc000, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xea00, 0x0000, 0x0047, 0xb031 }, ++ { 0x0000, 0xf20d, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xf212, 0x0000, 0x003e, 0xb031 }, ++ { 0x0000, 0xc001, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc003, 0x0000, 0x0022, 0xb031 }, ++ { 0x0000, 0xc004, 0x0000, 0x0044, 0xb031 }, ++ { 0x0000, 0xc005, 0x0000, 0x0044, 0xb031 }, ++ { 0x0000, 0xc007, 0x0000, 0x0064, 0xb031 }, ++ { 0x0000, 0xc00e, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xf223, 0x0000, 0x007f, 0xb031 }, ++ { 0x0000, 0xf224, 0x0000, 0x00db, 0xb031 }, ++ { 0x0000, 0xf225, 0x0000, 0x00ee, 0xb031 }, ++ { 0x0000, 0xf226, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xf227, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xf21a, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xf242, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xc120, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xc125, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xc321, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xc200, 0x0000, 0x00d8, 0xb031 }, ++ { 0x0000, 0xc201, 0x0000, 0x0027, 0xb031 }, ++ { 0x0000, 0xc202, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xc400, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc401, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xc402, 0x0000, 0x00e0, 0xb031 }, ++ { 0x0000, 0xc403, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc404, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xc406, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xc407, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc408, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xc300, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc125, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xdf00, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xdf5f, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xdf60, 0x0000, 0x00a7, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x0084, 0xb031 }, ++ { 0x0000, 0xc206, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xf10a, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f4, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x00e0, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f4, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x0065, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f7, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f7, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0031, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xe706, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xe707, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xe806, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xe807, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xce04, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xce05, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xce06, 0x0000, 0x0031, 0xb031 }, ++ { 0x0000, 0xce07, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xcf04, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xcf05, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xcf06, 0x0000, 0x0031, 0xb031 }, ++ { 0x0000, 0xcf07, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xc130, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xe000, 0x0000, 0x00a8, 0xb031 }, ++ { 0x4100, 0x1888, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc121, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xea00, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xf800, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xca00, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xca10, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca02, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xca12, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xed00, 0x0000, 0x0090, 0xb031 }, ++ { 0x0000, 0xcc2c, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc2d, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xcc2e, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xcc2f, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcd2c, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd2d, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xcd2e, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xcd2f, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcc24, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc25, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xcc26, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xcc27, 0x0000, 0x0085, 0xb031 }, ++ { 0x0000, 0xcd24, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd25, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xcd26, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xcd27, 0x0000, 0x0085, 0xb031 }, ++ { 0x0000, 0xcc20, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc21, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc22, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcd20, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd21, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd22, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcc16, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xcc17, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd16, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xcd17, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc29, 0x0000, 0x005d, 0xb031 }, ++ { 0x0000, 0xcc2a, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xcd29, 0x0000, 0x005d, 0xb031 }, ++ { 0x0000, 0xcd2a, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xcc31, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xcc32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc33, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc34, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd31, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xcd32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd33, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd34, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc36, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xcc37, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcc38, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcc39, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcd36, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xcd37, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcd38, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcd39, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcc09, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc0a, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xcc0b, 0x0000, 0x007c, 0xb031 }, ++ { 0x0000, 0xcc0c, 0x0000, 0x005b, 0xb031 }, ++ { 0x0000, 0xcd09, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd0a, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xcd0b, 0x0000, 0x007c, 0xb031 }, ++ { 0x0000, 0xcd0c, 0x0000, 0x005b, 0xb031 }, ++ { 0x0000, 0xcc0e, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc0f, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xcc10, 0x0000, 0x003e, 0xb031 }, ++ { 0x0000, 0xcc11, 0x0000, 0x002d, 0xb031 }, ++ { 0x0000, 0xcd0e, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd0f, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xcd10, 0x0000, 0x003e, 0xb031 }, ++ { 0x0000, 0xcd11, 0x0000, 0x002d, 0xb031 }, ++ { 0x0000, 0xccd6, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xccd7, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xcdd6, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcdd7, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xccd8, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xccd9, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xcdd8, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcdd9, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xccda, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xccdb, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xcdda, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcddb, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xc320, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xc321, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xe604, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xdd00, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xdc19, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdc1a, 0x0000, 0x006a, 0xb031 }, ++ { 0x0000, 0xdc1b, 0x0000, 0x00aa, 0xb031 }, ++ { 0x0000, 0xdc1c, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xdc1d, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdc1e, 0x0000, 0x0027, 0xb031 }, ++ { 0x0000, 0xdc1f, 0x0000, 0x0062, 0xb031 }, ++ { 0x0000, 0xdc20, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xde19, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xde1a, 0x0000, 0x006a, 0xb031 }, ++ { 0x0000, 0xde1b, 0x0000, 0x00aa, 0xb031 }, ++ { 0x0000, 0xde1c, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xde1d, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xde1e, 0x0000, 0x0027, 0xb031 }, ++ { 0x0000, 0xde1f, 0x0000, 0x0062, 0xb031 }, ++ { 0x0000, 0xde20, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xdb32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdd32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdb33, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xdd33, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xdb34, 0x0000, 0x001a, 0xb031 }, ++ { 0x0000, 0xdd34, 0x0000, 0x001a, 0xb031 }, ++ { 0x0000, 0xdb15, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xdd15, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xdb17, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xdd17, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xdb94, 0x0000, 0x0070, 0xb031 }, ++ { 0x0000, 0xdd94, 0x0000, 0x0070, 0xb031 }, ++ { 0x0000, 0xdb19, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xdd19, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x001c, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xdb04, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xdb05, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xdd04, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xdd05, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xdbbb, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xdbbc, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xdbbd, 0x0000, 0x00f3, 0xb031 }, ++ { 0x0000, 0xdbbe, 0x0000, 0x00cf, 0xb031 }, ++ { 0x0000, 0xddbb, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xddbc, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xddbd, 0x0000, 0x00f3, 0xb031 }, ++ { 0x0000, 0xddbe, 0x0000, 0x00cf, 0xb031 }, ++ { 0x0000, 0xdb01, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xdd01, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xdb08, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xdd08, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xdc52, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xde52, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0xdf0a, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdf0b, 0x0000, 0x007f, 0xb031 }, ++ { 0x0000, 0xc851, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xc951, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xdf01, 0x0000, 0x0073, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0xf800, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xebcb, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeb10, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb11, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xeb12, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xeb13, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xeb14, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb15, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xeb16, 0x0000, 0x0069, 0xb031 }, ++ { 0x0000, 0xeb21, 0x0000, 0x0011, 0xb031 }, ++ { 0x0000, 0xeb22, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xeb23, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeb24, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb25, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xeb26, 0x0000, 0x0055, 0xb031 }, ++ { 0x0000, 0xeb27, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xeb28, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb29, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xeb2a, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xeb2b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xeb30, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb31, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xeb32, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xeb33, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb34, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb35, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xeb36, 0x0000, 0x0024, 0xb031 }, ++ { 0x0000, 0xeb37, 0x0000, 0x0068, 0xb031 }, ++ { 0x0000, 0xeb40, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb41, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xeb42, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xeb43, 0x0000, 0x00e8, 0xb031 }, ++ { 0x0000, 0xeb44, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb45, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xeb46, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xeb47, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb48, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb49, 0x0000, 0x00c5, 0xb031 }, ++ { 0x0000, 0xeb4a, 0x0000, 0x0032, 0xb031 }, ++ { 0x0000, 0xeb4b, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeb50, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb51, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xeb52, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xeb53, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb54, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb55, 0x0000, 0x007d, 0xb031 }, ++ { 0x0000, 0xeb56, 0x0000, 0x00d9, 0xb031 }, ++ { 0x0000, 0xeb57, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xeb60, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xeb61, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xeb62, 0x0000, 0x00b5, 0xb031 }, ++ { 0x0000, 0xeb63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb64, 0x0000, 0x00fa, 0xb031 }, ++ { 0x0000, 0xeb65, 0x0000, 0x0086, 0xb031 }, ++ { 0x0000, 0xeb66, 0x0000, 0x00a8, 0xb031 }, ++ { 0x0000, 0xeb67, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb68, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xeb69, 0x0000, 0x009a, 0xb031 }, ++ { 0x0000, 0xeb6a, 0x0000, 0x002f, 0xb031 }, ++ { 0x0000, 0xeb6b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xebb6, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeb70, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xeb71, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xeb72, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xeb73, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xeb74, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xeb75, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xeb76, 0x0000, 0x002c, 0xb031 }, ++ { 0x0000, 0xeb77, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xeb80, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb81, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xeb82, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xeb83, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xeb84, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xeb85, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xeb86, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xeb87, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xeb88, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb89, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xeb8a, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xeb8b, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xebb8, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeb90, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xeb91, 0x0000, 0x008b, 0xb031 }, ++ { 0x0000, 0xeb92, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xeb93, 0x0000, 0x0064, 0xb031 }, ++ { 0x0000, 0xeb94, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xeb95, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xeb96, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xeb97, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xeba0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeba1, 0x0000, 0x0091, 0xb031 }, ++ { 0x0000, 0xeba2, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xeba3, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeba4, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xeba5, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeba6, 0x0000, 0x0093, 0xb031 }, ++ { 0x0000, 0xeba7, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xeba8, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xeba9, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xebaa, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xebab, 0x0000, 0x006c, 0xb031 }, ++ { 0x0000, 0xebc3, 0x0000, 0x00ff, 0xb031 }, ++ { 0x0000, 0xebc4, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xeb00, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xeb01, 0x0000, 0x00a9, 0xb031 }, ++ { 0x0000, 0xeb02, 0x0000, 0x00df, 0xb031 }, ++ { 0x0000, 0xeb03, 0x0000, 0x007b, 0xb031 }, ++ { 0x0000, 0xeb05, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xeb06, 0x0000, 0x005a, 0xb031 }, ++ { 0x0000, 0xeb07, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xebc0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xd0cb, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xd0b0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd010, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd011, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd012, 0x0000, 0x0092, 0xb031 }, ++ { 0x0000, 0xd013, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd014, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd015, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xd016, 0x0000, 0x0072, 0xb031 }, ++ { 0x0000, 0xd017, 0x0000, 0x00b0, 0xb031 }, ++ { 0x0000, 0xd020, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd021, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd022, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd023, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd024, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd025, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd026, 0x0000, 0x0090, 0xb031 }, ++ { 0x0000, 0xd027, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xd028, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd029, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd02a, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd02b, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c3, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c4, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd0c0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xce6a, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xce63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xce64, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xdb04, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xdb05, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xcc00, 0x0000, 0x00c1, 0xb031 }, ++ { 0x0000, 0xca02, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xca00, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca62, 0x0000, 0x0077, 0xb031 }, ++ { 0x0000, 0xca65, 0x0000, 0x0082, 0xb031 }, ++ { 0x0000, 0xca68, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xca75, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xca7b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xca00, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xca02, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc860, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc861, 0x0000, 0x00e5, 0xb031 }, ++ { 0x0000, 0xc862, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xc863, 0x0000, 0x00c6, 0xb031 }, ++ { 0x0000, 0xc864, 0x0000, 0x00ba, 0xb031 }, ++ { 0x0000, 0xc865, 0x0000, 0x0061, 0xb031 }, ++ { 0x0000, 0xc867, 0x0000, 0x0017, 0xb031 }, ++ { 0x0000, 0xc868, 0x0000, 0x00ed, 0xb031 }, ++ { 0x0000, 0xc869, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xc86a, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xc86b, 0x0000, 0x00e2, 0xb031 }, ++ { 0x0000, 0xc875, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xc87b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xc836, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc8a0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc833, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xc834, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc83f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc83e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc83a, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xc83b, 0x0000, 0x0089, 0xb031 }, ++ { 0x0000, 0xc830, 0x0000, 0x0013, 0xb031 }, ++ { 0x0000, 0xc826, 0x0000, 0x00d1, 0xb031 }, ++ { 0x0000, 0xc890, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc823, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc824, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xc82f, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xc82e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc820, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc816, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc880, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xc813, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc814, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc81f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc81e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc81a, 0x0000, 0x0066, 0xb031 }, ++ { 0x0000, 0xc81b, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc810, 0x0000, 0x000d, 0xb031 }, ++ { 0x0000, 0xc846, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc8d0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc844, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc84f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc84e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc84a, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc84b, 0x0000, 0x0056, 0xb031 }, ++ { 0x0000, 0xc800, 0x0000, 0x00b0, 0xb031 }, ++ { 0x0000, 0xc851, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xc500, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc501, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc502, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc503, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc504, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc505, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc506, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc507, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc510, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc511, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc512, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc513, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc514, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc515, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc516, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc517, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc518, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc541, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xd250, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xd251, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xd264, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0xf800, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xecb0, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec10, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec11, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xec12, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xec13, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xec14, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec15, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xec16, 0x0000, 0x0069, 0xb031 }, ++ { 0x0000, 0xec21, 0x0000, 0x0011, 0xb031 }, ++ { 0x0000, 0xec22, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xec23, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xec24, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec25, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xec26, 0x0000, 0x0055, 0xb031 }, ++ { 0x0000, 0xec27, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xec28, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec29, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xec2a, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xec2b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xecb2, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec30, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec31, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xec32, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xec33, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec34, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec35, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xec36, 0x0000, 0x0024, 0xb031 }, ++ { 0x0000, 0xec37, 0x0000, 0x0068, 0xb031 }, ++ { 0x0000, 0xec40, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec41, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xec42, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xec43, 0x0000, 0x00e8, 0xb031 }, ++ { 0x0000, 0xec44, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec45, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xec46, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xec47, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec48, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec49, 0x0000, 0x00c5, 0xb031 }, ++ { 0x0000, 0xec4a, 0x0000, 0x0032, 0xb031 }, ++ { 0x0000, 0xec4b, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xecb4, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec50, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec51, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xec52, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xec53, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec54, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec55, 0x0000, 0x007d, 0xb031 }, ++ { 0x0000, 0xec56, 0x0000, 0x00d9, 0xb031 }, ++ { 0x0000, 0xec57, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec60, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xec61, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xec62, 0x0000, 0x00b5, 0xb031 }, ++ { 0x0000, 0xec63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec64, 0x0000, 0x00fa, 0xb031 }, ++ { 0x0000, 0xec65, 0x0000, 0x0086, 0xb031 }, ++ { 0x0000, 0xec66, 0x0000, 0x00a8, 0xb031 }, ++ { 0x0000, 0xec67, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec68, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xec69, 0x0000, 0x009a, 0xb031 }, ++ { 0x0000, 0xec6a, 0x0000, 0x002f, 0xb031 }, ++ { 0x0000, 0xec6b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xecb6, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xec70, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xec71, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xec72, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xec73, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xec74, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xec75, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xec76, 0x0000, 0x002c, 0xb031 }, ++ { 0x0000, 0xec77, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xec80, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec81, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xec82, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xec83, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xec84, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xec85, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xec86, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xec87, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xec88, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec89, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xec8a, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xec8b, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xecb8, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xec90, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xec91, 0x0000, 0x008b, 0xb031 }, ++ { 0x0000, 0xec92, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xec93, 0x0000, 0x0064, 0xb031 }, ++ { 0x0000, 0xec94, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xec95, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xec96, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xec97, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xeca0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeca1, 0x0000, 0x0091, 0xb031 }, ++ { 0x0000, 0xeca2, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xeca3, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeca4, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xeca5, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeca6, 0x0000, 0x0093, 0xb031 }, ++ { 0x0000, 0xeca7, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xeca8, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xeca9, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xecaa, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xecab, 0x0000, 0x006c, 0xb031 }, ++ { 0x0000, 0xebc3, 0x0000, 0x00ff, 0xb031 }, ++ { 0x0000, 0xebc4, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xec00, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xec01, 0x0000, 0x00a9, 0xb031 }, ++ { 0x0000, 0xec02, 0x0000, 0x00df, 0xb031 }, ++ { 0x0000, 0xec03, 0x0000, 0x007b, 0xb031 }, ++ { 0x0000, 0xec05, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xec06, 0x0000, 0x005a, 0xb031 }, ++ { 0x0000, 0xec07, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xebc0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xd1b0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd110, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd111, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd112, 0x0000, 0x0092, 0xb031 }, ++ { 0x0000, 0xd113, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd114, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd115, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xd116, 0x0000, 0x0072, 0xb031 }, ++ { 0x0000, 0xd117, 0x0000, 0x00b0, 0xb031 }, ++ { 0x0000, 0xd120, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd121, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd122, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd123, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd124, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd125, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd126, 0x0000, 0x0090, 0xb031 }, ++ { 0x0000, 0xd127, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xd128, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd129, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd12a, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd12b, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c3, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c4, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd0c0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xce63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xce64, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xdd04, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xdd05, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xca12, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xca10, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca82, 0x0000, 0x0077, 0xb031 }, ++ { 0x0000, 0xca85, 0x0000, 0x0082, 0xb031 }, ++ { 0x0000, 0xca88, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xca95, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xca9b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xca10, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca12, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc960, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc961, 0x0000, 0x00e5, 0xb031 }, ++ { 0x0000, 0xc962, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xc963, 0x0000, 0x00c6, 0xb031 }, ++ { 0x0000, 0xc964, 0x0000, 0x00ba, 0xb031 }, ++ { 0x0000, 0xc965, 0x0000, 0x0061, 0xb031 }, ++ { 0x0000, 0xc967, 0x0000, 0x0017, 0xb031 }, ++ { 0x0000, 0xc968, 0x0000, 0x00ed, 0xb031 }, ++ { 0x0000, 0xc969, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xc96a, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xc96b, 0x0000, 0x00e2, 0xb031 }, ++ { 0x0000, 0xc975, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xc97b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xc936, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc9a0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc933, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xc934, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc93f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc93e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc93a, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xc93b, 0x0000, 0x0089, 0xb031 }, ++ { 0x0000, 0xc930, 0x0000, 0x0013, 0xb031 }, ++ { 0x0000, 0xc926, 0x0000, 0x00d1, 0xb031 }, ++ { 0x0000, 0xc990, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc923, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc924, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xc92f, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xc92e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc920, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc916, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc980, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xc913, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc914, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc91f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc91e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc91a, 0x0000, 0x0066, 0xb031 }, ++ { 0x0000, 0xc91b, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc910, 0x0000, 0x000d, 0xb031 }, ++ { 0x0000, 0xc946, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc9d0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc944, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc94f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc94e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc94a, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc94b, 0x0000, 0x0056, 0xb031 }, ++ { 0x0000, 0xc951, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xc600, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc601, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc602, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc603, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc604, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc605, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc606, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc607, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc610, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc611, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc612, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc613, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc614, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc615, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc616, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc617, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc618, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc541, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xc802, 0x0000, 0x00d2, 0xb031 }, ++ { 0x0000, 0xc902, 0x0000, 0x00d2, 0xb031 }, ++ { 0x0000, 0xc800, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd250, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xd252, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xd264, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xdf00, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xdbb5, 0x0041, 0xd27f, 0xb037 }, ++ { 0x0000, 0xddb5, 0x0040, 0x6e3b, 0xb037 }, ++ { 0x0000, 0xdb93, 0x0000, 0x009e, 0xb033 }, ++ { 0x0000, 0xdd93, 0x0000, 0x009e, 0xb033 }, ++ { 0x0000, 0xdb12, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xdd12, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xdb08, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xdd08, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0x0010, 0x0000, 0x0f21, 0x0000 }, ++}; ++ ++/* wake the amp (ea00=0x43, c121=0x0b, f109=0xe0) */ ++static const struct alc298_razer_blade16_2025_op alc298_razer_blade16_2025_amp_wake[] = { ++ { 0x0000, 0xea00, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xc121, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x00e0, 0xb031 }, ++}; ++ ++/* park the amp (ea00=0x47, c121=0x0a, f109=0xa0) */ ++static const struct alc298_razer_blade16_2025_op alc298_razer_blade16_2025_amp_sleep[] = { ++ { 0x0000, 0xea00, 0x0000, 0x0047, 0xb031 }, ++ { 0x0000, 0xc121, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x00a0, 0xb031 }, ++}; ++ ++/* ++ * Razer Blade 16 (2025): Realtek ALC298 driving an external smart amp. ++ * Crossover, voicing and speaker protection live in an external DSP ++ * reached over a vendor-coef mailbox on NID 0x20; the captured DSP ++ * programming is in the tables above. NID 0x14, the BIOS-disabled ++ * tweeter pin, is re-exposed via a pin-config override so the parser ++ * drives it as a normal internal speaker (shared volume/mute, auto-mute ++ * on headphone insertion). ++ * ++ * alc298_razer_blade16_2025_apply() runs one op-table: a plain coef write ++ * (strobe == 0) goes through alc_write_coef_idx(), while a mailbox op ++ * selects a bank and streams four PROC_COEF words (addr/hi/lo/commit) ++ * into a single coef index (0x23) on NID 0x20 to reach the external DSP ++ * -- that one-index/many-words form is why it cannot be expressed as an ++ * alc_process_coef_fw() table (one value per index). ++ */ ++static void alc298_razer_blade16_2025_apply(struct hda_codec *codec, ++ const struct alc298_razer_blade16_2025_op *seq, ++ int num) ++{ ++ int i; ++ ++ for (i = 0; i < num; i++) { ++ if (!seq[i].strobe) { ++ alc_write_coef_idx(codec, seq[i].addr, seq[i].lo); ++ continue; ++ } ++ alc_write_coef_idx(codec, 0x89, seq[i].bank); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 0x23); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].addr); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].hi); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].lo); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].strobe); ++ } ++} ++ ++static void alc298_razer_blade16_2025_pcm_hook(struct hda_pcm_stream *hinfo, ++ struct hda_codec *codec, ++ struct snd_pcm_substream *substream, ++ int action) ++{ ++ /* power the external amp only while a stream is running */ ++ switch (action) { ++ case HDA_GEN_PCM_ACT_PREPARE: ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_wake, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_wake)); ++ break; ++ case HDA_GEN_PCM_ACT_CLEANUP: ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_sleep, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_sleep)); ++ break; ++ } ++} ++ ++static void alc298_fixup_razer_blade16_2025(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ /* ++ * Pin every output to a fixed converter so the routing does not ++ * rely on the generic parser's DAC-allocation heuristics: without ++ * a fixed assignment the parser brings up only one speaker pin and ++ * leaves the other silent, and the allocation could change between ++ * kernel versions. Both speaker pins share DAC 0x03 through mixer ++ * 0x0d: that is the only converter that drives the woofer (NID ++ * 0x17) on this machine -- feeding it from DAC 0x02 or the digital ++ * converter 0x06 leaves the woofer silent. Both pins carry the ++ * same stereo stream and the external amp does the crossover. ++ * Headphone NID 0x21 keeps DAC 0x02. ++ */ ++ static const hda_nid_t preferred_pairs[] = { ++ 0x21, 0x02, ++ 0x14, 0x03, ++ 0x17, 0x03, ++ 0 ++ }; ++ struct alc_spec *spec = codec->spec; ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->gen.preferred_dacs = preferred_pairs; ++ break; ++ case HDA_FIXUP_ACT_PROBE: ++ spec->gen.pcm_playback_hook = alc298_razer_blade16_2025_pcm_hook; ++ break; ++ case HDA_FIXUP_ACT_INIT: ++ /* ++ * Wake the amp, program its DSP at boot and on resume, then ++ * park it. It is woken again for playback and parked ++ * afterwards from the pcm_playback_hook. ++ */ ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_wake, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_wake)); ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_init, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_init)); ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_sleep, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_sleep)); ++ break; ++ } ++} +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -296,7 +296,8 @@ static void alc282_init(struct hda_codec *codec) + msleep(100); + + /* Headphone capless set to normal mode */ +- alc_write_coef_idx(codec, 0x78, coef78); ++ if (coef78 != -1) ++ alc_write_coef_idx(codec, 0x78, coef78); + } + + static void alc282_shutup(struct hda_codec *codec) +@@ -333,7 +334,8 @@ static void alc282_shutup(struct hda_codec *codec) + + alc_auto_setup_eapd(codec, false); + alc_shutup_pins(codec); +- alc_write_coef_idx(codec, 0x78, coef78); ++ if (coef78 != -1) ++ alc_write_coef_idx(codec, 0x78, coef78); + } + + static const struct coef_fw alc283_coefs[] = { +@@ -585,15 +587,19 @@ static void alc285_hp_init(struct hda_codec *codec) + + alc_write_coefex_idx(codec, 0x58, 0x00, 0xf888); /* HP depop procedure start */ + val = alc_read_coefex_idx(codec, 0x58, 0x00); +- for (i = 0; i < 20 && val & 0x8000; i++) { ++ for (i = 0; i < 20 && val != -1 && val & 0x8000; i++) { + msleep(50); + val = alc_read_coefex_idx(codec, 0x58, 0x00); + } /* Wait for depop procedure finish */ + +- alc_write_coefex_idx(codec, 0x58, 0x00, val); /* write back the result */ +- alc_update_coef_idx(codec, 0x38, 1<<4, coef38); +- alc_update_coef_idx(codec, 0x0d, 0x110, coef0d); +- alc_update_coef_idx(codec, 0x36, 3<<13, coef36); ++ if (val != -1) ++ alc_write_coefex_idx(codec, 0x58, 0x00, val); /* write back the result */ ++ if (coef38 != -1) ++ alc_update_coef_idx(codec, 0x38, 1<<4, coef38); ++ if (coef0d != -1) ++ alc_update_coef_idx(codec, 0x0d, 0x110, coef0d); ++ if (coef36 != -1) ++ alc_update_coef_idx(codec, 0x36, 3<<13, coef36); + + msleep(50); + alc_update_coef_idx(codec, 0x4a, 1<<15, 0); +@@ -858,7 +864,7 @@ static void alc294_hp_init(struct hda_codec *codec) + + /* Wait for depop procedure finish */ + val = alc_read_coefex_idx(codec, 0x58, 0x01); +- for (i = 0; i < 20 && val & 0x0080; i++) { ++ for (i = 0; i < 20 && val != -1 && val & 0x0080; i++) { + msleep(50); + val = alc_read_coefex_idx(codec, 0x58, 0x01); + } +@@ -1392,6 +1398,15 @@ static void alc245_fixup_hp_gpio_led(struct hda_codec *codec, + alc_fixup_hp_gpio_led(codec, action, 0, 0x04); + } + ++static void alc245_fixup_minisforum_jack_detect(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ if (action == HDA_FIXUP_ACT_INIT) ++ /* Clear Reset HP JD while preserving the other coefficient bits. */ ++ alc_update_coef_idx(codec, 0x4a, BIT(15), 0); ++} ++ + /* turn on/off mic-mute LED per capture hook via VREF change */ + static int vref_micmute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +@@ -2557,6 +2572,13 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec, + } + } + ++static void alc285_lenovo_dac_rename(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ if (action == HDA_FIXUP_ACT_BUILD) ++ rename_ctl(codec, "Line Out Playback Volume", ++ "Headphone Playback Volume"); ++} + static void alc290_fixup_mono_speakers(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { +@@ -3262,6 +3284,36 @@ static void find_cirrus_companion_amps(struct hda_codec *cdc) + comp_generic_fixup(cdc, HDA_FIXUP_ACT_PRE_PROBE, bus, acpi_ids[i].hid, match, count); + } + ++static void aw88399_fixup_i2c_two(struct hda_codec *cdc, const struct hda_fixup *fix, int action) ++{ ++ comp_generic_fixup(cdc, action, "i2c", "AWDZ8399", "-%s:00-aw88399-hda.%d", 2); ++} ++ ++static void alc287_fixup_legion_16iax10h_aw88399(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ /* ++ * Force DAC 0x02 for the bass speaker 0x17, as the default 0x06 lacks volume controls. ++ */ ++ static const hda_nid_t conn[] = { 0x02 }; ++ struct alc_spec *spec = codec->spec; ++ ++ alc269_fixup_limit_int_mic_boost(codec, fix, action); ++ alc_fixup_headset_mode_no_hp_mic(codec, fix, action); ++ alc_fixup_headset_jack(codec, fix, action); ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->gen.suppress_auto_mic = 1; ++ snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn); ++ break; ++ case HDA_FIXUP_ACT_BUILD: ++ spec->gen.multiout.max_channels = 2; ++ spec->gen.pcm_rec[0]->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = 2; ++ break; ++ } ++} ++ + static void cs35l41_fixup_i2c_two(struct hda_codec *cdc, const struct hda_fixup *fix, int action) + { + comp_generic_fixup(cdc, action, "i2c", "CSC3551", "-%s:00-cs35l41-hda.%d", 2); +@@ -3357,6 +3409,9 @@ static void alc287_fixup_acer_micmute_led(struct hda_codec *codec, + /* for alc285_fixup_ideapad_s740_coef() */ + #include "../helpers/ideapad_s740.c" + ++/* for alc298_fixup_razer_blade16_2025() */ ++#include "../helpers/razer_blade16_2025.c" ++ + static const struct coef_fw alc256_fixup_set_coef_defaults_coefs[] = { + WRITE_COEF(0x10, 0x0020), WRITE_COEF(0x24, 0x0000), + WRITE_COEF(0x26, 0x0000), WRITE_COEF(0x29, 0x3000), +@@ -3524,6 +3579,16 @@ static void alc287_fixup_yoga9_14iap7_bass_spk_pin(struct hda_codec *codec, + } + } + ++static void alc_fixup_yoga_pro7_audio(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ /* Reuse the DAC routing selected for ThinkPad X1 Gen7 */ ++ alc285_fixup_thinkpad_x1_gen7(codec, fix, action); ++ ++ /* Limit the internal mic boost to 0 or 1 to avoid noise */ ++ alc269_fixup_limit_int_mic_boost(codec, fix, action); ++} ++ + static void alc295_fixup_dell_inspiron_top_speakers(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { +@@ -3929,6 +3994,7 @@ enum { + ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572, + ALC269_FIXUP_ACER_AC700, + ALC269_FIXUP_LIMIT_INT_MIC_BOOST, ++ ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST, + ALC269VB_FIXUP_ASUS_ZENBOOK, + ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A, + ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE, +@@ -3997,6 +4063,7 @@ enum { + ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC295_FIXUP_DISABLE_DAC3, + ALC285_FIXUP_SPEAKER2_TO_DAC1, ++ ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1, + ALC285_FIXUP_ASUS_SPEAKER2_TO_DAC1, + ALC285_FIXUP_ASUS_HEADSET_MIC, + ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS, +@@ -4066,7 +4133,6 @@ enum { + ALC294_FIXUP_ASUS_ALLY, + ALC294_FIXUP_ASUS_ALLY_PINS, + ALC294_FIXUP_ASUS_ALLY_VERBS, +- ALC294_FIXUP_ASUS_ALLY_SPEAKER, + ALC294_FIXUP_ASUS_HPE, + ALC294_FIXUP_ASUS_COEF_1B, + ALC294_FIXUP_ASUS_GX502_HP, +@@ -4091,6 +4157,8 @@ enum { + ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, + ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS, + ALC298_FIXUP_LG_GRAM_STYLE_14, ++ ALC298_FIXUP_RAZER_BLADE16_2025_PINS, ++ ALC298_FIXUP_RAZER_BLADE16_2025, + ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, + ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, + ALC295_FIXUP_ASUS_MIC_NO_PRESENCE, +@@ -4108,7 +4176,6 @@ enum { + ALC269_FIXUP_LEMOTE_A190X, + ALC256_FIXUP_INTEL_NUC8_RUGGED, + ALC233_FIXUP_INTEL_NUC8_DMIC, +- ALC233_FIXUP_INTEL_NUC8_BOOST, + ALC256_FIXUP_INTEL_NUC10, + ALC255_FIXUP_XIAOMI_HEADSET_MIC, + ALC274_FIXUP_HP_MIC, +@@ -4139,6 +4206,7 @@ enum { + ALC298_FIXUP_LENOVO_C940_DUET7, + ALC287_FIXUP_LENOVO_YOGA_BOOK_9I, + ALC287_FIXUP_LENOVO_YOGA_PRO7, ++ ALC287_FIXUP_LENOVO_XPAD_HEADSET_JACK, + ALC287_FIXUP_13S_GEN2_SPEAKERS, + ALC256_FIXUP_SET_COEF_DEFAULTS, + ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE, +@@ -4183,7 +4251,6 @@ enum { + ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD, + ALC2XX_FIXUP_HEADSET_MIC, + ALC289_FIXUP_DELL_CS35L41_SPI_2, +- ALC294_FIXUP_CS35L41_I2C_2, + ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, + ALC256_FIXUP_HEADPHONE_AMP_VOL, + ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX, +@@ -4197,6 +4264,7 @@ enum { + ALC287_FIXUP_LENOVO_THKPAD_WH_ALC1318, + ALC256_FIXUP_CHROME_BOOK, + ALC245_FIXUP_CLEVO_NOISY_MIC, ++ ALC245_FIXUP_MINISFORUM_JACK_DETECT, + ALC269_FIXUP_VAIO_VJFH52_MIC_NO_PRESENCE, + ALC233_FIXUP_MEDION_MTL_SPK, + ALC233_FIXUP_STARLABS_STARFIGHTER, +@@ -4206,7 +4274,6 @@ enum { + ALC274_FIXUP_HP_AIO_BIND_DACS, + ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2, + ALC285_FIXUP_ASUS_GA605K_HEADSET_MIC, +- ALC285_FIXUP_ASUS_GA605K_I2C_SPEAKER2_TO_DAC1, + ALC269_FIXUP_POSITIVO_P15X_HEADSET_MIC, + ALC289_FIXUP_ASUS_ZEPHYRUS_DUAL_SPK, + ALC256_FIXUP_VAIO_RPL_MIC_NO_PRESENCE, +@@ -4226,6 +4293,10 @@ enum { + ALC236_FIXUP_DELL_HP_POP_NOISE, + ALC274_FIXUP_HP_89E9_GPIO, + ALC274_FIXUP_HP_VERBS, ++ ALC287_FIXUP_AW88399_I2C_2, ++ ALC287_FIXUP_LENOVO_LEGION_AW88399, ++ ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, ++ ALC285_LENOVO_DAC_RENAME, + }; + + /* A special fixup for Lenovo C940 and Yoga Duet 7; +@@ -4263,6 +4334,20 @@ static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, + } + + static const struct hda_fixup alc269_fixups[] = { ++ [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x14, 0x90170121 }, /* tweeter as internal speaker, seq 1 */ ++ { 0x17, 0x90170120 }, /* woofer as internal speaker, seq 0 */ ++ { } ++ }, ++ .chained = true, ++ .chain_id = ALC298_FIXUP_RAZER_BLADE16_2025, ++ }, ++ [ALC298_FIXUP_RAZER_BLADE16_2025] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc298_fixup_razer_blade16_2025, ++ }, + [ALC233_FIXUP_WUJIE_SPEAKERS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { +@@ -4703,6 +4788,10 @@ static const struct hda_fixup alc269_fixups[] = { + .chain_id = ALC271_FIXUP_DMIC, + }, + [ALC269_FIXUP_LIMIT_INT_MIC_BOOST] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc269_fixup_limit_int_mic_boost ++ }, ++ [ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_limit_int_mic_boost, + .chained = true, +@@ -5078,11 +5167,7 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, + .chained = true, +- .chain_id = ALC233_FIXUP_INTEL_NUC8_BOOST, +- }, +- [ALC233_FIXUP_INTEL_NUC8_BOOST] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = alc269_fixup_limit_int_mic_boost ++ .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST, + }, + [ALC255_FIXUP_DELL_SPK_NOISE] = { + .type = HDA_FIXUP_FUNC, +@@ -5143,6 +5228,10 @@ static const struct hda_fixup alc269_fixups[] = { + [ALC285_FIXUP_SPEAKER2_TO_DAC1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, ++ }, ++ [ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_fixup_speaker2_to_dac1, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI + }, +@@ -5662,11 +5751,7 @@ static const struct hda_fixup alc269_fixups[] = { + { } + }, + .chained = true, +- .chain_id = ALC294_FIXUP_ASUS_ALLY_SPEAKER +- }, +- [ALC294_FIXUP_ASUS_ALLY_SPEAKER] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = alc285_fixup_speaker2_to_dac1, ++ .chain_id = ALC285_FIXUP_SPEAKER2_TO_DAC1 + }, + [ALC285_FIXUP_THINKPAD_X1_GEN7] = { + .type = HDA_FIXUP_FUNC, +@@ -6209,11 +6294,20 @@ static const struct hda_fixup alc269_fixups[] = { + }, + [ALC287_FIXUP_LENOVO_YOGA_PRO7] = { + .type = HDA_FIXUP_FUNC, +- /* Reuse the DAC routing selected for ThinkPad X1 Gen7 */ +- .v.func = alc285_fixup_thinkpad_x1_gen7, ++ .v.func = alc_fixup_yoga_pro7_audio, + .chained = true, + .chain_id = ALC269_FIXUP_LENOVO_XPAD_ACPI, + }, ++ [ALC287_FIXUP_LENOVO_XPAD_HEADSET_JACK] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_headset_jack, ++ .chained = true, ++ /* Same as ALC285_FIXUP_THINKPAD_HEADSET_JACK, except that the ++ * mute LEDs are driven through ideapad_laptop rather than ++ * thinkpad_acpi. ++ */ ++ .chain_id = ALC287_FIXUP_LENOVO_YOGA_PRO7, ++ }, + [ALC623_FIXUP_LENOVO_THINKSTATION_P340] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_no_shutup, +@@ -6653,10 +6747,6 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC289_FIXUP_DUAL_SPK + }, +- [ALC294_FIXUP_CS35L41_I2C_2] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = cs35l41_fixup_i2c_two, +- }, + [ALC256_FIXUP_ACER_SFG16_MICMUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_acer_sfg16_micmute_led, +@@ -6729,6 +6819,10 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE, + }, ++ [ALC245_FIXUP_MINISFORUM_JACK_DETECT] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc245_fixup_minisforum_jack_detect, ++ }, + [ALC269_FIXUP_VAIO_VJFH52_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { +@@ -6749,6 +6843,8 @@ static const struct hda_fixup alc269_fixups[] = { + [ALC233_FIXUP_STARLABS_STARFIGHTER] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_fixup_starlabs_starfighter, ++ .chained = true, ++ .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST, + }, + [ALC294_FIXUP_BASS_SPEAKER_15] = { + .type = HDA_FIXUP_FUNC, +@@ -6776,11 +6872,7 @@ static const struct hda_fixup alc269_fixups[] = { + { } + }, + .chained = true, +- .chain_id = ALC285_FIXUP_ASUS_GA605K_I2C_SPEAKER2_TO_DAC1 +- }, +- [ALC285_FIXUP_ASUS_GA605K_I2C_SPEAKER2_TO_DAC1] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = alc285_fixup_speaker2_to_dac1, ++ .chain_id = ALC285_FIXUP_SPEAKER2_TO_DAC1 + }, + [ALC269_FIXUP_POSITIVO_P15X_HEADSET_MIC] = { + .type = HDA_FIXUP_FUNC, +@@ -6898,6 +6990,26 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC274_FIXUP_HP_89E9_GPIO, + }, ++ [ALC287_FIXUP_AW88399_I2C_2] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = aw88399_fixup_i2c_two, ++ }, ++ [ALC287_FIXUP_LENOVO_LEGION_AW88399] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc287_fixup_legion_16iax10h_aw88399, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_AW88399_I2C_2, ++ }, ++ [ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_headset_jack, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, ++ }, ++ [ALC285_LENOVO_DAC_RENAME] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_lenovo_dac_rename, ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -6951,8 +7063,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1597, "Acer Nitro 5 AN517-55", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x159e, "Acer Nitro 5 AN515-46", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), ++ SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), +@@ -6961,6 +7075,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1025, 0x182c, "Acer Helios ZPD", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1025, 0x1844, "Acer Helios ZPS", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1025, 0x1909, "Acer Nitro ANV16-42", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1028, 0x0470, "Dell M101z", ALC269_FIXUP_DELL_M101Z), + SND_PCI_QUIRK(0x1028, 0x053c, "Dell Latitude E5430", ALC292_FIXUP_DELL_E7X), + SND_PCI_QUIRK(0x1028, 0x054b, "Dell XPS one 2710", ALC275_FIXUP_DELL_XPS), +@@ -7145,10 +7260,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), + SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), + SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), ++ SND_PCI_QUIRK(0x103c, 0x85f3, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8603, "HP Omen 17-cb0xxx", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x860c, "HP ZBook 17 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x860f, "HP ZBook 15 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x861f, "HP Elite Dragonfly G1", ALC285_FIXUP_HP_GPIO_AMP_INIT), ++ SND_PCI_QUIRK(0x103c, 0x864f, "HP Laptop 15-dy0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x869d, "HP", ALC236_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x86c1, "HP Laptop 15-da3001TU", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x86c7, "HP Envy AiO 32", ALC274_FIXUP_HP_ENVY_GPIO), +@@ -7259,6 +7376,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x89e7, "HP Elite x2 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a05, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a06, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), ++ SND_PCI_QUIRK(0x103c, 0x8a0e, "HP Pavilion Laptop 15-eh2xxx", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a0f, "HP Pavilion 14-ec1xxx", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a1b, "HP 255 15.6 inch G9 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8a1f, "HP Laptop 14s-dr5xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), +@@ -7350,10 +7468,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8bf0, "HP", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c15, "HP Spectre x360 2-in-1 Laptop 14-eu0xxx", ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX), + SND_PCI_QUIRK(0x103c, 0x8c16, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), +- SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre 16", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), + SND_PCI_QUIRK(0x103c, 0x8c21, "HP Pavilion Plus Laptop 14-ey0XXX", ALC245_FIXUP_HP_X360_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8c2d, "HP Victus 15-fa1xxx (MB 8C2D)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c30, "HP Victus 15-fb1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8c3f, "HP Victus 15-fa1xxx (MB 8C3F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c46, "HP EliteBook 830 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c47, "HP EliteBook 840 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c48, "HP EliteBook 860 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), +@@ -7502,6 +7621,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8f42, "HP ZBook 8 G2a 14W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8f57, "HP Trekker G7JC", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8f62, "HP ZBook 8 G2a 16W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), ++ SND_PCI_QUIRK(0x103c, 0x8f7e, "HP Messi", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8f94, "HP ZBook 8 G2a 14", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), + SND_PCI_QUIRK(0x103c, 0x8f95, "HP ZBook 8 G2a 16", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), + SND_PCI_QUIRK(0x1043, 0x1024, "ASUS Zephyrus G14 2025", ALC285_FIXUP_ASUS_GA403U_HEADSET_MIC), +@@ -7509,6 +7629,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1034, "ASUS GU605C", ALC285_FIXUP_ASUS_GU605_SPI_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300), ++ SND_PCI_QUIRK(0x1043, 0x1044, "ASUS GA403UM", ALC285_FIXUP_ASUS_GA403U_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1054, "ASUS G614FH/FM/FP", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x106d, "Asus K53BE", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x106f, "ASUS VivoBook X515UA", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), +@@ -7516,16 +7637,15 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x10a1, "ASUS UX391UA", ALC294_FIXUP_ASUS_SPK), + SND_PCI_QUIRK(0x1043, 0x10a4, "ASUS TP3407SA", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), ++ SND_PCI_QUIRK(0x1043, 0x10c4, "ASUS Vivobook S14 S5406SA", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x10d3, "ASUS K6500ZC", ALC294_FIXUP_ASUS_SPK), + SND_PCI_QUIRK(0x1043, 0x1154, "ASUS TP3607SH", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x1043, 0x115d, "Asus 1015E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x1194, "ASUS UM3406KA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x11c0, "ASUS X556UR", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), +- HDA_CODEC_QUIRK(0x1043, 0x1204, "ASUS Strix G16 G615JMR", ALC287_FIXUP_TXNW2781_I2C_ASUS), +- SND_PCI_QUIRK(0x1043, 0x1204, "ASUS Strix G615JHR_JMR_JPR", ALC287_FIXUP_TAS2781_I2C), +- HDA_CODEC_QUIRK(0x1043, 0x1214, "ASUS ROG Strix G615LP", ALC287_FIXUP_TXNW2781_I2C_ASUS), +- SND_PCI_QUIRK(0x1043, 0x1214, "ASUS Strix G615LH_LM_LP", ALC287_FIXUP_TAS2781_I2C), ++ SND_PCI_QUIRK(0x1043, 0x1204, "ASUS Strix G615JHR_JMR_JPR", ALC287_FIXUP_TXNW2781_I2C_ASUS), ++ SND_PCI_QUIRK(0x1043, 0x1214, "ASUS Strix G615LH_LM_LP", ALC287_FIXUP_TXNW2781_I2C_ASUS), + SND_PCI_QUIRK(0x1043, 0x125e, "ASUS Q524UQK", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1271, "ASUS X430UN", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1290, "ASUS X441SA", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), +@@ -7574,7 +7694,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1863, "ASUS UX6404VI/VV", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1881, "ASUS Zephyrus S/M", ALC294_FIXUP_ASUS_GX502_PINS), + SND_PCI_QUIRK(0x1043, 0x18b1, "Asus MJ401TA", ALC256_FIXUP_ASUS_HEADSET_MIC), +- SND_PCI_QUIRK(0x1043, 0x18d3, "ASUS UM3504DA", ALC294_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1043, 0x18d3, "ASUS UM3504DA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x18f1, "Asus FX505DT", ALC256_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x194e, "ASUS UX563FD", ALC294_FIXUP_ASUS_HPE), + SND_PCI_QUIRK(0x1043, 0x1970, "ASUS UX550VE", ALC289_FIXUP_ASUS_GA401), +@@ -7585,7 +7705,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x19f4, "ASUS UM3405GA", ALC294_FIXUP_ASUS_I2C_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1a13, "Asus G73Jw", ALC269_FIXUP_ASUS_G73JW), + SND_PCI_QUIRK(0x1043, 0x1a63, "ASUS UX3405MA", ALC294_FIXUP_ASUS_SPI_HEADSET_MIC), +- SND_PCI_QUIRK(0x1043, 0x1a83, "ASUS UM5302LA", ALC294_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1043, 0x1a83, "ASUS UM5302LA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1a8e, "ASUS G712LWS", ALC294_FIXUP_LENOVO_MIC_LOCATION), + SND_PCI_QUIRK(0x1043, 0x1a8f, "ASUS UX582ZS", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1b11, "ASUS UX431DA", ALC294_FIXUP_ASUS_COEF_1B), +@@ -7614,6 +7734,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1264, "ASUS UM5606KA", ALC294_FIXUP_BASS_SPEAKER_15), + SND_PCI_QUIRK(0x1043, 0x1e02, "ASUS UX3402ZA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1e10, "ASUS VivoBook X507UAR", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), ++ SND_PCI_QUIRK(0x1043, 0x1e3e, "ASUS VivoBook M515DA/X515DAP", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1e11, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA502), + SND_PCI_QUIRK(0x1043, 0x1e12, "ASUS UM3402", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1e1f, "ASUS Vivobook 15 X1504VAP", ALC2XX_FIXUP_HEADSET_MIC), +@@ -7625,8 +7746,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1e93, "ASUS ExpertBook B9403CVAR", ALC294_FIXUP_ASUS_HPE), + SND_PCI_QUIRK(0x1043, 0x1eb3, "ASUS Ally RC72LA", ALC287_FIXUP_ASUS_ALLY_X), + SND_PCI_QUIRK(0x1043, 0x1ed3, "ASUS HN7306W", ALC287_FIXUP_CS35L41_I2C_2), +- HDA_CODEC_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1), +- SND_PCI_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x1043, 0x1c52, "ASUS Zephyrus G15 2022", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1f11, "ASUS Zephyrus G14", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1f12, "ASUS UM5302", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7837,7 +7957,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x2211, "Thinkpad W541", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2212, "Thinkpad T440", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2214, "Thinkpad X240", ALC292_FIXUP_TPT440_DOCK), +- SND_PCI_QUIRK(0x17aa, 0x2215, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x2215, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x2218, "Thinkpad X1 Carbon 2nd", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2223, "ThinkPad T550", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2226, "ThinkPad X250", ALC292_FIXUP_TPT440_DOCK), +@@ -7853,7 +7973,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x224b, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x224c, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x224d, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), +- SND_PCI_QUIRK(0x17aa, 0x225d, "Thinkpad T480", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x225c, "Lenovo ThinkPad X1 Carbon 6th Gen", ALC285_LENOVO_DAC_RENAME), ++ SND_PCI_QUIRK(0x17aa, 0x225d, "Thinkpad T480", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x2288, "Thinkpad X390", ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK), + SND_PCI_QUIRK(0x17aa, 0x2292, "Thinkpad X1 Carbon 7th", ALC285_FIXUP_THINKPAD_HEADSET_JACK), + SND_PCI_QUIRK(0x17aa, 0x22be, "Thinkpad X1 Carbon 8th", ALC285_FIXUP_THINKPAD_HEADSET_JACK), +@@ -7928,7 +8049,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), +- SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_SPEAKER2_TO_DAC1), ++ SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), + HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7963,6 +8084,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38b8, "Yoga S780-14.5 proX AMD YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38b9, "Yoga S780-14.5 proX AMD LX Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38ba, "Yoga S780-14.5 Air AMD quad YC", ALC287_FIXUP_TAS2781_I2C), ++ HDA_CODEC_QUIRK(0x17aa, 0x3926, "Legion Pro 5 16ADR10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), ++ /* Legion R9000P ADR10 shares PCI SSID 17aa:38bb with Yoga S780-14.5 Air AMD quad AAC; ++ * use codec SSID to distinguish them ++ */ ++ HDA_CODEC_QUIRK(0x17aa, 0x3927, "Legion R9000P ADR10", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3928, "Legion R9000P ADR10", ALC287_FIXUP_LENOVO_LEGION_AW88399), + SND_PCI_QUIRK(0x17aa, 0x38bb, "Yoga S780-14.5 Air AMD quad AAC", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38be, "Yoga S980-14.5 proX YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38bf, "Yoga S980-14.5 proX LX Dual", ALC287_FIXUP_TAS2781_I2C), +@@ -7980,7 +8107,6 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38df, "Yoga Y990 Intel YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38e0, "Yoga Y990 Intel VECO Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38f8, "Yoga Book 9i", ALC287_FIXUP_TAS2781_I2C), +- SND_PCI_QUIRK(0x17aa, 0x38df, "Y990 YG DUAL", ALC287_FIXUP_TAS2781_I2C), + /* Legion 7 15ASH11 shares PCI SSID 17aa:38f9 with Thinkbook 16P Gen5; + * use codec SSID to distinguish them + */ +@@ -7990,7 +8116,9 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38fc, "Lenovo Yoga Pro 7 15ASH11", ALC287_FIXUP_LENOVO_YOGA_PRO7), + SND_PCI_QUIRK(0x17aa, 0x38fd, "ThinkBook plus Gen5 Hybrid", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x3902, "Lenovo E50-80", ALC269_FIXUP_DMIC_THINKPAD_ACPI), +- SND_PCI_QUIRK(0x17aa, 0x390d, "Lenovo Yoga Pro 7 14ASP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), ++ HDA_CODEC_QUIRK(0x17aa, 0x3906, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3907, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ SND_PCI_QUIRK(0x17aa, 0x390d, "Lenovo Yoga Pro 7 14ASP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET), + SND_PCI_QUIRK(0x17aa, 0x3911, "Lenovo Yoga Pro 7 14IAH10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), +@@ -7998,14 +8126,19 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x392b, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), ++ HDA_CODEC_QUIRK(0x17aa, 0x3936, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3937, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3938, "Legion Pro 7 16AFR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3939, "Legion Pro 7 16AFR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ SND_PCI_QUIRK(0x17aa, 0x393e, "Lenovo ThinkBook 14 G8+ IPH", ALC287_FIXUP_LENOVO_XPAD_HEADSET_JACK), + HDA_CODEC_QUIRK(0x17aa, 0x394c, "Lenovo Yoga Slim 7 14AGP11", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3977, "IdeaPad S210", ALC283_FIXUP_INT_MIC), + SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo B50-70", ALC269_FIXUP_DMIC_THINKPAD_ACPI), + SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K), +- SND_PCI_QUIRK(0x17aa, 0x5013, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x5013, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x501a, "Thinkpad", ALC283_FIXUP_INT_MIC), + SND_PCI_QUIRK(0x17aa, 0x501e, "Thinkpad L440", ALC292_FIXUP_TPT440_DOCK), +- SND_PCI_QUIRK(0x17aa, 0x5026, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x5026, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x5034, "Thinkpad T450", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x5036, "Thinkpad T450s", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x503c, "Thinkpad L450", ALC292_FIXUP_TPT440_DOCK), +@@ -8018,7 +8151,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x505f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x5062, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x508b, "Thinkpad X12 Gen 1", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), +- SND_PCI_QUIRK(0x17aa, 0x5109, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x5109, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x511e, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), +@@ -8032,9 +8165,14 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1854, 0x0489, "LG gram 16 (16Z90R-A)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), + SND_PCI_QUIRK(0x1854, 0x048a, "LG gram 17 (17ZD90R)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), + SND_PCI_QUIRK(0x1854, 0x0490, "LG Gram Style 14 (14Z90RS)", ALC298_FIXUP_LG_GRAM_STYLE_14), ++ SND_PCI_QUIRK(0x1854, 0x0554, "LG gram 16 (16Z90TR)", ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS), + SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS), + SND_PCI_QUIRK(0x19e5, 0x320f, "Huawei WRT-WX9 ", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x19e5, 0x3212, "Huawei KLV-WX9 ", ALC256_FIXUP_ACER_HEADSET_MIC), ++ SND_PCI_QUIRK(0x1a58, 0x2023, "Razer Blade 16 (2025)", ++ ALC298_FIXUP_RAZER_BLADE16_2025_PINS), ++ SND_PCI_QUIRK(0x1a58, 0x300e, "Razer Blade 16 (2025)", ++ ALC298_FIXUP_RAZER_BLADE16_2025_PINS), + SND_PCI_QUIRK(0x1b35, 0x1235, "CZC B20", ALC269_FIXUP_CZC_B20), + SND_PCI_QUIRK(0x1b35, 0x1236, "CZC TMI", ALC269_FIXUP_CZC_TMI), + SND_PCI_QUIRK(0x1b35, 0x1237, "CZC L101", ALC269_FIXUP_CZC_L101), +@@ -8069,13 +8207,17 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1d72, 0x1947, "RedmiBook Air", ALC255_FIXUP_XIAOMI_HEADSET_MIC), + SND_PCI_QUIRK(0x1e39, 0xca14, "MEDION NM14LNL", ALC233_FIXUP_MEDION_MTL_SPK), + SND_PCI_QUIRK(0x1e50, 0x7007, "Positivo DN50E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x1e50, 0x7036, "Acer Gadget E10 ETBook", ALC233_FIXUP_WUJIE_SPEAKERS), + SND_PCI_QUIRK(0x1e50, 0x7038, "Positivo DN140", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1ee7, 0x2078, "HONOR BRB-X M1010", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1ee7, 0x2081, "HONOR MRB-XXX M1020", ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO), ++ SND_PCI_QUIRK(0x1f4c, 0xb020, "Minisforum AI X1 Pro", ++ ALC245_FIXUP_MINISFORUM_JACK_DETECT), + SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", ALC245_FIXUP_BASS_HP_DAC), + SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), ++ SND_PCI_QUIRK(0x2145, 0x0001, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), + SND_PCI_QUIRK(0x2782, 0x0214, "VAIO VJFE-CL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0x0228, "Infinix ZERO BOOK 13", ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13), + SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO), +@@ -8086,6 +8228,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x2782, 0x1705, "MEDION E15433", ALC269VC_FIXUP_INFINIX_Y4_MAX), + SND_PCI_QUIRK(0x2782, 0x1707, "Vaio VJFE-ADL", ALC298_FIXUP_SPK_VOLUME), + SND_PCI_QUIRK(0x2782, 0x4900, "MEDION E15443", ALC233_FIXUP_MEDION_MTL_SPK), ++ SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), + SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), + SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC), +@@ -8159,6 +8302,7 @@ static const struct hda_quirk alc269_fixup_vendor_tbl[] = { + SND_PCI_QUIRK_VENDOR(0x104d, "Sony VAIO", ALC269_FIXUP_SONY_VAIO), + SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo XPAD", ALC269_FIXUP_LENOVO_XPAD_ACPI), + SND_PCI_QUIRK_VENDOR(0x19e5, "Huawei Matebook", ALC255_FIXUP_MIC_MUTE_LED), ++ SND_PCI_QUIRK_VENDOR(0x2145, "Star Labs", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + {} + }; + +@@ -8298,6 +8442,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = { + {.id = ALC2XX_FIXUP_HEADSET_MIC, .name = "alc2xx-fixup-headset-mic"}, + {.id = ALC245_FIXUP_BASS_HP_DAC, .name = "alc245-fixup-bass-hp-dac"}, + {.id = ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, .name = "alc256-honor-mrb-xxx-m1020-audio"}, ++ {.id = ALC287_FIXUP_LENOVO_LEGION_AW88399, .name = "alc287-lenovo-legion-aw88399"}, + {} + }; + #define ALC225_STANDARD_PINS \ +@@ -8903,6 +9048,7 @@ static int alc269_probe(struct hda_codec *codec, const struct hda_device_id *id) + spec->init_hook = alc256_init; + spec->gen.mixer_nid = 0; /* ALC256 does not have any loopback mixer path */ + if (codec->core.vendor_id == 0x10ec0236 && ++ codec->bus->pci && + codec->bus->pci->vendor != PCI_VENDOR_ID_AMD) + spec->en_3kpull_low = false; + break; +diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c +--- a/sound/hda/codecs/realtek/alc662.c ++++ b/sound/hda/codecs/realtek/alc662.c +@@ -325,6 +325,7 @@ enum { + ALC897_FIXUP_UNIS_H3C_X500S, + ALC897_FIXUP_HEADSET_MIC_PIN3, + ALC662_FIXUP_CSL_GPIO, ++ ALC897_FIXUP_BEELINK_SER6_AMP, + }; + + static const struct hda_fixup alc662_fixups[] = { +@@ -782,6 +783,10 @@ static const struct hda_fixup alc662_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc662_fixup_csl_amp, + }, ++ [ALC897_FIXUP_BEELINK_SER6_AMP] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_gpio4, ++ }, + }; + + static const struct hda_quirk alc662_fixup_tbl[] = { +@@ -853,6 +858,7 @@ static const struct hda_quirk alc662_fixup_tbl[] = { + SND_PCI_QUIRK(0x1b35, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T), + SND_PCI_QUIRK(0x1c6c, 0x1239, "Compaq N14JP6-V2", ALC897_FIXUP_HP_HSMIC_VERB), + SND_PCI_QUIRK(0x1e63, 0x6d9a, "F+ FLAPTOP r", ALC897_FIXUP_HP_HSMIC_VERB), ++ SND_PCI_QUIRK(0x1f66, 0x0202, "Beelink SER6 Max 6900", ALC897_FIXUP_BEELINK_SER6_AMP), + + #if 0 + /* Below is a quirk table taken from the old code. +diff --git a/sound/hda/codecs/side-codecs/Kconfig b/sound/hda/codecs/side-codecs/Kconfig +--- a/sound/hda/codecs/side-codecs/Kconfig ++++ b/sound/hda/codecs/side-codecs/Kconfig +@@ -13,6 +13,24 @@ config SND_HDA_CIRRUS_SCODEC_KUNIT_TEST + Documentation/dev-tools/kunit/. + If in doubt, say "N". + ++config SND_HDA_SCODEC_AW88399 ++ tristate ++ select SND_HDA_GENERIC ++ ++config SND_HDA_SCODEC_AW88399_I2C ++ tristate "Build AW88399 HD-audio side codec support for I2C Bus" ++ depends on I2C ++ depends on ACPI ++ depends on SND_SOC ++ select SND_HDA_SCODEC_AW88399 ++ select SND_SOC_AW88399_LIB ++ help ++ Say Y or M here to include AW88399 I2C HD-audio side codec support ++ in snd-hda-intel driver, such as ALC287. ++ ++comment "Set to Y if you want auto-loading the side codec driver" ++ depends on SND_HDA=y && SND_HDA_SCODEC_AW88399_I2C=m ++ + config SND_HDA_SCODEC_CS35L41 + tristate + select SND_HDA_GENERIC +diff --git a/sound/hda/codecs/side-codecs/Makefile b/sound/hda/codecs/side-codecs/Makefile +--- a/sound/hda/codecs/side-codecs/Makefile ++++ b/sound/hda/codecs/side-codecs/Makefile +@@ -3,6 +3,8 @@ subdir-ccflags-y += -I$(src)/../../common + + snd-hda-cirrus-scodec-y := cirrus_scodec.o + snd-hda-cirrus-scodec-test-y := cirrus_scodec_test.o ++snd-hda-scodec-aw88399-y := aw88399_hda.o ++snd-hda-scodec-aw88399-i2c-y := aw88399_hda_i2c.o + snd-hda-scodec-cs35l41-y := cs35l41_hda.o cs35l41_hda_property.o + snd-hda-scodec-cs35l41-i2c-y := cs35l41_hda_i2c.o + snd-hda-scodec-cs35l41-spi-y := cs35l41_hda_spi.o +@@ -16,6 +18,8 @@ snd-hda-scodec-tas2781-spi-y := tas2781_hda_spi.o + + obj-$(CONFIG_SND_HDA_CIRRUS_SCODEC) += snd-hda-cirrus-scodec.o + obj-$(CONFIG_SND_HDA_CIRRUS_SCODEC_KUNIT_TEST) += snd-hda-cirrus-scodec-test.o ++obj-$(CONFIG_SND_HDA_SCODEC_AW88399) += snd-hda-scodec-aw88399.o ++obj-$(CONFIG_SND_HDA_SCODEC_AW88399_I2C) += snd-hda-scodec-aw88399-i2c.o + obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) += snd-hda-scodec-cs35l41.o + obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) += snd-hda-scodec-cs35l41-i2c.o + obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) += snd-hda-scodec-cs35l41-spi.o +diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/side-codecs/aw88399_hda.c +@@ -0,0 +1,373 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// AW88399 HDA side codec driver ++// ++// Based on cs35l41_hda.c and aw88399.c ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../generic.h" ++#include "hda_component.h" ++#include "aw88399_hda.h" ++ ++#define AW88399_HDA_I2C_BASE_ADDR 0x34 ++ ++static void aw88399_hda_playback_hook(struct device *dev, int action) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ struct aw88399 *core = aw88399->core; ++ int ret = 0; ++ ++ dev_dbg(aw88399->dev, "Playback action: %d\n", action); ++ ++ switch (action) { ++ case HDA_GEN_PCM_ACT_OPEN: ++ pm_runtime_get_sync(dev); ++ aw88399->playing = true; ++ break; ++ case HDA_GEN_PCM_ACT_PREPARE: ++ if (core) ++ aw88399_start(core, AW88399_SYNC_START); ++ break; ++ case HDA_GEN_PCM_ACT_CLEANUP: ++ if (aw88399->aw_dev) ++ ret = aw88399_stop(aw88399->aw_dev); ++ if (ret) ++ dev_err(aw88399->dev, "Failed to stop amplifier: %d\n", ret); ++ break; ++ case HDA_GEN_PCM_ACT_CLOSE: ++ if (aw88399->aw_dev) ++ aw88399_stop(aw88399->aw_dev); ++ aw88399->playing = false; ++ pm_runtime_mark_last_busy(dev); ++ pm_runtime_put_autosuspend(dev); ++ break; ++ default: ++ dev_warn(aw88399->dev, "Unsupported action: %d\n", action); ++ break; ++ } ++} ++ ++static int aw88399_hda_bind(struct device *dev, struct device *master, void *master_data) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ struct hda_component_parent *parent = master_data; ++ struct hda_component *comp; ++ ++ comp = hda_component_from_index(parent, aw88399->index); ++ if (!comp) ++ return -EINVAL; ++ ++ if (comp->dev) ++ return -EBUSY; ++ ++ comp->dev = dev; ++ ++ strscpy(comp->name, dev_name(dev), sizeof(comp->name)); ++ ++ comp->playback_hook = aw88399_hda_playback_hook; ++ ++ dev_info(aw88399->dev, ++ "AW88399 Bound - SSID: %s, channel: %d\n", ++ aw88399->acpi_subsystem_id, aw88399->channel); ++ ++ return 0; ++} ++ ++static void aw88399_hda_unbind(struct device *dev, struct device *master, void *master_data) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ struct hda_component_parent *parent = master_data; ++ struct hda_component *comp; ++ ++ comp = hda_component_from_index(parent, aw88399->index); ++ if (comp && (comp->dev == dev)) ++ memset(comp, 0, sizeof(*comp)); ++ ++ dev_dbg(aw88399->dev, "Unbound from HDA codec\n"); ++} ++ ++static const struct component_ops aw88399_hda_comp_ops = { ++ .bind = aw88399_hda_bind, ++ .unbind = aw88399_hda_unbind, ++}; ++ ++static int aw88399_hda_index_from_i2c(struct aw88399_hda *aw88399) ++{ ++ return to_i2c_client(aw88399->dev)->addr - AW88399_HDA_I2C_BASE_ADDR; ++} ++ ++static int aw88399_hda_init(struct aw88399_hda *aw88399) ++{ ++ struct device *dev = aw88399->dev; ++ struct i2c_client *i2c = to_i2c_client(dev); ++ struct aw88399 *core; ++ int ret; ++ ++ core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); ++ if (!core) ++ return -ENOMEM; ++ ++ mutex_init(&core->lock); ++ core->reset_gpio = aw88399->reset_gpio; ++ core->regmap = aw88399->regmap; ++ core->bsts_unreliable = aw88399->bsts_unreliable; ++ ++ aw88399_hw_reset(core); ++ ++ ret = aw88399_init(core, i2c, aw88399->regmap); ++ if (ret) ++ return ret; ++ ++ /* Set channel BEFORE loading firmware so ACF parser sees correct value */ ++ if (core->aw_pa) ++ aw88399_dev_set_channel(core, aw88399->channel); ++ ++ ret = aw88399_request_firmware_file(core); ++ if (ret) ++ return ret; ++ ++ aw88399->core = core; ++ aw88399->aw_dev = core->aw_pa; ++ ++ return 0; ++} ++ ++static int aw88399_swap_channels(struct aw88399_hda *aw88399) ++{ ++ /* ++ * Certain Lenovo Legion laptops have their ++ * I2C wiring reversed: 0x34 is physically the right speaker, ++ * 0x35 is the left. Swap channels to correct L/R assignment. ++ * This is a model-specific hardware wiring issue, not a driver bug. ++ */ ++ aw88399->channel = 1 - aw88399->channel; ++ dev_dbg(aw88399->dev, ++ "Channel swap applied: index %d -> channel %d\n", ++ aw88399->index, aw88399->channel); ++ return 0; ++} ++ ++static int aw88399_skip_bsts_check(struct aw88399_hda *aw88399) ++{ ++ /* ++ * BSTS (boost-finished) status bit does not reliably report on ++ * some hardware. On certain Lenovo Legion laptops, both amps ++ * report BSTS=0 (boost not finished) during normal playback ++ * despite clean audio output. Skip BSTS in the startup status ++ * check to avoid false init failures. ++ */ ++ aw88399->bsts_unreliable = true; ++ dev_dbg(aw88399->dev, "BSTS status check disabled\n"); ++ return 0; ++} ++ ++static int aw88399_apply_legion_quirks(struct aw88399_hda *aw88399) ++{ ++ aw88399_swap_channels(aw88399); ++ aw88399_skip_bsts_check(aw88399); ++ return 0; ++} ++ ++struct aw88399_prop_model { ++ const char *ssid; ++ int (*apply_prop)(struct aw88399_hda *aw88399); ++}; ++ ++static const struct aw88399_prop_model aw88399_prop_model_table[] = { ++ { "17AA3906", aw88399_apply_legion_quirks }, ++ { "17AA3907", aw88399_apply_legion_quirks }, ++ { "17AA3927", aw88399_apply_legion_quirks }, ++ { "17AA3928", aw88399_apply_legion_quirks }, ++ { "17AA3936", aw88399_apply_legion_quirks }, ++ { "17AA3937", aw88399_apply_legion_quirks }, ++ { "17AA3938", aw88399_apply_legion_quirks }, ++ { "17AA3939", aw88399_apply_legion_quirks }, ++ { } ++}; ++ ++static int aw88399_hda_acpi_probe(struct aw88399_hda *aw88399) ++{ ++ struct acpi_device *adev; ++ const char *sub; ++ const struct aw88399_prop_model *model; ++ ++ aw88399->index = aw88399_hda_index_from_i2c(aw88399); ++ aw88399->channel = aw88399->index; ++ aw88399->acpi_subsystem_id = NULL; ++ ++ adev = acpi_dev_get_first_match_dev("AWDZ8399", NULL, -1); ++ if (!adev) { ++ dev_err(aw88399->dev, "Failed to find an ACPI device for AWDZ8399\n"); ++ return -ENODEV; ++ } ++ ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); ++ acpi_dev_put(adev); ++ if (!physdev) ++ return -ENODEV; ++ ++ sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); ++ if (IS_ERR_OR_NULL(sub)) ++ return 0; ++ ++ aw88399->acpi_subsystem_id = devm_kstrdup(aw88399->dev, sub, GFP_KERNEL); ++ kfree(sub); ++ if (!aw88399->acpi_subsystem_id) ++ return -ENOMEM; ++ ++ for (model = aw88399_prop_model_table; model->ssid; model++) { ++ if (!strcasecmp(model->ssid, aw88399->acpi_subsystem_id)) { ++ dev_info(aw88399->dev, ++ "Applying properties for SSID %s\n", ++ aw88399->acpi_subsystem_id); ++ return model->apply_prop(aw88399); ++ } ++ } ++ ++ return 0; ++} ++ ++int aw88399_hda_probe(struct device *dev, struct regmap *regmap) ++{ ++ struct aw88399_hda *aw88399; ++ int ret; ++ ++ aw88399 = devm_kzalloc(dev, sizeof(*aw88399), GFP_KERNEL); ++ if (!aw88399) ++ return -ENOMEM; ++ ++ if (IS_ERR(regmap)) ++ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to obtain regmap\n"); ++ ++ aw88399->dev = dev; ++ aw88399->regmap = regmap; ++ dev_set_drvdata(dev, aw88399); ++ ++ aw88399->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); ++ if (IS_ERR(aw88399->reset_gpio)) ++ return dev_err_probe(dev, PTR_ERR(aw88399->reset_gpio), ++ "Failed to get reset GPIO\n"); ++ ++ ret = aw88399_hda_acpi_probe(aw88399); ++ if (ret) ++ return dev_err_probe(dev, ret, "ACPI probe failed\n"); ++ ++ ret = aw88399_hda_init(aw88399); ++ if (ret) ++ return dev_err_probe(dev, ret, "Chip initialization failed\n"); ++ ++ /* Enable runtime PM */ ++ pm_runtime_set_autosuspend_delay(dev, 3000); ++ pm_runtime_use_autosuspend(dev); ++ pm_runtime_mark_last_busy(dev); ++ pm_runtime_set_active(dev); ++ pm_runtime_enable(dev); ++ ++ ret = component_add(dev, &aw88399_hda_comp_ops); ++ if (ret) { ++ pm_runtime_disable(dev); ++ return dev_err_probe(dev, ret, "Failed to register component\n"); ++ } ++ ++ dev_info(dev, "AW88399 HDA side codec registered successfully\n"); ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(aw88399_hda_probe, "SND_HDA_SCODEC_AW88399"); ++ ++void aw88399_hda_remove(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ ++ pm_runtime_disable(dev); ++ ++ if (aw88399->aw_dev) ++ aw88399_stop(aw88399->aw_dev); ++ ++ component_del(dev, &aw88399_hda_comp_ops); ++ ++ dev_dbg(aw88399->dev, "AW88399 HDA side codec removed\n"); ++} ++EXPORT_SYMBOL_NS_GPL(aw88399_hda_remove, "SND_HDA_SCODEC_AW88399"); ++ ++static int aw88399_hda_runtime_suspend(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ ++ dev_dbg(aw88399->dev, "Runtime suspend\n"); ++ ++ if (aw88399->aw_dev && aw88399->playing) ++ aw88399_stop(aw88399->aw_dev); ++ ++ return 0; ++} ++ ++static int aw88399_hda_runtime_resume(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ ++ dev_dbg(aw88399->dev, "Runtime resume\n"); ++ ++ if (aw88399->core && aw88399->aw_dev && aw88399->playing) ++ aw88399_start(aw88399->core, AW88399_SYNC_START); ++ ++ return 0; ++} ++ ++static int aw88399_hda_system_suspend(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ int ret; ++ ++ dev_dbg(aw88399->dev, "System suspend\n"); ++ ++ if (aw88399->aw_dev && aw88399->playing) ++ aw88399_stop(aw88399->aw_dev); ++ ++ if (aw88399->core) ++ aw88399->core->fw_needs_reload = true; ++ ++ ret = pm_runtime_force_suspend(dev); ++ if (ret) ++ dev_err(aw88399->dev, "Runtime force suspend failed: %d\n", ret); ++ ++ return ret; ++} ++ ++static int aw88399_hda_system_resume(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ int ret; ++ ++ dev_dbg(aw88399->dev, "System resume\n"); ++ ++ if (aw88399->aw_dev) ++ aw88399_hw_reset(aw88399->core); ++ ++ ret = pm_runtime_force_resume(dev); ++ if (ret) ++ dev_err(aw88399->dev, "Runtime force resume failed: %d\n", ret); ++ ++ return ret; ++} ++ ++const struct dev_pm_ops aw88399_hda_pm_ops = { ++ RUNTIME_PM_OPS(aw88399_hda_runtime_suspend, aw88399_hda_runtime_resume, NULL) ++ SYSTEM_SLEEP_PM_OPS(aw88399_hda_system_suspend, aw88399_hda_system_resume) ++}; ++EXPORT_SYMBOL_NS_GPL(aw88399_hda_pm_ops, "SND_HDA_SCODEC_AW88399"); ++ ++MODULE_DESCRIPTION("AW88399 HDA driver"); ++MODULE_AUTHOR("Yakov Till "); ++MODULE_AUTHOR("Marco Giunta "); ++MODULE_LICENSE("GPL"); ++MODULE_FIRMWARE("aw88399_acf.bin"); +diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.h b/sound/hda/codecs/side-codecs/aw88399_hda.h +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/side-codecs/aw88399_hda.h +@@ -0,0 +1,36 @@ ++/* SPDX-License-Identifier: GPL-2.0-only ++ * ++ * AW88399 HDA side codec driver ++ */ ++ ++#ifndef __AW88399_HDA_H__ ++#define __AW88399_HDA_H__ ++ ++#include ++#include ++#include ++ ++struct aw88399; ++struct aw_device; ++ ++struct aw88399_hda { ++ struct device *dev; ++ struct regmap *regmap; ++ struct gpio_desc *reset_gpio; ++ struct aw_device *aw_dev; ++ struct aw88399 *core; ++ bool bsts_unreliable; ++ ++ const char *acpi_subsystem_id; ++ int index; ++ int channel; ++ ++ bool playing; ++}; ++ ++int aw88399_hda_probe(struct device *dev, struct regmap *regmap); ++void aw88399_hda_remove(struct device *dev); ++ ++extern const struct dev_pm_ops aw88399_hda_pm_ops; ++ ++#endif /* __AW88399_HDA_H__ */ +diff --git a/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c +@@ -0,0 +1,54 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// AW88399 HDA I2C driver ++// ++// Based on cs35l41_hda_i2c.c ++// ++ ++#include ++#include ++#include ++ ++#include "aw88399_hda.h" ++ ++static int aw88399_hda_i2c_probe(struct i2c_client *clt) ++{ ++ if (!strstr(dev_name(&clt->dev), "AWDZ8399")) ++ return -ENODEV; ++ ++ return aw88399_hda_probe(&clt->dev, ++ devm_regmap_init_i2c(clt, &aw88399_remap_config)); ++} ++ ++static void aw88399_hda_i2c_remove(struct i2c_client *clt) ++{ ++ aw88399_hda_remove(&clt->dev); ++} ++ ++static const struct i2c_device_id aw88399_hda_i2c_id[] = { ++ { .name = "aw88399-hda" }, ++ { } ++}; ++ ++static const struct acpi_device_id aw88399_acpi_hda_match[] = { ++ { "AWDZ8399", 0 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, aw88399_acpi_hda_match); ++ ++static struct i2c_driver aw88399_hda_i2c_driver = { ++ .driver = { ++ .name = "aw88399-hda", ++ .acpi_match_table = aw88399_acpi_hda_match, ++ .pm = &aw88399_hda_pm_ops, ++ }, ++ .probe = aw88399_hda_i2c_probe, ++ .remove = aw88399_hda_i2c_remove, ++ .id_table = aw88399_hda_i2c_id, ++}; ++module_i2c_driver(aw88399_hda_i2c_driver); ++ ++MODULE_DESCRIPTION("HDA AW88399 I2C driver"); ++MODULE_IMPORT_NS("SND_HDA_SCODEC_AW88399"); ++MODULE_AUTHOR("Yakov Till "); ++MODULE_LICENSE("GPL"); +diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda.c b/sound/hda/codecs/side-codecs/cs35l41_hda.c +--- a/sound/hda/codecs/side-codecs/cs35l41_hda.c ++++ b/sound/hda/codecs/side-codecs/cs35l41_hda.c +@@ -170,6 +170,7 @@ static int cs35l41_request_firmware_file(struct cs35l41_hda *cs35l41, + char *s, c; + int ret = 0; + ++ *firmware = NULL; + if (spkid > -1 && ssid && amp_name) + *filename = kasprintf(GFP_KERNEL, "cirrus/%s-%s-%s-%s-spkid%d-%s.%s", CS35L41_PART, + dsp_name, cs35l41_hda_fw_ids[cs35l41->firmware_type], +@@ -528,8 +529,8 @@ static int cs35l41_read_tuning_params(struct cs35l41_hda *cs35l41, const struct + + static int cs35l41_load_tuning_params(struct cs35l41_hda *cs35l41, char *tuning_filename) + { +- const struct firmware *tuning_param_file = NULL; +- char *tuning_param_filename = NULL; ++ const struct firmware *tuning_param_file __free(firmware) = NULL; ++ char *tuning_param_filename __free(kfree) = NULL; + int ret; + + ret = cs35l41_request_tuning_param_file(cs35l41, tuning_filename, &tuning_param_file, +@@ -548,19 +549,16 @@ static int cs35l41_load_tuning_params(struct cs35l41_hda *cs35l41, char *tuning_ + cs35l41_set_default_tuning_params(cs35l41); + } + +- release_firmware(tuning_param_file); +- kfree(tuning_param_filename); +- + return ret; + } + + static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41) + { +- const struct firmware *coeff_firmware = NULL; +- const struct firmware *wmfw_firmware = NULL; ++ const struct firmware *coeff_firmware __free(firmware) = NULL; ++ const struct firmware *wmfw_firmware __free(firmware) = NULL; + struct cs_dsp *dsp = &cs35l41->cs_dsp; +- char *coeff_filename = NULL; +- char *wmfw_filename = NULL; ++ char *coeff_filename __free(kfree) = NULL; ++ char *wmfw_filename __free(kfree) = NULL; + int ret; + + if (!cs35l41->halo_initialized) { +@@ -592,20 +590,13 @@ static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41) + + ret = cs_dsp_power_up(dsp, wmfw_firmware, wmfw_filename, coeff_firmware, coeff_filename, + cs35l41_hda_fw_ids[cs35l41->firmware_type]); +- if (ret) +- goto err; ++ if (ret) { ++ cs35l41_set_default_tuning_params(cs35l41); ++ return ret; ++ } + + cs35l41_hda_apply_calibration(cs35l41); +- +-err: +- if (ret) +- cs35l41_set_default_tuning_params(cs35l41); +- release_firmware(wmfw_firmware); +- release_firmware(coeff_firmware); +- kfree(wmfw_filename); +- kfree(coeff_filename); +- +- return ret; ++ return 0; + } + + static void cs35l41_shutdown_dsp(struct cs35l41_hda *cs35l41) +@@ -1924,7 +1915,6 @@ int cs35l41_hda_parse_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, + static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, int id) + { + struct acpi_device *adev; +- struct device *physdev; + struct spi_device *spi; + const char *sub; + int ret; +@@ -1936,7 +1926,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i + } + + cs35l41->dacpi = adev; +- physdev = get_device(acpi_get_first_physical_node(adev)); ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); + if (!physdev) { + acpi_dev_put(adev); + return -ENODEV; +@@ -1954,13 +1945,9 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i + } + + ret = cs35l41_hda_parse_acpi(cs35l41, physdev, id); +- if (ret) { +- put_device(physdev); ++ if (ret) + return ret; +- } + out: +- put_device(physdev); +- + cs35l41->bypass_fw = false; + if (cs35l41->control_bus == SPI) { + spi = to_spi_device(cs35l41->dev); +diff --git a/sound/hda/codecs/side-codecs/cs35l56_hda.c b/sound/hda/codecs/side-codecs/cs35l56_hda.c +--- a/sound/hda/codecs/side-codecs/cs35l56_hda.c ++++ b/sound/hda/codecs/side-codecs/cs35l56_hda.c +@@ -527,18 +527,6 @@ static void cs35l56_hda_request_firmware_files(struct cs35l56_hda *cs35l56, + base_name, NULL, NULL, "bin"); + } + +-static void cs35l56_hda_release_firmware_files(const struct firmware *wmfw_firmware, +- char *wmfw_filename, +- const struct firmware *coeff_firmware, +- char *coeff_filename) +-{ +- release_firmware(wmfw_firmware); +- kfree(wmfw_filename); +- +- release_firmware(coeff_firmware); +- kfree(coeff_filename); +-} +- + static int cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) + { + int ret; +@@ -561,10 +549,10 @@ static int cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) + + static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + { +- const struct firmware *coeff_firmware = NULL; +- const struct firmware *wmfw_firmware = NULL; +- char *coeff_filename = NULL; +- char *wmfw_filename = NULL; ++ const struct firmware *coeff_firmware __free(firmware) = NULL; ++ const struct firmware *wmfw_firmware __free(firmware) = NULL; ++ char *coeff_filename __free(kfree) = NULL; ++ char *wmfw_filename __free(kfree) = NULL; + unsigned int preloaded_fw_ver; + bool firmware_missing; + int ret; +@@ -606,14 +594,14 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + if (firmware_missing) { + if (!wmfw_firmware) { + dev_err(cs35l56->base.dev, ".%s file required but not found\n", "wmfw"); +- goto err_fw_release; ++ return; + } else if (!coeff_firmware) { + dev_err(cs35l56->base.dev, ".%s file required but not found\n", "bin"); +- goto err_fw_release; ++ return; + } + } + +- mutex_lock(&cs35l56->base.irq_lock); ++ guard(mutex)(&cs35l56->base.irq_lock); + + /* + * If the firmware hasn't been patched it must be shutdown before +@@ -624,14 +612,14 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + if (firmware_missing && (wmfw_firmware || coeff_firmware)) { + ret = cs35l56_firmware_shutdown(&cs35l56->base); + if (ret) +- goto err; ++ return; + } + + ret = cs_dsp_power_up(&cs35l56->cs_dsp, wmfw_firmware, wmfw_filename, + coeff_firmware, coeff_filename, "misc"); + if (ret) { + dev_dbg(cs35l56->base.dev, "%s: cs_dsp_power_up ret %d\n", __func__, ret); +- goto err; ++ return; + } + + if (wmfw_filename) +@@ -679,11 +667,6 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + err_powered_up: + if (!cs35l56->base.fw_patched) + cs_dsp_power_down(&cs35l56->cs_dsp); +-err: +- mutex_unlock(&cs35l56->base.irq_lock); +-err_fw_release: +- cs35l56_hda_release_firmware_files(wmfw_firmware, wmfw_filename, +- coeff_firmware, coeff_filename); + } + + static void cs35l56_hda_dsp_work(struct work_struct *work) +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +@@ -88,7 +88,6 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) + { + struct gpio_desc *speaker_id; + struct acpi_device *adev; +- struct device *physdev; + LIST_HEAD(resources); + const char *sub; + uint32_t subid; +@@ -101,7 +100,8 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) + return -ENODEV; + } + +- physdev = get_device(acpi_get_first_physical_node(adev)); ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); + ret = acpi_dev_get_resources(adev, &resources, tas2781_get_i2c_res, p); + if (ret < 0) { + dev_err(p->dev, "Failed to get ACPI resource.\n"); +@@ -151,14 +151,12 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) + end_2563: + acpi_dev_free_resource_list(&resources); + strscpy(p->dev_name, hid, sizeof(p->dev_name)); +- put_device(physdev); + acpi_dev_put(adev); + + return 0; + + err: + dev_err(p->dev, "read acpi error, ret: %d\n", ret); +- put_device(physdev); + acpi_dev_put(adev); + + return ret; +@@ -173,13 +171,13 @@ static void tas2781_hda_playback_hook(struct device *dev, int action) + case HDA_GEN_PCM_ACT_OPEN: + pm_runtime_get_sync(dev); + scoped_guard(mutex, &tas_hda->priv->codec_lock) { +- tasdevice_tuning_switch(tas_hda->priv, 0); ++ tasdevice_tuning_switch(tas_hda->priv, 0, false); + tas_hda->priv->playback_started = true; + } + break; + case HDA_GEN_PCM_ACT_CLOSE: + scoped_guard(mutex, &tas_hda->priv->codec_lock) { +- tasdevice_tuning_switch(tas_hda->priv, 1); ++ tasdevice_tuning_switch(tas_hda->priv, 1, false); + tas_hda->priv->playback_started = false; + } + +@@ -722,7 +720,7 @@ static int tas2781_runtime_suspend(struct device *dev) + * Stop the playback if it's unused. + */ + if (tas_hda->priv->playback_started) { +- tasdevice_tuning_switch(tas_hda->priv, 1); ++ tasdevice_tuning_switch(tas_hda->priv, 1, false); + tas_hda->priv->playback_started = false; + } + +@@ -752,7 +750,7 @@ static int tas2781_system_suspend(struct device *dev) + + /* Shutdown chip before system suspend */ + if (tas_hda->priv->playback_started) +- tasdevice_tuning_switch(tas_hda->priv, 1); ++ tasdevice_tuning_switch(tas_hda->priv, 1, false); + + /* + * Reset GPIO may be shared, so cannot reset here. +@@ -785,7 +783,7 @@ static int tas2781_system_resume(struct device *dev) + TASDEVICE_BIN_BLK_PRE_POWER_UP); + + if (tas_hda->priv->playback_started) +- tasdevice_tuning_switch(tas_hda->priv, 0); ++ tasdevice_tuning_switch(tas_hda->priv, 0, false); + + return 0; + } +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +@@ -328,7 +328,6 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + { + struct tasdevice_priv *p = tas_hda->priv; + struct acpi_device *adev; +- struct device *physdev; + u32 values[HDA_MAX_COMPONENTS]; + const char *property; + size_t nval; +@@ -341,7 +340,9 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + } + + strscpy(p->dev_name, hid, sizeof(p->dev_name)); +- physdev = get_device(acpi_get_first_physical_node(adev)); ++ ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); + acpi_dev_put(adev); + if (!physdev) + return -ENODEV; +@@ -381,13 +382,11 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + goto err; + } + } +- put_device(physdev); + + return 0; ++ + err: + dev_err(p->dev, "read acpi error, ret: %d\n", ret); +- put_device(physdev); +- + return ret; + } + +@@ -400,11 +399,11 @@ static void tas2781_hda_playback_hook(struct device *dev, int action) + pm_runtime_get_sync(dev); + guard(mutex)(&tas_priv->codec_lock); + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK) +- tasdevice_tuning_switch(tas_hda->priv, 0); ++ tasdevice_tuning_switch(tas_hda->priv, 0, false); + } else if (action == HDA_GEN_PCM_ACT_CLOSE) { + guard(mutex)(&tas_priv->codec_lock); + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK) +- tasdevice_tuning_switch(tas_priv, 1); ++ tasdevice_tuning_switch(tas_priv, 1, false); + pm_runtime_put_autosuspend(dev); + } + } +@@ -848,7 +847,7 @@ static int tas2781_runtime_suspend(struct device *dev) + + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK + && tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 1); ++ tasdevice_tuning_switch(tas_priv, 1, false); + + tas_priv->tasdevice[tas_priv->index].cur_book = -1; + tas_priv->tasdevice[tas_priv->index].cur_conf = -1; +@@ -865,7 +864,7 @@ static int tas2781_runtime_resume(struct device *dev) + + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK + && tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 0); ++ tasdevice_tuning_switch(tas_priv, 0, false); + + return 0; + } +@@ -883,7 +882,7 @@ static int tas2781_system_suspend(struct device *dev) + /* Shutdown chip before system suspend */ + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK + && tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 1); ++ tasdevice_tuning_switch(tas_priv, 1, false); + + return 0; + } +@@ -918,7 +917,7 @@ static int tas2781_system_resume(struct device *dev) + tas_priv->fw_state = TASDEVICE_DSP_FW_ALL_OK; + + if (tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 0); ++ tasdevice_tuning_switch(tas_priv, 0, false); + } + + return ret; +diff --git a/sound/hda/common/auto_parser.c b/sound/hda/common/auto_parser.c +--- a/sound/hda/common/auto_parser.c ++++ b/sound/hda/common/auto_parser.c +@@ -601,9 +601,9 @@ static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums) + return -1; + } + +-/* get a unique suffix or an index number */ ++/* get a unique suffix */ + static const char *check_output_sfx(hda_nid_t nid, const hda_nid_t *pins, +- int num_pins, int *indexp) ++ int num_pins) + { + static const char * const channel_sfx[] = { + " Front", " Surround", " CLFE", " Side" +@@ -615,11 +615,8 @@ static const char *check_output_sfx(hda_nid_t nid, const hda_nid_t *pins, + return NULL; + if (num_pins == 1) + return ""; +- if (num_pins > ARRAY_SIZE(channel_sfx)) { +- if (indexp) +- *indexp = i; ++ if (num_pins > ARRAY_SIZE(channel_sfx)) + return ""; +- } + return channel_sfx[i]; + } + +@@ -638,27 +635,9 @@ static const char *check_output_pfx(struct hda_codec *codec, hda_nid_t nid) + return ""; + } + +-static int get_hp_label_index(struct hda_codec *codec, hda_nid_t nid, +- const hda_nid_t *pins, int num_pins) +-{ +- int i, j, idx = 0; +- +- const char *pfx = check_output_pfx(codec, nid); +- +- i = find_idx_in_nid_list(nid, pins, num_pins); +- if (i < 0) +- return -1; +- for (j = 0; j < i; j++) +- if (pfx == check_output_pfx(codec, pins[j])) +- idx++; +- +- return idx; +-} +- + static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, +- const char *name, char *label, int maxlen, +- int *indexp) ++ const char *name, char *label, int maxlen) + { + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid); + int attr = snd_hda_get_input_pin_attr(def_conf); +@@ -671,19 +650,11 @@ static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + + if (cfg) { + /* try to give a unique suffix if needed */ +- sfx = check_output_sfx(nid, cfg->line_out_pins, cfg->line_outs, +- indexp); ++ sfx = check_output_sfx(nid, cfg->line_out_pins, cfg->line_outs); ++ if (!sfx) ++ sfx = check_output_sfx(nid, cfg->speaker_pins, cfg->speaker_outs); + if (!sfx) +- sfx = check_output_sfx(nid, cfg->speaker_pins, cfg->speaker_outs, +- indexp); +- if (!sfx) { +- /* don't add channel suffix for Headphone controls */ +- int idx = get_hp_label_index(codec, nid, cfg->hp_pins, +- cfg->hp_outs); +- if (idx >= 0 && indexp) +- *indexp = idx; + sfx = ""; +- } + } + snprintf(label, maxlen, "%s%s%s", pfx, name, sfx); + return 1; +@@ -699,7 +670,6 @@ static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + * @cfg: the parsed pin configuration + * @label: the string buffer to store + * @maxlen: the max length of string buffer (including termination) +- * @indexp: the pointer to return the index number (for multiple ctls) + * + * Get a label for the given pin. This function works for both input and + * output pins. When @cfg is given as non-NULL, the function tries to get +@@ -708,47 +678,33 @@ static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + * This function tries to give a unique label string for the pin as much as + * possible. For example, when the multiple line-outs are present, it adds + * the channel suffix like "Front", "Surround", etc (only when @cfg is given). +- * If no unique name with a suffix is available and @indexp is non-NULL, the +- * index number is stored in the pointer. + */ + int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, +- char *label, int maxlen, int *indexp) ++ char *label, int maxlen) + { + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid); + const char *name = NULL; + int i; + bool hdmi; + +- if (indexp) +- *indexp = 0; + if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE) + return 0; + + switch (get_defcfg_device(def_conf)) { + case AC_JACK_LINE_OUT: + return fill_audio_out_name(codec, nid, cfg, "Line Out", +- label, maxlen, indexp); ++ label, maxlen); + case AC_JACK_SPEAKER: + return fill_audio_out_name(codec, nid, cfg, "Speaker", +- label, maxlen, indexp); ++ label, maxlen); + case AC_JACK_HP_OUT: + return fill_audio_out_name(codec, nid, cfg, "Headphone", +- label, maxlen, indexp); ++ label, maxlen); + case AC_JACK_SPDIF_OUT: + case AC_JACK_DIG_OTHER_OUT: + hdmi = is_hdmi_cfg(def_conf); + name = hdmi ? "HDMI" : "SPDIF"; +- if (cfg && indexp) +- for (i = 0; i < cfg->dig_outs; i++) { +- hda_nid_t pin = cfg->dig_out_pins[i]; +- unsigned int c; +- if (pin == nid) +- break; +- c = snd_hda_codec_get_pincfg(codec, pin); +- if (hdmi == is_hdmi_cfg(c)) +- (*indexp)++; +- } + break; + default: + if (cfg) { +diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c +--- a/sound/hda/common/codec.c ++++ b/sound/hda/common/codec.c +@@ -39,6 +39,12 @@ static int call_exec_verb(struct hda_bus *bus, struct hda_codec *codec, + int err; + + CLASS(snd_hda_power_pm, pm)(codec); ++ if (pm.err < 0 && !bus->core.chip_init) { ++ codec_warn(codec, ++ "Failed to send cmd 0x%x ret=[%d], hda control stopped\n", ++ cmd, pm.err); ++ return pm.err; ++ } + guard(mutex)(&bus->core.cmd_mutex); + if (flags & HDA_RW_NO_RESPONSE_FALLBACK) + bus->no_response_fallback = 1; +@@ -2967,8 +2973,11 @@ static void hda_codec_pm_complete(struct device *dev) + dev->power.power_state = PMSG_RESUME; + + if (pm_runtime_suspended(dev) && (codec->jackpoll_interval || +- hda_codec_need_resume(codec) || codec->forced_resume)) ++ hda_codec_need_resume(codec) || codec->forced_resume || ++ codec->acomp_requested_resume)) { ++ codec->acomp_requested_resume = 0; + pm_request_resume(dev); ++ } + } + + static int hda_codec_pm_suspend(struct device *dev) +@@ -3370,7 +3379,7 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, + for (; knew->name; knew++) { + struct snd_kcontrol *kctl; + int addr = 0, idx = 0; +- if (knew->iface == (__force snd_ctl_elem_iface_t)-1) ++ if (knew->iface == -1) + continue; /* skip this codec private value */ + for (;;) { + kctl = snd_ctl_new1(knew, codec); +diff --git a/sound/hda/common/hda_auto_parser.h b/sound/hda/common/hda_auto_parser.h +--- a/sound/hda/common/hda_auto_parser.h ++++ b/sound/hda/common/hda_auto_parser.h +@@ -46,7 +46,7 @@ const char *hda_get_autocfg_input_label(struct hda_codec *codec, + int input); + int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, +- char *label, int maxlen, int *indexp); ++ char *label, int maxlen); + + enum { + INPUT_PIN_ATTR_UNUSED, /* pin not connected */ +diff --git a/sound/hda/common/hda_local.h b/sound/hda/common/hda_local.h +--- a/sound/hda/common/hda_local.h ++++ b/sound/hda/common/hda_local.h +@@ -723,4 +723,11 @@ void snd_hda_codec_display_power(struct hda_codec *codec, bool enable); + #define codec_dbg(codec, fmt, args...) \ + dev_dbg(hda_codec_dev(codec), fmt, ##args) + ++/* append a suffix string safely; equivalent with strlcat() */ ++static inline void hda_append_suffix(char *str, const char *suffix, size_t size) ++{ ++ size_t len = strnlen(str, size); ++ strscpy(str + len, suffix, size - len); ++} ++ + #endif /* __SOUND_HDA_LOCAL_H */ +diff --git a/sound/hda/common/jack.c b/sound/hda/common/jack.c +--- a/sound/hda/common/jack.c ++++ b/sound/hda/common/jack.c +@@ -612,10 +612,10 @@ static int add_jack_kctl(struct hda_codec *codec, hda_nid_t nid, + if (base_name) + strscpy(name, base_name, sizeof(name)); + else +- snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name), NULL); ++ snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name)); + if (phantom_jack) + /* Example final name: "Internal Mic Phantom Jack" */ +- strncat(name, " Phantom", sizeof(name) - strlen(name) - 1); ++ hda_append_suffix(name, " Phantom", sizeof(name)); + err = snd_hda_jack_add_kctl(codec, nid, name, phantom_jack, 0, NULL); + if (err < 0) + return err; +diff --git a/sound/hda/common/proc.c b/sound/hda/common/proc.c +--- a/sound/hda/common/proc.c ++++ b/sound/hda/common/proc.c +@@ -624,7 +624,7 @@ static void print_conn_list(struct snd_info_buffer *buffer, + /* Get Cache connections info */ + cache_len = snd_hda_get_conn_list(codec, nid, &list); + if (cache_len >= 0 && (cache_len != conn_len || +- memcmp(list, conn, conn_len) != 0)) { ++ memcmp(list, conn, conn_len * sizeof(*conn)) != 0)) { + snd_iprintf(buffer, " In-driver Connection: %d\n", cache_len); + if (cache_len > 0) { + snd_iprintf(buffer, " "); +diff --git a/sound/hda/controllers/Kconfig b/sound/hda/controllers/Kconfig +--- a/sound/hda/controllers/Kconfig ++++ b/sound/hda/controllers/Kconfig +@@ -33,6 +33,7 @@ config SND_HDA_TEGRA + config SND_HDA_CIX_IPBLOQ + tristate "CIX IPBLOQ HD Audio" + depends on ARCH_CIX || COMPILE_TEST ++ depends on ARCH_DMA_ADDR_T_64BIT + select SND_HDA + select SND_HDA_ALIGNED_MMIO + help +diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c +--- a/sound/hda/controllers/intel.c ++++ b/sound/hda/controllers/intel.c +@@ -100,6 +100,8 @@ enum { + #define ATIHDMI_NUM_CAPTURE 0 + #define ATIHDMI_NUM_PLAYBACK 8 + ++/* Hygon HD Audio controller */ ++#define PCI_DEVICE_ID_HYGON_18H_M05H_HDA 0x14a9 + + static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; + static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +@@ -241,6 +243,7 @@ enum { + AZX_DRIVER_ZHAOXIN, + AZX_DRIVER_ZHAOXINHDMI, + AZX_DRIVER_LOONGSON, ++ AZX_DRIVER_HYGON, + AZX_DRIVER_GENERIC, + AZX_NUM_DRIVERS, /* keep this as last entry */ + }; +@@ -360,6 +363,7 @@ static const char * const driver_short_names[] = { + [AZX_DRIVER_ZHAOXIN] = "HDA Zhaoxin", + [AZX_DRIVER_ZHAOXINHDMI] = "HDA Zhaoxin HDMI", + [AZX_DRIVER_LOONGSON] = "HDA Loongson", ++ [AZX_DRIVER_HYGON] = "HDA Hygon", + [AZX_DRIVER_GENERIC] = "HD-Audio Generic", + }; + +@@ -1926,6 +1930,10 @@ static int azx_first_init(struct azx *chip) + if (chip->driver_type == AZX_DRIVER_ZHAOXINHDMI) + bus->polling_mode = 1; + ++ if (chip->driver_type == AZX_DRIVER_HYGON && ++ chip->pci->device == PCI_DEVICE_ID_HYGON_18H_M05H_HDA) ++ bus->access_sdnctl_in_dword = 1; ++ + bus->remap_addr = pcim_iomap_region(pci, 0, "ICH HD audio"); + if (IS_ERR(bus->remap_addr)) + return PTR_ERR(bus->remap_addr); +@@ -2189,6 +2197,15 @@ static int azx_probe(struct pci_dev *pci, + dev_warn(&pci->dev, "dmic_detect option is deprecated, pass snd-intel-dspcfg.dsp_driver=1 option instead\n"); + } + ++ /* A sanity check against wild device binding; ++ * here the range 0x200 is enough for the registers used at probe, ++ * but it doesn't mean covering all HD-audio registers ++ */ ++ if (pci_resource_len(pci, 0) < 0x200) { ++ dev_err(&pci->dev, "Too small PCI BAR0\n"); ++ return -EINVAL; ++ } ++ + err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) { +@@ -2381,7 +2398,7 @@ static int azx_probe_continue(struct azx *chip) + + #ifdef CONFIG_SND_HDA_PATCH_LOADER + if (patch[dev] && *patch[dev]) { +- const struct firmware *fw = NULL; ++ const struct firmware *fw __free(firmware) = NULL; + + dev_info(&pci->dev, "Applying patch firmware '%s'\n", + patch[dev]); +@@ -2390,7 +2407,6 @@ static int azx_probe_continue(struct azx *chip) + "Cannot load firmware, continue without patching\n"); + } else { + err = snd_hda_load_patch(&chip->bus, fw->size, fw->data); +- release_firmware(fw); + if (err < 0) + goto out_free; + } +@@ -2838,6 +2854,11 @@ static const struct pci_device_id azx_ids[] = { + .driver_data = AZX_DRIVER_LOONGSON | AZX_DCAPS_NO_TCSEL }, + { PCI_VDEVICE(LOONGSON, PCI_DEVICE_ID_LOONGSON_HDMI), + .driver_data = AZX_DRIVER_LOONGSON | AZX_DCAPS_NO_TCSEL }, ++ /* Hygon HDAudio */ ++ { PCI_VDEVICE(HYGON, PCI_DEVICE_ID_HYGON_18H_M05H_HDA), ++ .driver_data = AZX_DRIVER_HYGON | AZX_DCAPS_POSFIX_LPIB | AZX_DCAPS_NO_MSI }, ++ /* Lisuan HD-audio */ ++ { PCI_DEVICE(0x4c54, 0x5010), .driver_data = AZX_DRIVER_GENERIC }, + { 0, } + }; + MODULE_DEVICE_TABLE(pci, azx_ids); +diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c +--- a/sound/hda/core/controller.c ++++ b/sound/hda/core/controller.c +@@ -511,7 +511,10 @@ void snd_hdac_bus_exit_link_reset(struct hdac_bus *bus) + { + unsigned long timeout; + +- snd_hdac_chip_updateb(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET); ++ else ++ snd_hdac_chip_updateb(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET); + + timeout = jiffies + msecs_to_jiffies(100); + while (!snd_hdac_chip_readb(bus, GCTL) && time_before(jiffies, timeout)) +@@ -576,7 +579,10 @@ static void azx_int_disable(struct hdac_bus *bus) + + /* disable interrupts in stream descriptor */ + list_for_each_entry(azx_dev, &bus->stream_list, list) +- snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, SD_INT_MASK, 0); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0); + + /* disable SIE for all streams & disable controller CIE and GIE */ + snd_hdac_chip_writel(bus, INTCTL, 0); +@@ -682,6 +688,11 @@ int snd_hdac_bus_handle_stream_irq(struct hdac_bus *bus, unsigned int status, + sd_status = snd_hdac_stream_readb(azx_dev, SD_STS); + snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); + handled |= 1 << azx_dev->index; ++ if (sd_status & (SD_INT_FIFO_ERR | SD_INT_DESC_ERR)) { ++ dev_warn_ratelimited(bus->dev, ++ "stream %u dma error: 0x%02x\n", ++ azx_dev->index, sd_status); ++ } + if ((!azx_dev->substream && !azx_dev->cstream) || + !azx_dev->running || !(sd_status & SD_INT_COMPLETE)) + continue; +@@ -713,7 +724,7 @@ int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus) + BDL_SIZE, &s->bdl); + num_streams++; + if (err < 0) +- return -ENOMEM; ++ goto error_bdl; + } + + if (WARN_ON(!num_streams)) +@@ -722,12 +733,24 @@ int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus) + err = snd_dma_alloc_pages(dma_type, bus->dev, + num_streams * 8, &bus->posbuf); + if (err < 0) +- return -ENOMEM; ++ goto error_bdl; + list_for_each_entry(s, &bus->stream_list, list) + s->posbuf = (__le32 *)(bus->posbuf.area + s->index * 8); + + /* single page (at least 4096 bytes) must suffice for both ringbuffes */ +- return snd_dma_alloc_pages(dma_type, bus->dev, PAGE_SIZE, &bus->rb); ++ err = snd_dma_alloc_pages(dma_type, bus->dev, PAGE_SIZE, &bus->rb); ++ if (err < 0) ++ goto error_posbuf; ++ return 0; ++ ++error_posbuf: ++ snd_dma_free_pages(&bus->posbuf); ++error_bdl: ++ list_for_each_entry(s, &bus->stream_list, list) { ++ if (s->bdl.area) ++ snd_dma_free_pages(&s->bdl); ++ } ++ return -ENOMEM; + } + EXPORT_SYMBOL_GPL(snd_hdac_bus_alloc_stream_pages); + +diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c +--- a/sound/hda/core/device.c ++++ b/sound/hda/core/device.c +@@ -663,6 +663,7 @@ static const struct hda_vendor_id hda_vendor_ids[] = { + { 0x1af4, "QEMU" }, + { 0x1fa8, "Senarytech" }, + { 0x434d, "C-Media" }, ++ { 0x4c54, "Lisuan" }, + { 0x8086, "Intel" }, + { 0x8384, "SigmaTel" }, + {} /* terminator */ +@@ -765,7 +766,7 @@ unsigned int snd_hdac_stream_format_bits(snd_pcm_format_t format, snd_pcm_subfor + + params_set_format(¶ms, snd_hdac_format_normalize(format)); + snd_mask_set(hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT), +- (__force unsigned int)subformat); ++ subformat); + + bits = snd_pcm_hw_params_bits(¶ms); + if (maxbits) +diff --git a/sound/hda/core/hda_bus_type.c b/sound/hda/core/hda_bus_type.c +--- a/sound/hda/core/hda_bus_type.c ++++ b/sound/hda/core/hda_bus_type.c +@@ -39,10 +39,7 @@ EXPORT_SYMBOL_GPL(hdac_get_device_id); + + static int hdac_codec_match(struct hdac_device *dev, const struct hdac_driver *drv) + { +- if (hdac_get_device_id(dev, drv)) +- return 1; +- else +- return 0; ++ return !!hdac_get_device_id(dev, drv); + } + + static int hda_bus_match(struct device *dev, const struct device_driver *drv) +@@ -59,9 +56,7 @@ static int hda_bus_match(struct device *dev, const struct device_driver *drv) + */ + if (hdrv->match) + return hdrv->match(hdev, hdrv); +- else +- return hdac_codec_match(hdev, hdrv); +- return 1; ++ return hdac_codec_match(hdev, hdrv); + } + + static int hda_uevent(const struct device *dev, struct kobj_uevent_env *env) +diff --git a/sound/hda/core/stream.c b/sound/hda/core/stream.c +--- a/sound/hda/core/stream.c ++++ b/sound/hda/core/stream.c +@@ -146,8 +146,12 @@ void snd_hdac_stream_start(struct hdac_stream *azx_dev) + stripe_ctl = snd_hdac_get_stream_stripe_ctl(bus, azx_dev->substream); + else + stripe_ctl = 0; +- snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, +- stripe_ctl); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, ++ stripe_ctl); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, ++ stripe_ctl); + } + /* set DMA start and interrupt mask */ + if (bus->access_sdnctl_in_dword) +@@ -166,11 +170,22 @@ EXPORT_SYMBOL_GPL(snd_hdac_stream_start); + */ + static void snd_hdac_stream_clear(struct hdac_stream *azx_dev) + { +- snd_hdac_stream_updateb(azx_dev, SD_CTL, +- SD_CTL_DMA_START | SD_INT_MASK, 0); +- snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ +- if (azx_dev->stripe) +- snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0); ++ struct hdac_bus *bus = azx_dev->bus; ++ ++ if (bus->access_sdnctl_in_dword) { ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, ++ SD_CTL_DMA_START | SD_INT_MASK, 0); ++ snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ ++ if (azx_dev->stripe) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0); ++ } else { ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, ++ SD_CTL_DMA_START | SD_INT_MASK, 0); ++ snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ ++ if (azx_dev->stripe) ++ snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0); ++ } ++ + azx_dev->running = false; + } + +@@ -225,12 +240,16 @@ void snd_hdac_stream_reset(struct hdac_stream *azx_dev) + { + unsigned char val; + int dma_run_state; ++ struct hdac_bus *bus = azx_dev->bus; + + snd_hdac_stream_clear(azx_dev); + + dma_run_state = snd_hdac_stream_readb(azx_dev, SD_CTL) & SD_CTL_DMA_START; + +- snd_hdac_stream_updateb(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); + + /* wait for hardware to report that the stream entered reset */ + snd_hdac_stream_readb_poll(azx_dev, SD_CTL, val, (val & SD_CTL_STREAM_RESET), 3, 300); +@@ -238,7 +257,10 @@ void snd_hdac_stream_reset(struct hdac_stream *azx_dev) + if (azx_dev->bus->dma_stop_delay && dma_run_state) + udelay(azx_dev->bus->dma_stop_delay); + +- snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0); + + /* wait for hardware to report that the stream is out of reset */ + snd_hdac_stream_readb_poll(azx_dev, SD_CTL, val, !(val & SD_CTL_STREAM_RESET), 3, 300); +diff --git a/sound/isa/msnd/msnd_pinnacle.c b/sound/isa/msnd/msnd_pinnacle.c +--- a/sound/isa/msnd/msnd_pinnacle.c ++++ b/sound/isa/msnd/msnd_pinnacle.c +@@ -367,7 +367,8 @@ static int snd_msnd_init_sma(struct snd_msnd *chip) + static int upload_dsp_code(struct snd_card *card) + { + struct snd_msnd *chip = card->private_data; +- const struct firmware *init_fw = NULL, *perm_fw = NULL; ++ const struct firmware *init_fw __free(firmware) = NULL; ++ const struct firmware *perm_fw __free(firmware) = NULL; + int err; + + outb(HPBLKSEL_0, chip->io + HP_BLKS); +@@ -375,28 +376,21 @@ static int upload_dsp_code(struct snd_card *card) + err = request_firmware(&init_fw, INITCODEFILE, card->dev); + if (err < 0) { + dev_err(card->dev, LOGNAME ": Error loading " INITCODEFILE); +- goto cleanup1; ++ return err; + } + err = request_firmware(&perm_fw, PERMCODEFILE, card->dev); + if (err < 0) { + dev_err(card->dev, LOGNAME ": Error loading " PERMCODEFILE); +- goto cleanup; ++ return err; + } + + memcpy_toio(chip->mappedbase, perm_fw->data, perm_fw->size); + if (snd_msnd_upload_host(chip, init_fw->data, init_fw->size) < 0) { + dev_warn(card->dev, LOGNAME ": Error uploading to DSP\n"); +- err = -ENODEV; +- goto cleanup; ++ return -ENODEV; + } + dev_info(card->dev, LOGNAME ": DSP firmware uploaded\n"); +- err = 0; +- +-cleanup: +- release_firmware(perm_fw); +-cleanup1: +- release_firmware(init_fw); +- return err; ++ return 0; + } + + #ifdef MSND_CLASSIC +diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c +--- a/sound/isa/sscape.c ++++ b/sound/isa/sscape.c +@@ -526,7 +526,7 @@ static int upload_dma_data(struct soundscape *s, const unsigned char *data, + static int sscape_upload_bootblock(struct snd_card *card) + { + struct soundscape *sscape = get_card_soundscape(card); +- const struct firmware *init_fw = NULL; ++ const struct firmware *init_fw __free(firmware) = NULL; + int data = 0; + int ret; + +@@ -537,8 +537,6 @@ static int sscape_upload_bootblock(struct snd_card *card) + } + ret = upload_dma_data(sscape, init_fw->data, init_fw->size); + +- release_firmware(init_fw); +- + guard(spinlock_irqsave)(&sscape->lock); + if (ret == 0) + data = host_read_ctrl_unsafe(sscape->io_base, 100); +@@ -562,7 +560,7 @@ static int sscape_upload_bootblock(struct snd_card *card) + static int sscape_upload_microcode(struct snd_card *card, int version) + { + struct soundscape *sscape = get_card_soundscape(card); +- const struct firmware *init_fw = NULL; ++ const struct firmware *init_fw __free(firmware) = NULL; + char name[14]; + int err; + +@@ -579,8 +577,6 @@ static int sscape_upload_microcode(struct snd_card *card, int version) + dev_info(card->dev, "sscape: MIDI firmware loaded %zu KBs\n", + init_fw->size >> 10); + +- release_firmware(init_fw); +- + return err; + } + +diff --git a/sound/isa/wavefront/wavefront_fx.c b/sound/isa/wavefront/wavefront_fx.c +--- a/sound/isa/wavefront/wavefront_fx.c ++++ b/sound/isa/wavefront/wavefront_fx.c +@@ -232,41 +232,32 @@ snd_wavefront_fx_start (snd_wavefront_t *dev) + { + unsigned int i; + int err; +- const struct firmware *firmware = NULL; ++ const struct firmware *firmware __free(firmware) = NULL; + + if (dev->fx_initialized) + return 0; + + err = request_firmware(&firmware, "yamaha/yss225_registers.bin", + dev->card->dev); +- if (err < 0) { +- err = -1; +- goto out; +- } ++ if (err < 0) ++ return -1; + + for (i = 0; i + 1 < firmware->size; i += 2) { + if (firmware->data[i] >= 8 && firmware->data[i] < 16) { + outb(firmware->data[i + 1], + dev->base + firmware->data[i]); + } else if (firmware->data[i] == WAIT_IDLE) { +- if (!wavefront_fx_idle(dev)) { +- err = -1; +- goto out; +- } ++ if (!wavefront_fx_idle(dev)) ++ return -1; + } else { + dev_err(dev->card->dev, + "invalid address in register data\n"); +- err = -1; +- goto out; ++ return -1; + } + } + + dev->fx_initialized = 1; +- err = 0; +- +-out: +- release_firmware(firmware); +- return err; ++ return 0; + } + + MODULE_FIRMWARE("yamaha/yss225_registers.bin"); +diff --git a/sound/isa/wavefront/wavefront_synth.c b/sound/isa/wavefront/wavefront_synth.c +--- a/sound/isa/wavefront/wavefront_synth.c ++++ b/sound/isa/wavefront/wavefront_synth.c +@@ -2053,7 +2053,7 @@ wavefront_download_firmware (snd_wavefront_t *dev, char *path) + const unsigned char *buf; + int len, err; + int section_cnt_downloaded = 0; +- const struct firmware *firmware; ++ const struct firmware *firmware __free(firmware) = NULL; + + err = request_firmware(&firmware, path, dev->card->dev); + if (err < 0) { +@@ -2108,11 +2108,9 @@ wavefront_download_firmware (snd_wavefront_t *dev, char *path) + section_cnt_downloaded++; + } + +- release_firmware(firmware); + return 0; + + failure: +- release_firmware(firmware); + dev_err(dev->card->dev, "firmware download failed!!!\n"); + return 1; + } +diff --git a/sound/pci/asihpi/asihpi.c b/sound/pci/asihpi/asihpi.c +--- a/sound/pci/asihpi/asihpi.c ++++ b/sound/pci/asihpi/asihpi.c +@@ -280,7 +280,7 @@ static void print_hwparams(struct snd_pcm_substream *substream, + snd_pcm_format_width(params_format(p)) / 8); + } + +-#define INVALID_FORMAT (__force snd_pcm_format_t)(-1) ++#define INVALID_FORMAT -1 + + static const snd_pcm_format_t hpi_to_alsa_formats[] = { + INVALID_FORMAT, /* INVALID */ +diff --git a/sound/pci/asihpi/hpi6000.c b/sound/pci/asihpi/hpi6000.c +--- a/sound/pci/asihpi/hpi6000.c ++++ b/sound/pci/asihpi/hpi6000.c +@@ -537,6 +537,11 @@ static short create_adapter_obj(struct hpi_adapter_obj *pao, + hr1.size = sizeof(hr1); + + error = hpi6000_message_response_sequence(pao, 0, &hm, &hr0); ++ if (error) { ++ HPI_DEBUG_LOG(ERROR, "message transport error %d\n", ++ error); ++ return error; ++ } + if (hr0.error) { + HPI_DEBUG_LOG(DEBUG, "message error %d\n", hr0.error); + return hr0.error; +diff --git a/sound/pci/asihpi/hpidspcd.c b/sound/pci/asihpi/hpidspcd.c +--- a/sound/pci/asihpi/hpidspcd.c ++++ b/sound/pci/asihpi/hpidspcd.c +@@ -23,7 +23,7 @@ struct dsp_code_private { + short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + u32 *os_error_code) + { +- const struct firmware *firmware; ++ const struct firmware *firmware __free(firmware) = NULL; + struct pci_dev *dev = os_data; + struct code_header header; + char fw_name[20]; +@@ -37,11 +37,11 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + if (err || !firmware) { + dev_err(&dev->dev, "%d, request_firmware failed for %s\n", + err, fw_name); +- goto error1; ++ goto error; + } + if (firmware->size < sizeof(header)) { + dev_err(&dev->dev, "Header size too small %s\n", fw_name); +- goto error2; ++ goto error; + } + memcpy(&header, firmware->data, sizeof(header)); + +@@ -51,7 +51,7 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + dev_err(&dev->dev, + "Invalid firmware header size %d != file %zd\n", + header.size, firmware->size); +- goto error2; ++ goto error; + } + + if (HPI_VER_MAJOR(header.version) != HPI_VER_MAJOR(HPI_VER)) { +@@ -59,7 +59,7 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + dev_err(&dev->dev, + "Incompatible firmware version DSP image %X != Driver %X\n", + header.version, HPI_VER); +- goto error2; ++ goto error; + } + + if (header.version != HPI_VER) { +@@ -72,19 +72,17 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + dsp_code->pvt = kmalloc_obj(*dsp_code->pvt); + if (!dsp_code->pvt) { + err_ret = HPI_ERROR_MEMORY_ALLOC; +- goto error2; ++ goto error; + } + + dsp_code->pvt->dev = dev; +- dsp_code->pvt->firmware = firmware; ++ dsp_code->pvt->firmware = no_free_ptr(firmware); + dsp_code->header = header; + dsp_code->block_length = header.size / sizeof(u32); + dsp_code->word_count = sizeof(header) / sizeof(u32); + return 0; + +-error2: +- release_firmware(firmware); +-error1: ++error: + dsp_code->block_length = 0; + return err_ret; + } +diff --git a/sound/pci/asihpi/hpioctl.c b/sound/pci/asihpi/hpioctl.c +--- a/sound/pci/asihpi/hpioctl.c ++++ b/sound/pci/asihpi/hpioctl.c +@@ -385,8 +385,7 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, + if (pci_resource_flags(pci_dev, idx) & IORESOURCE_MEM) { + memlen = pci_resource_len(pci_dev, idx); + pci.ap_mem_base[idx] = +- ioremap(pci_resource_start(pci_dev, idx), +- memlen); ++ pcim_iomap(pci_dev, idx, memlen); + if (!pci.ap_mem_base[idx]) { + HPI_DEBUG_LOG(ERROR, + "ioremap failed, aborting\n"); +@@ -509,13 +508,6 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, + return 0; + + err: +- while (--idx >= 0) { +- if (pci.ap_mem_base[idx]) { +- iounmap(pci.ap_mem_base[idx]); +- pci.ap_mem_base[idx] = NULL; +- } +- } +- + if (adapter.p_buffer) { + adapter.buffer_size = 0; + vfree(adapter.p_buffer); +@@ -527,14 +519,11 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, + + void asihpi_adapter_remove(struct pci_dev *pci_dev) + { +- int idx; + struct hpi_message hm; + struct hpi_response hr; + struct hpi_adapter *pa; +- struct hpi_pci pci; + + pa = pci_get_drvdata(pci_dev); +- pci = pa->adapter->pci; + + /* Disable IRQ generation on DSP side */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, +@@ -550,10 +539,6 @@ void asihpi_adapter_remove(struct pci_dev *pci_dev) + hm.adapter_index = pa->adapter->index; + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); + +- /* unmap PCI memory space, mapped during device init. */ +- for (idx = 0; idx < HPI_MAX_ADAPTER_MEM_SPACES; ++idx) +- iounmap(pci.ap_mem_base[idx]); +- + if (pa->irq) + free_irq(pa->irq, pa); + +diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c +--- a/sound/pci/cs46xx/cs46xx_lib.c ++++ b/sound/pci/cs46xx/cs46xx_lib.c +@@ -387,7 +387,7 @@ static int load_firmware(struct snd_cs46xx *chip, + unsigned int nums, fwlen, fwsize; + const __le32 *fwdat; + struct dsp_module_desc *module = NULL; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + char fw_path[32]; + + sprintf(fw_path, "cs46xx/%s", fw_name); +@@ -395,10 +395,8 @@ static int load_firmware(struct snd_cs46xx *chip, + if (err < 0) + return err; + fwsize = fw->size / 4; +- if (fwsize < 2) { +- err = -EINVAL; +- goto error; +- } ++ if (fwsize < 2) ++ return -EINVAL; + + err = -ENOMEM; + module = kzalloc_obj(*module); +@@ -454,14 +452,12 @@ static int load_firmware(struct snd_cs46xx *chip, + } + + *module_ret = module; +- release_firmware(fw); + return 0; + + error_inval: + err = -EINVAL; + error: + free_module_desc(module); +- release_firmware(fw); + return err; + } + +@@ -500,22 +496,18 @@ MODULE_FIRMWARE("cs46xx/ba1"); + + static int load_firmware(struct snd_cs46xx *chip) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int i, size, err; + + err = request_firmware(&fw, "cs46xx/ba1", &chip->pci->dev); + if (err < 0) + return err; +- if (fw->size != sizeof(*chip->ba1)) { +- err = -EINVAL; +- goto error; +- } ++ if (fw->size != sizeof(*chip->ba1)) ++ return -EINVAL; + + chip->ba1 = vmalloc(sizeof(*chip->ba1)); +- if (!chip->ba1) { +- err = -ENOMEM; +- goto error; +- } ++ if (!chip->ba1) ++ return -ENOMEM; + + memcpy_le32(chip->ba1, fw->data, sizeof(*chip->ba1)); + +@@ -524,11 +516,9 @@ static int load_firmware(struct snd_cs46xx *chip) + for (i = 0; i < BA1_MEMORY_COUNT; i++) + size += chip->ba1->memory[i].size; + if (size > BA1_DWORD_SIZE * 4) +- err = -EINVAL; ++ return -EINVAL; + +- error: +- release_firmware(fw); +- return err; ++ return 0; + } + + static __maybe_unused int snd_cs46xx_download_image(struct snd_cs46xx *chip) +diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c +--- a/sound/pci/emu10k1/emufx.c ++++ b/sound/pci/emu10k1/emufx.c +@@ -990,7 +990,7 @@ static int snd_emu10k1_list_controls(struct snd_emu10k1 *emu, + i < icode->gpr_list_control_count) { + memset(gctl, 0, sizeof(*gctl)); + id = &ctl->kcontrol->id; +- gctl->id.iface = (__force int)id->iface; ++ gctl->id.iface = id->iface; + strscpy(gctl->id.name, id->name, sizeof(gctl->id.name)); + gctl->id.index = id->index; + gctl->id.device = id->device; +@@ -1156,7 +1156,7 @@ static void + snd_emu10k1_init_mono_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 1; + if (high_res_gpr_volume) { +@@ -1180,7 +1180,7 @@ static void + snd_emu10k1_init_stereo_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 2; + if (high_res_gpr_volume) { +@@ -1205,7 +1205,7 @@ static void + snd_emu10k1_init_mono_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 1; + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; +@@ -1218,7 +1218,7 @@ static void + snd_emu10k1_init_stereo_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 2; + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; +@@ -1542,7 +1542,7 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) + * Process tone control + */ + ctl = &controls[nctl + 0]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Bass"); + ctl->vcount = 2; + ctl->count = 10; +@@ -1551,7 +1551,7 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) + ctl->value[0] = ctl->value[1] = 20; + ctl->translation = EMU10K1_GPR_TRANSLATION_BASS; + ctl = &controls[nctl + 1]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Treble"); + ctl->vcount = 2; + ctl->count = 10; +@@ -2137,7 +2137,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) + * Process tone control + */ + ctl = &controls[i + 0]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Bass"); + ctl->vcount = 2; + ctl->count = 10; +@@ -2147,7 +2147,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) + ctl->tlv = snd_emu10k1_bass_treble_db_scale; + ctl->translation = EMU10K1_GPR_TRANSLATION_BASS; + ctl = &controls[i + 1]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Treble"); + ctl->vcount = 2; + ctl->count = 10; +diff --git a/sound/pci/korg1212/korg1212.c b/sound/pci/korg1212/korg1212.c +--- a/sound/pci/korg1212/korg1212.c ++++ b/sound/pci/korg1212/korg1212.c +@@ -1980,7 +1980,7 @@ static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + __maybe_unused unsigned ioport_size; + __maybe_unused unsigned iomem2_size; + struct snd_korg1212 *korg1212 = card->private_data; +- const struct firmware *dsp_code; ++ const struct firmware *dsp_code __free(firmware) = NULL; + + err = pcim_enable_device(pci); + if (err < 0) +@@ -2147,10 +2147,8 @@ static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + + korg1212->dma_dsp = snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, + dsp_code->size); +- if (!korg1212->dma_dsp) { +- release_firmware(dsp_code); ++ if (!korg1212->dma_dsp) + return -ENOMEM; +- } + + K1212_DEBUG_PRINTK("K1212_DEBUG: DSP Code area = 0x%p (0x%08x) %d bytes [%s]\n", + korg1212->dma_dsp->area, korg1212->dma_dsp->addr, dsp_code->size, +@@ -2158,8 +2156,6 @@ static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + + memcpy(korg1212->dma_dsp->area, dsp_code->data, dsp_code->size); + +- release_firmware(dsp_code); +- + rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_RebootCard, 0, 0, 0, 0); + + if (rc) +diff --git a/sound/pci/mixart/mixart_hwdep.c b/sound/pci/mixart/mixart_hwdep.c +--- a/sound/pci/mixart/mixart_hwdep.c ++++ b/sound/pci/mixart/mixart_hwdep.c +@@ -560,12 +560,11 @@ int snd_mixart_setup_firmware(struct mixart_mgr *mgr) + "miXart8.xlx", "miXart8.elf", "miXart8AES.xlx" + }; + char path[32]; +- +- const struct firmware *fw_entry; + int i, err; + + for (i = 0; i < 3; i++) { + sprintf(path, "mixart/%s", fw_files[i]); ++ const struct firmware *fw_entry __free(firmware) = NULL; + if (request_firmware(&fw_entry, path, &mgr->pci->dev)) { + dev_err(&mgr->pci->dev, + "miXart: can't load firmware %s\n", path); +@@ -573,7 +572,6 @@ int snd_mixart_setup_firmware(struct mixart_mgr *mgr) + } + /* fake hwdep dsp record */ + err = mixart_dsp_load(mgr, i, fw_entry); +- release_firmware(fw_entry); + if (err < 0) + return err; + mgr->dsp_loaded |= 1 << i; +diff --git a/sound/pci/pcxhr/pcxhr_hwdep.c b/sound/pci/pcxhr/pcxhr_hwdep.c +--- a/sound/pci/pcxhr/pcxhr_hwdep.c ++++ b/sound/pci/pcxhr/pcxhr_hwdep.c +@@ -367,7 +367,6 @@ int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) + }; + char path[32]; + +- const struct firmware *fw_entry; + int i, err; + int fw_set = mgr->fw_file_set; + +@@ -375,6 +374,7 @@ int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) + if (!fw_files[fw_set][i]) + continue; + sprintf(path, "pcxhr/%s", fw_files[fw_set][i]); ++ const struct firmware *fw_entry __free(firmware) = NULL; + if (request_firmware(&fw_entry, path, &mgr->pci->dev)) { + dev_err(&mgr->pci->dev, + "pcxhr: can't load firmware %s\n", +@@ -383,7 +383,6 @@ int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) + } + /* fake hwdep dsp record */ + err = pcxhr_dsp_load(mgr, i, fw_entry); +- release_firmware(fw_entry); + if (err < 0) + return err; + mgr->dsp_loaded |= 1 << i; +diff --git a/sound/pci/sis7019.c b/sound/pci/sis7019.c +--- a/sound/pci/sis7019.c ++++ b/sound/pci/sis7019.c +@@ -1260,16 +1260,16 @@ static int sis_chip_create(struct snd_card *card, + sis->irq = -1; + sis->ioport = pci_resource_start(pci, 0); + +- rc = pcim_request_all_regions(pci, "SiS7019"); ++ rc = pcim_request_region(pci, 0, "SiS7019"); + if (rc) { +- dev_err(&pci->dev, "unable request regions\n"); ++ dev_err(&pci->dev, "unable request I/O region\n"); + return rc; + } + +- sis->ioaddr = devm_ioremap(&pci->dev, pci_resource_start(pci, 1), 0x4000); +- if (!sis->ioaddr) { ++ sis->ioaddr = pcim_iomap_region(pci, 1, "SiS7019"); ++ if (IS_ERR(sis->ioaddr)) { + dev_err(&pci->dev, "unable to remap MMIO, aborting\n"); +- return -EIO; ++ return PTR_ERR(sis->ioaddr); + } + + rc = sis_alloc_suspend(sis); +diff --git a/sound/pci/via82xx_modem.c b/sound/pci/via82xx_modem.c +--- a/sound/pci/via82xx_modem.c ++++ b/sound/pci/via82xx_modem.c +@@ -573,24 +573,18 @@ static inline unsigned int calc_linear_pos(struct via82xx_modem *chip, + viadev->bufsize2, viadev->idx_table[idx].offset, + viadev->idx_table[idx].size, count); + #endif +- if (count && size < count) { ++ if (! count) ++ /* bogus count 0 on the DMA boundary? */ ++ res = viadev->idx_table[idx].offset; ++ else ++ /* count register returns full size ++ * when end of buffer is reached ++ */ ++ res = viadev->idx_table[idx].offset + size; ++ if (check_invalid_pos(viadev, res)) { + dev_dbg(chip->card->dev, +- "invalid via82xx_cur_ptr, using last valid pointer\n"); ++ "invalid via82xx_cur_ptr (2), using last valid pointer\n"); + res = viadev->lastpos; +- } else { +- if (! count) +- /* bogus count 0 on the DMA boundary? */ +- res = viadev->idx_table[idx].offset; +- else +- /* count register returns full size +- * when end of buffer is reached +- */ +- res = viadev->idx_table[idx].offset + size; +- if (check_invalid_pos(viadev, res)) { +- dev_dbg(chip->card->dev, +- "invalid via82xx_cur_ptr (2), using last valid pointer\n"); +- res = viadev->lastpos; +- } + } + } + viadev->lastpos = res; /* remember the last position */ +diff --git a/sound/sh/aica.c b/sound/sh/aica.c +--- a/sound/sh/aica.c ++++ b/sound/sh/aica.c +@@ -518,8 +518,9 @@ static const struct snd_kcontrol_new snd_aica_pcmvolume_control = { + static int load_aica_firmware(void) + { + int err; +- const struct firmware *fw_entry; + spu_reset(); ++ ++ const struct firmware *fw_entry __free(firmware) = NULL; + err = request_firmware(&fw_entry, "aica_firmware.bin", &pd->dev); + if (unlikely(err)) + return err; +@@ -527,7 +528,6 @@ static int load_aica_firmware(void) + spu_disable(); + spu_memload(0, fw_entry->data, fw_entry->size); + spu_enable(); +- release_firmware(fw_entry); + return err; + } + +diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c +--- a/sound/soc/amd/acp-config.c ++++ b/sound/soc/amd/acp-config.c +@@ -63,6 +63,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook 18 M1807GA"), + }, + }, ++ { ++ /* HP OmniBook X Flip 14-kc0xxx (Strix Point, ACP 7.2) */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "HP"), ++ DMI_MATCH(DMI_BOARD_NAME, "8EA1"), ++ }, ++ }, + {} + }; + +@@ -379,5 +386,17 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[] = { + }; + EXPORT_SYMBOL(snd_soc_acpi_amd_acp70_sof_machines); + ++struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[] = { ++ { ++ .id = "AMDI1010", ++ .drv_name = "acp7x-dsp", ++ .pdata = &acp_quirk_data, ++ .fw_filename = "sof-acp7x.ri", ++ .sof_tplg_filename = "sof-acp7x.tplg", ++ }, ++ {}, ++}; ++EXPORT_SYMBOL(snd_soc_acpi_amd_acp7x_sof_machines); ++ + MODULE_DESCRIPTION("AMD ACP Machine Configuration Module"); + MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c +--- a/sound/soc/amd/acp-pcm-dma.c ++++ b/sound/soc/amd/acp-pcm-dma.c +@@ -691,12 +691,10 @@ static irqreturn_t dma_irq_handler(int irq, void *arg) + { + u16 dscr_idx; + u32 intr_flag, ext_intr_status; +- struct audio_drv_data *irq_data; ++ struct audio_drv_data *irq_data = arg; + void __iomem *acp_mmio; +- struct device *dev = arg; + bool valid_irq = false; + +- irq_data = dev_get_drvdata(dev); + acp_mmio = irq_data->acp_mmio; + + ext_intr_status = acp_reg_read(acp_mmio, mmACP_EXTERNAL_INTR_STAT); +@@ -1291,10 +1289,10 @@ static int acp_audio_probe(struct platform_device *pdev) + + irq = platform_get_irq(pdev, 0); + if (irq < 0) +- return -ENODEV; ++ return irq; + + status = devm_request_irq(&pdev->dev, irq, dma_irq_handler, +- 0, "ACP_IRQ", &pdev->dev); ++ 0, "ACP_IRQ", audio_drv_data); + if (status) { + dev_err(&pdev->dev, "ACP IRQ request failed\n"); + return status; +diff --git a/sound/soc/amd/acp/acp-i2s.c b/sound/soc/amd/acp/acp-i2s.c +--- a/sound/soc/amd/acp/acp-i2s.c ++++ b/sound/soc/amd/acp/acp-i2s.c +@@ -686,6 +686,10 @@ static int acp_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_d + return 0; + } + ++static const u64 acp_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops = { + .startup = acp_i2s_startup, + .hw_params = acp_i2s_hwparams, +@@ -693,6 +697,8 @@ const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops = { + .trigger = acp_i2s_trigger, + .set_fmt = acp_i2s_set_fmt, + .set_tdm_slot = acp_i2s_set_tdm_slot, ++ .auto_selectable_formats = &acp_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + EXPORT_SYMBOL_NS_GPL(asoc_acp_cpu_dai_ops, "SND_SOC_ACP_COMMON"); + +diff --git a/sound/soc/amd/acp/acp-mach-common.c b/sound/soc/amd/acp/acp-mach-common.c +--- a/sound/soc/amd/acp/acp-mach-common.c ++++ b/sound/soc/amd/acp/acp-mach-common.c +@@ -938,15 +938,10 @@ static int acp_max98388_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai = + snd_soc_card_get_codec_dai(card, + MAX98388_CODEC_DAI); +- int ret; + +- ret = snd_soc_dai_set_fmt(codec_dai, +- SND_SOC_DAIFMT_CBC_CFC | SND_SOC_DAIFMT_I2S | +- SND_SOC_DAIFMT_NB_NF); +- if (ret < 0) +- return ret; +- +- return ret; ++ return snd_soc_dai_set_fmt(codec_dai, ++ SND_SOC_DAIFMT_CBC_CFC | SND_SOC_DAIFMT_I2S | ++ SND_SOC_DAIFMT_NB_NF); + } + + static const struct snd_soc_ops acp_max98388_ops = { +diff --git a/sound/soc/amd/acp/acp-pci.c b/sound/soc/amd/acp/acp-pci.c +--- a/sound/soc/amd/acp/acp-pci.c ++++ b/sound/soc/amd/acp/acp-pci.c +@@ -118,17 +118,10 @@ static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id + if (!chip) + return -ENOMEM; + +- if (pci_enable_device(pci)) ++ if (pcim_enable_device(pci)) + return dev_err_probe(&pci->dev, -ENODEV, + "pci_enable_device failed\n"); + +- ret = pci_request_regions(pci, "AMD ACP3x audio"); +- if (ret < 0) { +- dev_err(&pci->dev, "pci_request_regions failed\n"); +- ret = -ENOMEM; +- goto disable_pci; +- } +- + pci_set_master(pci); + + chip->acp_rev = pci->revision; +@@ -161,24 +154,21 @@ static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id + break; + default: + dev_err(dev, "Unsupported device revision:0x%x\n", pci->revision); +- ret = -EINVAL; +- goto release_regions; ++ return -EINVAL; + } + chip->flag = flag; + + addr = pci_resource_start(pci, 0); +- chip->base = devm_ioremap(&pci->dev, addr, pci_resource_len(pci, 0)); +- if (!chip->base) { +- ret = -ENOMEM; +- goto release_regions; +- } ++ chip->base = pcim_iomap_region(pci, 0, "AMD ACP3x audio"); ++ if (IS_ERR(chip->base)) ++ return PTR_ERR(chip->base); + + chip->addr = addr; + + chip->acp_hw_ops_init(chip); + ret = acp_hw_init(chip); + if (ret) +- goto release_regions; ++ goto de_init; + + ret = devm_request_irq(dev, pci->irq, irq_handler, + IRQF_SHARED, "ACP_I2S_IRQ", chip); +@@ -214,10 +204,6 @@ static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id + + de_init: + acp_hw_deinit(chip); +-release_regions: +- pci_release_regions(pci); +-disable_pci: +- pci_disable_device(pci); + + return ret; + }; +diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c +--- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c ++++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c +@@ -432,7 +432,7 @@ static int soc_card_dai_links_create(struct snd_soc_card *card) + if (!soc_aux) + return -ENOMEM; + +- ret = asoc_sdw_parse_sdw_endpoints(card, soc_aux, soc_dais, soc_ends, &num_confs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, soc_aux, soc_dais, soc_ends, &num_confs); + if (ret < 0) + return ret; + +@@ -521,15 +521,15 @@ static int mc_probe(struct platform_device *pdev) + dmi_check_system(soc_sdw_quirk_table); + + if (quirk_override != -1) { +- dev_info(card->dev, "Overriding quirk 0x%lx => 0x%x\n", ++ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + soc_sdw_quirk, quirk_override); + soc_sdw_quirk = quirk_override; + } + +- log_quirks(card->dev); ++ log_quirks(&pdev->dev); + + ctx->mc_quirk = soc_sdw_quirk; +- dev_dbg(card->dev, "legacy quirk 0x%lx\n", ctx->mc_quirk); ++ dev_dbg(&pdev->dev, "legacy quirk 0x%lx\n", ctx->mc_quirk); + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ + for (i = 0; i < ctx->codec_info_list_count; i++) + codec_info_list[i].amp_num = 0; +@@ -546,12 +546,12 @@ static int mc_probe(struct platform_device *pdev) + for (i = 0; i < ctx->codec_info_list_count; i++) + amp_num += codec_info_list[i].amp_num; + +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + " cfg-amp:%d", amp_num); + if (!card->components) + return -ENOMEM; + if (soc_sdw_quirk & ASOC_SDW_ACP_DMIC) { +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%s mic:acp-dmic cfg-mics:%d", + card->components, + 1); +@@ -560,9 +560,9 @@ static int mc_probe(struct platform_device *pdev) + } + + /* Register the card */ +- ret = devm_snd_soc_register_card(card->dev, card); ++ ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { +- dev_err_probe(card->dev, ret, "snd_soc_register_card failed %d\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed %d\n", ret); + asoc_sdw_mc_dailink_exit_loop(card); + return ret; + } +@@ -580,8 +580,8 @@ static void mc_remove(struct platform_device *pdev) + } + + static const struct platform_device_id mc_id_table[] = { +- { "amd_sdw", }, +- {} ++ { .name = "amd_sdw" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, mc_id_table); + +diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c +--- a/sound/soc/amd/acp/acp-sdw-sof-mach.c ++++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c +@@ -303,7 +303,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) + if (!sof_aux) + return -ENOMEM; + +- ret = asoc_sdw_parse_sdw_endpoints(card, sof_aux, sof_dais, sof_ends, &num_devs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_devs); + if (ret < 0) + return ret; + +@@ -388,12 +388,12 @@ static int mc_probe(struct platform_device *pdev) + dmi_check_system(sof_sdw_quirk_table); + + if (quirk_override != -1) { +- dev_info(card->dev, "Overriding quirk 0x%lx => 0x%x\n", ++ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + sof_sdw_quirk, quirk_override); + sof_sdw_quirk = quirk_override; + } + +- log_quirks(card->dev); ++ log_quirks(&pdev->dev); + + ctx->mc_quirk = sof_sdw_quirk; + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ +@@ -412,15 +412,15 @@ static int mc_probe(struct platform_device *pdev) + for (i = 0; i < ctx->codec_info_list_count; i++) + amp_num += codec_info_list[i].amp_num; + +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + " cfg-amp:%d", amp_num); + if (!card->components) + return -ENOMEM; + + /* Register the card */ +- ret = devm_snd_soc_register_card(card->dev, card); ++ ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { +- dev_err_probe(card->dev, ret, "snd_soc_register_card failed %d\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed %d\n", ret); + asoc_sdw_mc_dailink_exit_loop(card); + return ret; + } +@@ -438,8 +438,8 @@ static void mc_remove(struct platform_device *pdev) + } + + static const struct platform_device_id mc_id_table[] = { +- { "amd_sof_sdw", }, +- {} ++ { .name = "amd_sof_sdw" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, mc_id_table); + +diff --git a/sound/soc/amd/acp/acp-sof-mach.c b/sound/soc/amd/acp/acp-sof-mach.c +--- a/sound/soc/amd/acp/acp-sof-mach.c ++++ b/sound/soc/amd/acp/acp-sof-mach.c +@@ -128,31 +128,31 @@ static int acp_sof_probe(struct platform_device *pdev) + static const struct platform_device_id board_ids[] = { + { + .name = "rt5682-rt1019", +- .driver_data = (kernel_ulong_t)&sof_rt5682_rt1019_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682_rt1019_data, + }, + { + .name = "rt5682-max", +- .driver_data = (kernel_ulong_t)&sof_rt5682_max_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682_max_data, + }, + { + .name = "rt5682s-max", +- .driver_data = (kernel_ulong_t)&sof_rt5682s_max_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682s_max_data, + }, + { + .name = "rt5682s-rt1019", +- .driver_data = (kernel_ulong_t)&sof_rt5682s_rt1019_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682s_rt1019_data, + }, + { + .name = "nau8825-max", +- .driver_data = (kernel_ulong_t)&sof_nau8825_data ++ .driver_data = (kernel_ulong_t)&sof_nau8825_data, + }, + { + .name = "rt5682s-hs-rt1019", +- .driver_data = (kernel_ulong_t)&sof_rt5682s_hs_rt1019_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682s_hs_rt1019_data, + }, + { + .name = "nau8821-max", +- .driver_data = (kernel_ulong_t)&sof_nau8821_max98388_data ++ .driver_data = (kernel_ulong_t)&sof_nau8821_max98388_data, + }, + { } + }; +diff --git a/sound/soc/amd/acp/amd-acp70-acpi-match.c b/sound/soc/amd/acp/amd-acp70-acpi-match.c +--- a/sound/soc/amd/acp/amd-acp70-acpi-match.c ++++ b/sound/soc/amd/acp/amd-acp70-acpi-match.c +@@ -659,6 +659,56 @@ static const struct snd_soc_acpi_link_adr acp70_rt721_l1u0_tas2783x2_l1u8b[] = { + {} + }; + ++static const struct snd_soc_acpi_adr_device rt712_vb_l1u0_adr[] = { ++ { ++ .adr = 0x000130025D071201ull, ++ /* ++ * On this platform speakers are provided by two TAS2783 amps, ++ * so the AIF2 amp path is left unused: jack + DMIC only. ++ */ ++ .num_endpoints = ARRAY_SIZE(jack_dmic_endpoints), ++ .endpoints = jack_dmic_endpoints, ++ .name_prefix = "rt712" ++ } ++}; ++ ++/* ++ * Unique ID 0xC drives the left speaker and 0x9 the right one. The order of the ++ * entries matters as much as the endpoints: asoc_sdw_parse_sdw_endpoints() ++ * appends them to the dailink in array order and sdw_compute_slave_ports() ++ * hands out payload block offsets in that same order, so the first entry is the ++ * one that receives channel 0. ++ */ ++static const struct snd_soc_acpi_adr_device tas2783x2_l0u9c_adr[] = { ++ { ++ .adr = 0x00003C0102000001ull, ++ .num_endpoints = 1, ++ .endpoints = &spk_l_endpoint, ++ .name_prefix = "tas2783-1", ++ }, ++ { ++ .adr = 0x0000390102000001ull, ++ .num_endpoints = 1, ++ .endpoints = &spk_r_endpoint, ++ .name_prefix = "tas2783-2", ++ }, ++}; ++ ++/* HP OmniBook X Flip 14-kc0xxx (board 8EA1) */ ++static const struct snd_soc_acpi_link_adr acp70_tas2783x2_l0u9c_rt712_vb_l1u0[] = { ++ { ++ .mask = BIT(0), ++ .num_adr = ARRAY_SIZE(tas2783x2_l0u9c_adr), ++ .adr_d = tas2783x2_l0u9c_adr, ++ }, ++ { ++ .mask = BIT(1), ++ .num_adr = ARRAY_SIZE(rt712_vb_l1u0_adr), ++ .adr_d = rt712_vb_l1u0_adr, ++ }, ++ {} ++}; ++ + static const struct snd_soc_acpi_endpoint rt721_endpoints[] = { + { /* Jack Playback/Capture Endpoint (AIF1) */ + .num = 0, +@@ -704,6 +754,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[] = { + .links = acp70_tas2783_2, + .drv_name = "amd_sdw", + }, ++ { ++ .link_mask = BIT(0) | BIT(1), ++ .links = acp70_tas2783x2_l0u9c_rt712_vb_l1u0, ++ .machine_check = snd_soc_acpi_amd_sdca_is_device_rt712_vb, ++ .drv_name = "amd_sdw", ++ }, + { + .link_mask = BIT(0) | BIT(1), + .links = acp70_rt1320_l0_rt722_l1, +diff --git a/sound/soc/amd/mach-config.h b/sound/soc/amd/mach-config.h +--- a/sound/soc/amd/mach-config.h ++++ b/sound/soc/amd/mach-config.h +@@ -28,6 +28,7 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[]; + extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[]; + extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[]; + extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_sdw_machines[]; ++extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[]; + + struct config_entry { + u32 flags; +diff --git a/sound/soc/amd/raven/acp3x-i2s.c b/sound/soc/amd/raven/acp3x-i2s.c +--- a/sound/soc/amd/raven/acp3x-i2s.c ++++ b/sound/soc/amd/raven/acp3x-i2s.c +@@ -250,11 +250,17 @@ static int acp3x_i2s_trigger(struct snd_pcm_substream *substream, + return ret; + } + ++static const u64 acp3x_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + static const struct snd_soc_dai_ops acp3x_i2s_dai_ops = { + .hw_params = acp3x_i2s_hwparams, + .trigger = acp3x_i2s_trigger, + .set_fmt = acp3x_i2s_set_fmt, + .set_tdm_slot = acp3x_i2s_set_tdm_slot, ++ .auto_selectable_formats = &acp3x_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_component_driver acp3x_dai_component = { +diff --git a/sound/soc/amd/vangogh/acp5x-i2s.c b/sound/soc/amd/vangogh/acp5x-i2s.c +--- a/sound/soc/amd/vangogh/acp5x-i2s.c ++++ b/sound/soc/amd/vangogh/acp5x-i2s.c +@@ -337,11 +337,17 @@ static int acp5x_i2s_trigger(struct snd_pcm_substream *substream, + return ret; + } + ++static const u64 acp5x_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + static const struct snd_soc_dai_ops acp5x_i2s_dai_ops = { + .hw_params = acp5x_i2s_hwparams, + .trigger = acp5x_i2s_trigger, + .set_fmt = acp5x_i2s_set_fmt, + .set_tdm_slot = acp5x_i2s_set_tdm_slot, ++ .auto_selectable_formats = &acp5x_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_component_driver acp5x_dai_component = { +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -795,12 +795,12 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + } + }, + { +- .driver_data = &acp6x_card, +- .matches = { +- DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), +- DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook_ASUSLaptop M6501RR_M6501RR"), +- } +- }, ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook_ASUSLaptop M6501RR_M6501RR"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c +--- a/sound/soc/apple/mca.c ++++ b/sound/soc/apple/mca.c +@@ -133,12 +133,17 @@ struct mca_cluster { + struct clk *clk_parent; + struct dma_chan *dma_chans[SNDRV_PCM_STREAM_LAST + 1]; + +- bool port_started[SNDRV_PCM_STREAM_LAST + 1]; +- int port_driver; /* The cluster driving this cluster's port */ ++ bool clk_provider; ++ ++ bool port_clk_started[SNDRV_PCM_STREAM_LAST + 1]; ++ int port_clk_driver; /* The cluster driving this cluster's port */ + + bool clocks_in_use[SNDRV_PCM_STREAM_LAST + 1]; + struct device_link *pd_link; + ++ /* In case of clock consumer FE */ ++ int syncgen_in_use; ++ + unsigned int bclk_ratio; + + /* Masks etc. picked up via the set_tdm_slot method */ +@@ -157,7 +162,7 @@ struct mca_data { + struct reset_control *rstc; + struct device_link *pd_link; + +- /* Mutex for accessing port_driver of foreign clusters */ ++ /* Mutex for accessing port_clk_driver of foreign clusters */ + struct mutex port_mutex; + + int nclusters; +@@ -210,16 +215,22 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd, + SERDES_STATUS_EN | SERDES_STATUS_RST, + SERDES_STATUS_RST); + /* +- * Experiments suggest that it takes at most ~1 us +- * for the bit to clear, so wait 2 us for good measure. ++ * The SERDES cluster needs a bit of time to reset itself ++ * and settle before we start poking it. This is... slow... + */ +- udelay(2); ++ udelay(25); + WARN_ON(readl_relaxed(cl->base + serdes_unit + REG_SERDES_STATUS) & + SERDES_STATUS_RST); + mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL, + FIELD_PREP(SERDES_CONF_SYNC_SEL, 0)); + mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL, + FIELD_PREP(SERDES_CONF_SYNC_SEL, cl->no + 1)); ++ /* ++ * ADMAC gets started right after this. This delay seems ++ * to be needed for that to be reliable, e.g. ensure the ++ * clock is stable? ++ */ ++ udelay(100); + break; + default: + break; +@@ -256,11 +267,29 @@ static int mca_fe_trigger(struct snd_pcm_substream *substream, int cmd, + return 0; + } + ++static int mca_fe_get_portmask(struct snd_pcm_substream *substream) ++{ ++ struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dpcm *dpcm; ++ int mask = 0; ++ ++ for_each_dpcm_be(fe, substream->stream, dpcm) { ++ int no = mca_dai_to_cluster(snd_soc_rtd_to_cpu(dpcm->be, 0))->no; ++ ++ mask |= 1 << no; ++ } ++ ++ return mask; ++} ++ + static int mca_fe_enable_clocks(struct mca_cluster *cl) + { + struct mca_data *mca = cl->host; + int ret; + ++ if (!cl->clk_provider) ++ return -EINVAL; ++ + ret = clk_prepare_enable(cl->clk_parent); + if (ret) { + dev_err(mca->dev, +@@ -311,7 +340,7 @@ static bool mca_fe_clocks_in_use(struct mca_cluster *cl) + for (i = 0; i < mca->nclusters; i++) { + be_cl = &mca->clusters[i]; + +- if (be_cl->port_driver != cl->no) ++ if (be_cl->port_clk_driver != cl->no) + continue; + + for_each_pcm_streams(stream) { +@@ -323,36 +352,6 @@ static bool mca_fe_clocks_in_use(struct mca_cluster *cl) + return false; + } + +-static int mca_be_prepare(struct snd_pcm_substream *substream, +- struct snd_soc_dai *dai) +-{ +- struct mca_cluster *cl = mca_dai_to_cluster(dai); +- struct mca_data *mca = cl->host; +- struct mca_cluster *fe_cl; +- int ret; +- +- if (cl->port_driver < 0) +- return -EINVAL; +- +- fe_cl = &mca->clusters[cl->port_driver]; +- +- /* +- * Typically the CODECs we are paired with will require clocks +- * to be present at time of unmute with the 'mute_stream' op +- * or at time of DAPM widget power-up. We need to enable clocks +- * here at the latest (frontend prepare would be too late). +- */ +- if (!mca_fe_clocks_in_use(fe_cl)) { +- ret = mca_fe_enable_clocks(fe_cl); +- if (ret < 0) +- return ret; +- } +- +- cl->clocks_in_use[substream->stream] = true; +- +- return 0; +-} +- + static int mca_be_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { +@@ -360,7 +359,7 @@ static int mca_be_hw_free(struct snd_pcm_substream *substream, + struct mca_data *mca = cl->host; + struct mca_cluster *fe_cl; + +- if (cl->port_driver < 0) ++ if (cl->port_clk_driver < 0) + return -EINVAL; + + /* +@@ -368,7 +367,7 @@ static int mca_be_hw_free(struct snd_pcm_substream *substream, + * belong to the same PCM, accesses should have been + * synchronized at ASoC level. + */ +- fe_cl = &mca->clusters[cl->port_driver]; ++ fe_cl = &mca->clusters[cl->port_clk_driver]; + if (!mca_fe_clocks_in_use(fe_cl)) + return 0; /* Nothing to do */ + +@@ -380,6 +379,56 @@ static int mca_be_hw_free(struct snd_pcm_substream *substream, + return 0; + } + ++static int mca_fe_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ struct mca_data *mca = cl->host; ++ ++ if (cl->clk_provider) ++ return 0; ++ ++ /* Turn on the cluster power domain if not already in use */ ++ if (!cl->syncgen_in_use) { ++ int port = ffs(mca_fe_get_portmask(substream)); ++ ++ cl->pd_link = device_link_add(mca->dev, cl->pd_dev, ++ DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | ++ DL_FLAG_RPM_ACTIVE); ++ if (!cl->pd_link) { ++ dev_err(mca->dev, ++ "cluster %d: unable to prop-up power domain\n", cl->no); ++ return -EINVAL; ++ } ++ ++ mca_modify(cl, REG_SYNCGEN_MCLK_SEL, SYNCGEN_MCLK_SEL, BIT(port)); ++ mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, ++ SYNCGEN_STATUS_EN); ++ } ++ cl->syncgen_in_use |= 1 << substream->stream; ++ ++ return 0; ++} ++ ++static int mca_fe_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ ++ if (cl->clk_provider) ++ return 0; ++ ++ cl->syncgen_in_use &= ~(1 << substream->stream); ++ if (cl->syncgen_in_use) ++ return 0; ++ ++ mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, 0); ++ if (cl->pd_link) ++ device_link_del(cl->pd_link); ++ ++ return 0; ++} ++ + static unsigned int mca_crop_mask(unsigned int mask, int nchans) + { + while (hweight32(mask) > nchans) +@@ -390,7 +439,7 @@ static unsigned int mca_crop_mask(unsigned int mask, int nchans) + + static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit, + unsigned int mask, int slots, int nchans, +- int slot_width, bool is_tx, int port) ++ int slot_width, bool is_tx, int portmask) + { + __iomem void *serdes_base = cl->base + serdes_unit; + u32 serdes_conf, serdes_conf_mask; +@@ -449,7 +498,7 @@ static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit, + serdes_base + REG_RX_SERDES_SLOTMASK); + writel_relaxed(~((u32)mca_crop_mask(mask, nchans)), + serdes_base + REG_RX_SERDES_SLOTMASK + 0x4); +- writel_relaxed(1 << port, ++ writel_relaxed(portmask, + serdes_base + REG_RX_SERDES_PORT); + } + +@@ -505,9 +554,18 @@ static int mca_fe_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + u32 serdes_conf = 0; + u32 bitstart; + +- if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != +- SND_SOC_DAIFMT_BP_FP) ++ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { ++ case SND_SOC_DAIFMT_BP_FP: ++ cl->clk_provider = true; ++ break; ++ ++ case SND_SOC_DAIFMT_BC_FC: ++ cl->clk_provider = false; ++ break; ++ ++ default: + goto err; ++ } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: +@@ -564,24 +622,6 @@ static int mca_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) + return 0; + } + +-static int mca_fe_get_port(struct snd_pcm_substream *substream) +-{ +- struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); +- struct snd_soc_pcm_runtime *be; +- struct snd_soc_dpcm *dpcm; +- +- be = NULL; +- for_each_dpcm_be(fe, substream->stream, dpcm) { +- be = dpcm->be; +- break; +- } +- +- if (!be) +- return -EINVAL; +- +- return mca_dai_to_cluster(snd_soc_rtd_to_cpu(be, 0))->no; +-} +- + static int mca_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +@@ -595,7 +635,7 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, + unsigned long bclk_ratio; + unsigned int tdm_slots, tdm_slot_width, tdm_mask; + u32 regval, pad; +- int ret, port, nchans_ceiled; ++ int ret, portmask, nchans_ceiled; + + if (!cl->tdm_slot_width) { + /* +@@ -644,13 +684,13 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, + tdm_mask = (1 << tdm_slots) - 1; + } + +- port = mca_fe_get_port(substream); +- if (port < 0) +- return port; ++ portmask = mca_fe_get_portmask(substream); ++ if (!portmask) ++ return -EINVAL; + + ret = mca_configure_serdes(cl, is_tx ? CLUSTER_TX_OFF : CLUSTER_RX_OFF, + tdm_mask, tdm_slots, params_channels(params), +- tdm_slot_width, is_tx, port); ++ tdm_slot_width, is_tx, portmask); + if (ret) + return ret; + +@@ -699,6 +739,19 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 mca_fe_selectable_formats[] = ++{ ++ /* pattern 1 */ ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF, ++ ++ /* pattern 2 */ ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF ++}; ++ + static const struct snd_soc_dai_ops mca_fe_ops = { + .startup = mca_fe_startup, + .set_fmt = mca_fe_set_fmt, +@@ -706,66 +759,128 @@ static const struct snd_soc_dai_ops mca_fe_ops = { + .set_tdm_slot = mca_fe_set_tdm_slot, + .hw_params = mca_fe_hw_params, + .trigger = mca_fe_trigger, ++ .prepare = mca_fe_prepare, ++ .hw_free = mca_fe_hw_free, ++ .auto_selectable_formats = mca_fe_selectable_formats, ++ .num_auto_selectable_formats = ARRAY_SIZE(mca_fe_selectable_formats), + }; + +-static bool mca_be_started(struct mca_cluster *cl) ++/* ++ * Is there a FE attached which will be feeding this port's clocks? ++ */ ++static bool mca_be_clk_started(struct mca_cluster *cl) + { + int stream; + + for_each_pcm_streams(stream) +- if (cl->port_started[stream]) ++ if (cl->port_clk_started[stream]) + return true; + return false; + } + ++static struct snd_soc_pcm_runtime *mca_be_get_fe(struct snd_soc_pcm_runtime *be, ++ int stream) ++{ ++ struct snd_soc_pcm_runtime *fe = NULL; ++ struct snd_soc_dpcm *dpcm; ++ ++ for_each_dpcm_fe(be, stream, dpcm) { ++ if (fe && dpcm->fe != fe) { ++ dev_err(be->dev, "many FE per one BE unsupported\n"); ++ return NULL; ++ } ++ ++ fe = dpcm->fe; ++ } ++ ++ return fe; ++} ++ ++static int mca_be_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); ++ struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ struct mca_data *mca = cl->host; ++ struct mca_cluster *fe_cl, *fe_clk_cl; ++ int ret; ++ ++ fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); ++ ++ if (!fe_cl->clk_provider) ++ return 0; ++ ++ if (cl->port_clk_driver < 0) ++ return 0; ++ ++ fe_clk_cl = &mca->clusters[cl->port_clk_driver]; ++ ++ /* ++ * Typically the CODECs we are paired with will require clocks ++ * to be present at time of unmute with the 'mute_stream' op ++ * or at time of DAPM widget power-up. We need to enable clocks ++ * here at the latest (frontend prepare would be too late). ++ */ ++ if (!mca_fe_clocks_in_use(fe_clk_cl)) { ++ ret = mca_fe_enable_clocks(fe_clk_cl); ++ if (ret < 0) ++ return ret; ++ } ++ ++ cl->clocks_in_use[substream->stream] = true; ++ ++ return 0; ++} ++ + static int mca_be_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { + struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); +- struct snd_soc_pcm_runtime *fe; ++ struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); + struct mca_cluster *cl = mca_dai_to_cluster(dai); + struct mca_cluster *fe_cl; + struct mca_data *mca = cl->host; +- struct snd_soc_dpcm *dpcm; +- +- fe = NULL; +- +- for_each_dpcm_fe(be, substream->stream, dpcm) { +- if (fe && dpcm->fe != fe) { +- dev_err(mca->dev, "many FE per one BE unsupported\n"); +- return -EINVAL; +- } +- +- fe = dpcm->fe; +- } + + if (!fe) + return -EINVAL; +- + fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); + +- if (mca_be_started(cl)) { ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ writel_relaxed(PORT_DATA_SEL_TXA(fe_cl->no), ++ cl->base + REG_PORT_DATA_SEL); ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA, ++ PORT_ENABLES_TX_DATA); ++ } ++ ++ if (!fe_cl->clk_provider) ++ return 0; ++ ++ if (mca_be_clk_started(cl)) { + /* + * Port is already started in the other direction. + * Make sure there isn't a conflict with another cluster +- * driving the port. ++ * driving the port clocks. + */ +- if (cl->port_driver != fe_cl->no) ++ if (cl->port_clk_driver != fe_cl->no) + return -EINVAL; + +- cl->port_started[substream->stream] = true; ++ cl->port_clk_started[substream->stream] = true; + return 0; + } + +- writel_relaxed(PORT_ENABLES_CLOCKS | PORT_ENABLES_TX_DATA, +- cl->base + REG_PORT_ENABLES); + writel_relaxed(FIELD_PREP(PORT_CLOCK_SEL, fe_cl->no + 1), + cl->base + REG_PORT_CLOCK_SEL); ++ + writel_relaxed(PORT_DATA_SEL_TXA(fe_cl->no), + cl->base + REG_PORT_DATA_SEL); ++ ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_CLOCKS, ++ PORT_ENABLES_CLOCKS); ++ + scoped_guard(mutex, &mca->port_mutex) +- cl->port_driver = fe_cl->no; +- cl->port_started[substream->stream] = true; ++ cl->port_clk_driver = fe_cl->no; ++ cl->port_clk_started[substream->stream] = true; + + return 0; + } +@@ -773,20 +888,34 @@ static int mca_be_startup(struct snd_pcm_substream *substream, + static void mca_be_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { ++ struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); + struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ struct mca_cluster *fe_cl; + struct mca_data *mca = cl->host; + +- cl->port_started[substream->stream] = false; ++ if (!fe) ++ return; ++ fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); + +- if (!mca_be_started(cl)) { ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA, 0); ++ writel_relaxed(0, cl->base + REG_PORT_DATA_SEL); ++ } ++ ++ if (!fe_cl->clk_provider) ++ return; ++ ++ cl->port_clk_started[substream->stream] = false; ++ if (!mca_be_clk_started(cl)) { + /* + * Were we the last direction to shutdown? +- * Turn off the lights. ++ * Turn off the lights (clocks). + */ +- writel_relaxed(0, cl->base + REG_PORT_ENABLES); +- writel_relaxed(0, cl->base + REG_PORT_DATA_SEL); ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_CLOCKS, 0); ++ writel_relaxed(0, cl->base + REG_PORT_CLOCK_SEL); + scoped_guard(mutex, &mca->port_mutex) +- cl->port_driver = -1; ++ cl->port_clk_driver = -1; + } + } + +@@ -1092,7 +1221,7 @@ static int apple_mca_probe(struct platform_device *pdev) + cl->host = mca; + cl->no = i; + cl->base = base + CLUSTER_STRIDE * i; +- cl->port_driver = -1; ++ cl->port_clk_driver = -1; + cl->clk_parent = of_clk_get(pdev->dev.of_node, i); + if (IS_ERR(cl->clk_parent)) { + dev_err(&pdev->dev, "unable to obtain clock %d: %ld\n", +diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c +--- a/sound/soc/atmel/atmel-i2s.c ++++ b/sound/soc/atmel/atmel-i2s.c +@@ -540,12 +540,16 @@ static int atmel_i2s_dai_probe(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 atmel_i2s_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_I2S; ++ + static const struct snd_soc_dai_ops atmel_i2s_dai_ops = { + .probe = atmel_i2s_dai_probe, + .prepare = atmel_i2s_prepare, + .trigger = atmel_i2s_trigger, + .hw_params = atmel_i2s_hw_params, + .set_fmt = atmel_i2s_set_dai_fmt, ++ .auto_selectable_formats = &atmel_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver atmel_i2s_dai = { +diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c +--- a/sound/soc/atmel/atmel_ssc_dai.c ++++ b/sound/soc/atmel/atmel_ssc_dai.c +@@ -825,6 +825,11 @@ static int atmel_ssc_resume(struct snd_soc_component *component) + #define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + ++static const u64 atmel_ssc_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + static const struct snd_soc_dai_ops atmel_ssc_dai_ops = { + .startup = atmel_ssc_startup, + .shutdown = atmel_ssc_shutdown, +@@ -833,6 +838,8 @@ static const struct snd_soc_dai_ops atmel_ssc_dai_ops = { + .hw_params = atmel_ssc_hw_params, + .set_fmt = atmel_ssc_set_dai_fmt, + .set_clkdiv = atmel_ssc_set_dai_clkdiv, ++ .auto_selectable_formats = &atmel_ssc_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver atmel_ssc_dai = { +diff --git a/sound/soc/atmel/atmel_wm8904.c b/sound/soc/atmel/atmel_wm8904.c +--- a/sound/soc/atmel/atmel_wm8904.c ++++ b/sound/soc/atmel/atmel_wm8904.c +@@ -82,32 +82,33 @@ static struct snd_soc_card atmel_asoc_wm8904_card = { + + static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev) + { +- struct device_node *np = pdev->dev.of_node; ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node; + struct device_node *codec_np, *cpu_np; + struct snd_soc_card *card = &atmel_asoc_wm8904_card; + struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; + int ret; + + if (!np) { +- dev_err(&pdev->dev, "only device tree supported\n"); ++ dev_err(dev, "only device tree supported\n"); + return -EINVAL; + } + + ret = snd_soc_of_parse_card_name(card, "atmel,model"); + if (ret) { +- dev_err(&pdev->dev, "failed to parse card name\n"); ++ dev_err(dev, "failed to parse card name\n"); + return ret; + } + + ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing"); + if (ret) { +- dev_err(&pdev->dev, "failed to parse audio routing\n"); ++ dev_err(dev, "failed to parse audio routing\n"); + return ret; + } + + cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0); + if (!cpu_np) { +- dev_err(&pdev->dev, "failed to get dai and pcm info\n"); ++ dev_err(dev, "failed to get dai and pcm info\n"); + ret = -EINVAL; + return ret; + } +@@ -117,7 +118,7 @@ static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev) + + codec_np = of_parse_phandle(np, "atmel,audio-codec", 0); + if (!codec_np) { +- dev_err(&pdev->dev, "failed to get codec info\n"); ++ dev_err(dev, "failed to get codec info\n"); + ret = -EINVAL; + return ret; + } +diff --git a/sound/soc/atmel/mchp-i2s-mcc.c b/sound/soc/atmel/mchp-i2s-mcc.c +--- a/sound/soc/atmel/mchp-i2s-mcc.c ++++ b/sound/soc/atmel/mchp-i2s-mcc.c +@@ -912,6 +912,13 @@ static int mchp_i2s_mcc_dai_probe(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 mchp_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_GATED | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = { + .probe = mchp_i2s_mcc_dai_probe, + .set_sysclk = mchp_i2s_mcc_set_sysclk, +@@ -922,6 +929,8 @@ static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = { + .hw_free = mchp_i2s_mcc_hw_free, + .set_fmt = mchp_i2s_mcc_set_dai_fmt, + .set_tdm_slot = mchp_i2s_mcc_set_dai_tdm_slot, ++ .auto_selectable_formats = &mchp_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define MCHP_I2SMCC_RATES SNDRV_PCM_RATE_8000_192000 +diff --git a/sound/soc/atmel/mchp-pdmc.c b/sound/soc/atmel/mchp-pdmc.c +--- a/sound/soc/atmel/mchp-pdmc.c ++++ b/sound/soc/atmel/mchp-pdmc.c +@@ -741,6 +741,8 @@ static int mchp_pdmc_pcm_new(struct snd_soc_pcm_runtime *rtd, + return ret; + } + ++static const u64 mchp_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_PDM; ++ + static const struct snd_soc_dai_ops mchp_pdmc_dai_ops = { + .probe = mchp_pdmc_dai_probe, + .set_fmt = mchp_pdmc_set_fmt, +@@ -748,6 +750,8 @@ static const struct snd_soc_dai_ops mchp_pdmc_dai_ops = { + .hw_params = mchp_pdmc_hw_params, + .trigger = mchp_pdmc_trigger, + .pcm_new = &mchp_pdmc_pcm_new, ++ .auto_selectable_formats = &mchp_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver mchp_pdmc_dai = { +diff --git a/sound/soc/au1x/db1200.c b/sound/soc/au1x/db1200.c +--- a/sound/soc/au1x/db1200.c ++++ b/sound/soc/au1x/db1200.c +@@ -42,7 +42,7 @@ static const struct platform_device_id db1200_pids[] = { + .name = "db1550-i2s", + .driver_data = 5, + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, db1200_pids); + +diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c +--- a/sound/soc/au1x/i2sc.c ++++ b/sound/soc/au1x/i2sc.c +@@ -202,11 +202,22 @@ static int au1xi2s_startup(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 au1xi2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops au1xi2s_dai_ops = { + .startup = au1xi2s_startup, + .trigger = au1xi2s_trigger, + .hw_params = au1xi2s_hw_params, + .set_fmt = au1xi2s_set_fmt, ++ .auto_selectable_formats = &au1xi2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver au1xi2s_dai_driver = { +diff --git a/sound/soc/au1x/psc-ac97.c b/sound/soc/au1x/psc-ac97.c +--- a/sound/soc/au1x/psc-ac97.c ++++ b/sound/soc/au1x/psc-ac97.c +@@ -210,9 +210,7 @@ static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream, + { + struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai); + unsigned long r, ro, stat; +- int chans, t, stype = substream->stream; +- +- chans = params_channels(params); ++ int t, stype = substream->stream; + + r = ro = __raw_readl(AC97_CFG(pscdata)); + stat = __raw_readl(AC97_STAT(pscdata)); +diff --git a/sound/soc/au1x/psc-i2s.c b/sound/soc/au1x/psc-i2s.c +--- a/sound/soc/au1x/psc-i2s.c ++++ b/sound/soc/au1x/psc-i2s.c +@@ -262,11 +262,22 @@ static int au1xpsc_i2s_startup(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 au1xpsc_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops au1xpsc_i2s_dai_ops = { + .startup = au1xpsc_i2s_startup, + .trigger = au1xpsc_i2s_trigger, + .hw_params = au1xpsc_i2s_hw_params, + .set_fmt = au1xpsc_i2s_set_fmt, ++ .auto_selectable_formats = &au1xpsc_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_driver au1xpsc_i2s_dai_template = { +diff --git a/sound/soc/bcm/bcm2835-i2s.c b/sound/soc/bcm/bcm2835-i2s.c +--- a/sound/soc/bcm/bcm2835-i2s.c ++++ b/sound/soc/bcm/bcm2835-i2s.c +@@ -748,6 +748,17 @@ static int bcm2835_i2s_dai_probe(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 bcm2835_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = { + .probe = bcm2835_i2s_dai_probe, + .startup = bcm2835_i2s_startup, +@@ -758,6 +769,8 @@ static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = { + .set_fmt = bcm2835_i2s_set_dai_fmt, + .set_bclk_ratio = bcm2835_i2s_set_dai_bclk_ratio, + .set_tdm_slot = bcm2835_i2s_set_dai_tdm_slot, ++ .auto_selectable_formats = &bcm2835_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver bcm2835_i2s_dai = { +diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c +--- a/sound/soc/bcm/cygnus-ssp.c ++++ b/sound/soc/bcm/cygnus-ssp.c +@@ -1133,6 +1133,11 @@ static int cygnus_ssp_resume(struct snd_soc_component *component) + #define cygnus_ssp_resume NULL + #endif + ++static const u64 cygnus_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B; ++ + static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { + .startup = cygnus_ssp_startup, + .shutdown = cygnus_ssp_shutdown, +@@ -1141,6 +1146,8 @@ static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { + .set_fmt = cygnus_ssp_set_fmt, + .set_sysclk = cygnus_ssp_set_sysclk, + .set_tdm_slot = cygnus_set_dai_tdm_slot, ++ .auto_selectable_formats = &cygnus_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_ops cygnus_spdif_dai_ops = { +diff --git a/sound/soc/cirrus/ep93xx-i2s.c b/sound/soc/cirrus/ep93xx-i2s.c +--- a/sound/soc/cirrus/ep93xx-i2s.c ++++ b/sound/soc/cirrus/ep93xx-i2s.c +@@ -401,6 +401,15 @@ static int ep93xx_i2s_resume(struct snd_soc_component *component) + #define ep93xx_i2s_resume NULL + #endif + ++static const u64 ep93xx_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = { + .probe = ep93xx_i2s_dai_probe, + .startup = ep93xx_i2s_startup, +@@ -408,6 +417,8 @@ static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = { + .hw_params = ep93xx_i2s_hw_params, + .set_sysclk = ep93xx_i2s_set_sysclk, + .set_fmt = ep93xx_i2s_set_dai_fmt, ++ .auto_selectable_formats = &ep93xx_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define EP93XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -238,6 +238,7 @@ config SND_SOC_ALL_CODECS + imply SND_SOC_RT715_SDCA_SDW + imply SND_SOC_RT721_SDCA_SDW + imply SND_SOC_RT722_SDCA_SDW ++ imply SND_SOC_RT766_SDCA_SDW + imply SND_SOC_RT1308_SDW + imply SND_SOC_RT1316_SDW + imply SND_SOC_RT1318 +@@ -367,6 +368,7 @@ config SND_SOC_ALL_CODECS + imply SND_SOC_WSA881X + imply SND_SOC_WSA883X + imply SND_SOC_WSA884X ++ imply SND_SOC_WSA885X + imply SND_SOC_ZL38060 + help + Normally ASoC codec drivers are only built if a machine driver which +@@ -615,8 +617,8 @@ config SND_SOC_AK4613 + depends on I2C + + config SND_SOC_AK4619 +- tristate "AKM AK4619 CODEC" +- depends on I2C ++ tristate "AKM AK4619 CODEC" ++ depends on I2C + + config SND_SOC_AK4642 + tristate "AKM AK4642 CODEC" +@@ -726,6 +728,10 @@ config SND_SOC_AW87390 + sound quality, which is a new high efficiency, low + noise, constant large volume, 6th Smart K audio amplifier. + ++config SND_SOC_AW88399_LIB ++ tristate ++ select SND_SOC_AW88395_LIB ++ + config SND_SOC_AW88399 + tristate "Soc Audio for awinic aw88399" + depends on I2C +@@ -733,6 +739,7 @@ config SND_SOC_AW88399 + select REGMAP_I2C + select GPIOLIB + select SND_SOC_AW88395_LIB ++ select SND_SOC_AW88399_LIB + help + This option enables support for aw88399 Smart PA. + The awinic AW88399 is an I2S/TDM input, high efficiency +@@ -889,6 +896,7 @@ config SND_SOC_CS35L56_SDW + tristate "Cirrus Logic CS35L56 CODEC (SDW)" + depends on SOUNDWIRE + select REGMAP_SOUNDWIRE ++ select IRQ_DOMAIN + select SND_SOC_CS35L56 + select SND_SOC_CS35L56_SHARED + help +@@ -1201,16 +1209,16 @@ config SND_SOC_JZ4725B_CODEC + will be called snd-soc-jz4725b-codec. + + config SND_SOC_JZ4760_CODEC +- depends on MACH_INGENIC || COMPILE_TEST +- depends on OF +- select REGMAP +- tristate "Ingenic JZ4760 internal CODEC" +- help +- Enable support for the internal CODEC found in the JZ4760 SoC +- from Ingenic. ++ depends on MACH_INGENIC || COMPILE_TEST ++ depends on OF ++ select REGMAP ++ tristate "Ingenic JZ4760 internal CODEC" ++ help ++ Enable support for the internal CODEC found in the JZ4760 SoC ++ from Ingenic. + +- This driver can also be built as a module. If so, the module +- will be called snd-soc-jz4760-codec. ++ This driver can also be built as a module. If so, the module ++ will be called snd-soc-jz4760-codec. + + config SND_SOC_JZ4770_CODEC + depends on MACH_INGENIC || COMPILE_TEST +@@ -1888,7 +1896,7 @@ config SND_SOC_RT5670 + depends on I2C + + config SND_SOC_RT5677 +- tristate ++ tristate "Realtek RT5677 Codec" + depends on I2C + select REGMAP_I2C + select REGMAP_IRQ +@@ -1978,6 +1986,15 @@ config SND_SOC_RT715_SDCA_SDW + select REGMAP_SOUNDWIRE + select REGMAP_SOUNDWIRE_MBQ + ++config SND_SOC_RT766_SDCA_SDW ++ tristate "Realtek RT766 SDCA Codec - SDW" ++ depends on SOUNDWIRE ++ depends on SND_SOC_SDCA ++ select SND_SOC_SDCA_HID ++ select SND_SOC_SDCA_IRQ ++ select REGMAP_SOUNDWIRE ++ select REGMAP_SOUNDWIRE_MBQ ++ + config SND_SOC_RT9120 + tristate "Richtek RT9120 Stereo Class-D Amplifier" + depends on I2C +@@ -2297,8 +2314,8 @@ config SND_SOC_TLV320ADC3XXX + depends on I2C + depends on GPIOLIB + help +- Enable support for Texas Instruments TLV320ADC3001 and TLV320ADC3101 +- ADCs. ++ Enable support for Texas Instruments TLV320ADC3001 and TLV320ADC3101 ++ ADCs. + + config SND_SOC_TLV320AIC23 + tristate +@@ -2771,6 +2788,16 @@ config SND_SOC_WSA884X + This enables support for Qualcomm WSA8840/WSA8845/WSA8845H Class-D + Smart Speaker Amplifier. + ++config SND_SOC_WSA885X ++ tristate "WSA885X Codec" ++ depends on I2C ++ select REGMAP_I2C ++ help ++ This enables support for Qualcomm WSA885X Stereo Smart Speaker ++ Amplifier. The codec driver programs the amplifier register ++ map and exposes the DAI and mixer controls used by Qualcomm ++ audio machine drivers. ++ + config SND_SOC_ZL38060 + tristate "Microsemi ZL38060 Connected Home Audio Processor" + depends on SPI_MASTER +@@ -2894,28 +2921,32 @@ config SND_SOC_TPA6130A2 + depends on I2C + + config SND_SOC_LPASS_MACRO_COMMON +- tristate ++ tristate + + config SND_SOC_LPASS_WSA_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm WSA Macro in LPASS(Low Power Audio SubSystem)" + + config SND_SOC_LPASS_VA_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm VA Macro in LPASS(Low Power Audio SubSystem)" + + config SND_SOC_LPASS_RX_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm RX Macro in LPASS(Low Power Audio SubSystem)" + + config SND_SOC_LPASS_TX_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm TX Macro in LPASS(Low Power Audio SubSystem)" +diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile +--- a/sound/soc/codecs/Makefile ++++ b/sound/soc/codecs/Makefile +@@ -55,6 +55,7 @@ snd-soc-aw88395-y := aw88395/aw88395.o + snd-soc-aw88166-y := aw88166.o + snd-soc-aw88261-y := aw88261.o + snd-soc-aw88399-y := aw88399.o ++snd-soc-aw88399-lib-y := aw88399-lib.o + snd-soc-bd28623-y := bd28623.o + snd-soc-bt-sco-y := bt-sco.o + snd-soc-chv3-codec-y := chv3-codec.o +@@ -284,6 +285,7 @@ snd-soc-rt715-y := rt715.o rt715-sdw.o + snd-soc-rt715-sdca-y := rt715-sdca.o rt715-sdca-sdw.o + snd-soc-rt721-sdca-y := rt721-sdca.o rt721-sdca-sdw.o + snd-soc-rt722-sdca-y := rt722-sdca.o rt722-sdca-sdw.o ++snd-soc-rt766-sdca-y := rt766-sdca.o rt766-sdca-sdw.o + snd-soc-rt9120-y := rt9120.o + snd-soc-rt9123-y := rt9123.o + snd-soc-rt9123p-y := rt9123p.o +@@ -426,6 +428,7 @@ snd-soc-wm-hubs-y := wm_hubs.o + snd-soc-wsa881x-y := wsa881x.o + snd-soc-wsa883x-y := wsa883x.o + snd-soc-wsa884x-y := wsa884x.o ++snd-soc-wsa885x-y := wsa885x.o + snd-soc-zl38060-y := zl38060.o + # Amp + snd-soc-max9877-y := max9877.o +@@ -496,6 +499,7 @@ obj-$(CONFIG_SND_SOC_AW88395) +=snd-soc-aw88395.o + obj-$(CONFIG_SND_SOC_AW88166) +=snd-soc-aw88166.o + obj-$(CONFIG_SND_SOC_AW88261) +=snd-soc-aw88261.o + obj-$(CONFIG_SND_SOC_AW88399) += snd-soc-aw88399.o ++obj-$(CONFIG_SND_SOC_AW88399_LIB) += snd-soc-aw88399-lib.o + obj-$(CONFIG_SND_SOC_BD28623) += snd-soc-bd28623.o + obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o + obj-$(CONFIG_SND_SOC_CHV3_CODEC) += snd-soc-chv3-codec.o +@@ -724,6 +728,7 @@ obj-$(CONFIG_SND_SOC_RT715) += snd-soc-rt715.o + obj-$(CONFIG_SND_SOC_RT715_SDCA_SDW) += snd-soc-rt715-sdca.o + obj-$(CONFIG_SND_SOC_RT721_SDCA_SDW) += snd-soc-rt721-sdca.o + obj-$(CONFIG_SND_SOC_RT722_SDCA_SDW) += snd-soc-rt722-sdca.o ++obj-$(CONFIG_SND_SOC_RT766_SDCA_SDW) += snd-soc-rt766-sdca.o + obj-$(CONFIG_SND_SOC_RT9120) += snd-soc-rt9120.o + obj-$(CONFIG_SND_SOC_RT9123) += snd-soc-rt9123.o + obj-$(CONFIG_SND_SOC_RT9123P) += snd-soc-rt9123p.o +@@ -876,6 +881,7 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o + obj-$(CONFIG_SND_SOC_WSA881X) += snd-soc-wsa881x.o + obj-$(CONFIG_SND_SOC_WSA883X) += snd-soc-wsa883x.o + obj-$(CONFIG_SND_SOC_WSA884X) += snd-soc-wsa884x.o ++obj-$(CONFIG_SND_SOC_WSA885X) += snd-soc-wsa885x.o + obj-$(CONFIG_SND_SOC_ZL38060) += snd-soc-zl38060.o + + # Amp +diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c +--- a/sound/soc/codecs/ab8500-codec.c ++++ b/sound/soc/codecs/ab8500-codec.c +@@ -14,6 +14,7 @@ + * for ST-Ericsson. + */ + ++#include + #include + #include + #include +@@ -989,9 +990,8 @@ static int sid_status_control_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); + +- mutex_lock(&drvdata->ctrl_lock); ++ guard(mutex)(&drvdata->ctrl_lock); + ucontrol->value.enumerated.item[0] = drvdata->sid_status; +- mutex_unlock(&drvdata->ctrl_lock); + + return 0; + } +@@ -1014,7 +1014,7 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, + return -EIO; + } + +- mutex_lock(&drvdata->ctrl_lock); ++ guard(mutex)(&drvdata->ctrl_lock); + + sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF); + if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { +@@ -1025,7 +1025,8 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, + } else { + status = -EBUSY; + } +- goto out; ++ dev_dbg(component->dev, "%s: Exit\n", __func__); ++ return status; + } + + snd_soc_component_write(component, AB8500_SIDFIRADR, 0); +@@ -1043,9 +1044,6 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, + + drvdata->sid_status = SID_FIR_CONFIGURED; + +-out: +- mutex_unlock(&drvdata->ctrl_lock); +- + dev_dbg(component->dev, "%s: Exit\n", __func__); + + return status; +diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c +--- a/sound/soc/codecs/ad1836.c ++++ b/sound/soc/codecs/ad1836.c +@@ -193,9 +193,15 @@ static int ad1836_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 ad1836_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops ad1836_dai_ops = { + .hw_params = ad1836_hw_params, + .set_fmt = ad1836_set_dai_fmt, ++ .auto_selectable_formats = &ad1836_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define AD183X_DAI(_name, num_dacs, num_adcs) \ +diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c +--- a/sound/soc/codecs/ad193x.c ++++ b/sound/soc/codecs/ad193x.c +@@ -394,6 +394,14 @@ static int ad193x_startup(struct snd_pcm_substream *substream, + &constr); + } + ++static const u64 ad193x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops ad193x_dai_ops = { + .startup = ad193x_startup, + .hw_params = ad193x_hw_params, +@@ -401,6 +409,8 @@ static const struct snd_soc_dai_ops ad193x_dai_ops = { + .set_tdm_slot = ad193x_set_tdm_slot, + .set_sysclk = ad193x_set_dai_sysclk, + .set_fmt = ad193x_set_dai_fmt, ++ .auto_selectable_formats = &ad193x_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/adau7118-hw.c b/sound/soc/codecs/adau7118-hw.c +--- a/sound/soc/codecs/adau7118-hw.c ++++ b/sound/soc/codecs/adau7118-hw.c +@@ -22,7 +22,7 @@ static const struct of_device_id adau7118_of_match[] = { + MODULE_DEVICE_TABLE(of, adau7118_of_match); + + static const struct platform_device_id adau7118_id[] = { +- { .name = "adau7118" }, ++ { .name = "adau7118" }, + { } + }; + MODULE_DEVICE_TABLE(platform, adau7118_id); +diff --git a/sound/soc/codecs/adav80x.c b/sound/soc/codecs/adav80x.c +--- a/sound/soc/codecs/adav80x.c ++++ b/sound/soc/codecs/adav80x.c +@@ -743,11 +743,19 @@ static void adav80x_dai_shutdown(struct snd_pcm_substream *substream, + adav80x->rate = 0; + } + ++static const u64 adav80x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops adav80x_dai_ops = { + .set_fmt = adav80x_set_dai_fmt, + .hw_params = adav80x_hw_params, + .startup = adav80x_dai_startup, + .shutdown = adav80x_dai_shutdown, ++ .auto_selectable_formats = &adav80x_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define ADAV80X_PLAYBACK_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ +diff --git a/sound/soc/codecs/ak4613.c b/sound/soc/codecs/ak4613.c +--- a/sound/soc/codecs/ak4613.c ++++ b/sound/soc/codecs/ak4613.c +@@ -95,6 +95,7 @@ + * see + * AK4613_ENABLE_TDM_TEST + */ ++#include + #include + #include + #include +@@ -384,7 +385,7 @@ static void ak4613_dai_shutdown(struct snd_pcm_substream *substream, + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + struct device *dev = component->dev; + +- mutex_lock(&priv->lock); ++ guard(mutex)(&priv->lock); + priv->cnt--; + if (priv->cnt < 0) { + dev_err(dev, "unexpected counter error\n"); +@@ -392,7 +393,6 @@ static void ak4613_dai_shutdown(struct snd_pcm_substream *substream, + } + if (!priv->cnt) + priv->ctrl1 = 0; +- mutex_unlock(&priv->lock); + } + + static void ak4613_hw_constraints(struct ak4613_priv *priv, +@@ -507,10 +507,9 @@ static int ak4613_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->lock); ++ guard(mutex)(&priv->lock); + ak4613_hw_constraints(priv, substream); + priv->cnt++; +- mutex_unlock(&priv->lock); + + return 0; + } +@@ -599,45 +598,47 @@ static int ak4613_dai_hw_params(struct snd_pcm_substream *substream, + */ + ret = -EINVAL; + +- mutex_lock(&priv->lock); +- if (priv->cnt > 1) { +- /* +- * If it was already working, use current priv->ctrl1 +- */ +- ret = 0; +- } else { +- /* +- * It is not yet working, +- */ +- unsigned int channel = params_channels(params); +- u8 tdm; ++ scoped_guard(mutex, &priv->lock) { ++ if (priv->cnt > 1) { ++ /* ++ * If it was already working, use current priv->ctrl1 ++ */ ++ ret = 0; ++ } else { ++ /* ++ * It is not yet working, ++ */ ++ unsigned int channel = params_channels(params); ++ u8 tdm; + +- /* STEREO or TDM */ +- if (channel == 2) +- tdm = AK4613_CONFIG_MODE_STEREO; +- else +- tdm = AK4613_CONFIG_GET(priv, MODE); ++ /* STEREO or TDM */ ++ if (channel == 2) ++ tdm = AK4613_CONFIG_MODE_STEREO; ++ else ++ tdm = AK4613_CONFIG_GET(priv, MODE); + +- for (i = ARRAY_SIZE(ak4613_iface) - 1; i >= 0; i--) { +- const struct ak4613_interface *iface = ak4613_iface + i; ++ for (i = ARRAY_SIZE(ak4613_iface) - 1; i >= 0; i--) { ++ const struct ak4613_interface *iface = ak4613_iface + i; + +- if ((iface->fmt == fmt) && (iface->width == width)) { +- /* +- * Ctrl1 +- * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | +- * |TDM1|TDM0|DIF2|DIF1|DIF0|ATS1|ATS0|SMUTE| +- * < tdm > < iface->dif > +- */ +- priv->ctrl1 = (tdm << 6) | (iface->dif << 3); +- ret = 0; +- break; ++ if (iface->fmt == fmt && iface->width == width) { ++ /* ++ * Ctrl1 ++ * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | ++ * |TDM1|TDM0|DIF2|DIF1|DIF0|ATS1|ATS0|SMUTE| ++ * < tdm > < iface->dif > ++ */ ++ priv->ctrl1 = (tdm << 6) | (iface->dif << 3); ++ ret = 0; ++ break; ++ } + } + } + } +- mutex_unlock(&priv->lock); + +- if (ret < 0) +- goto hw_params_end; ++ if (ret < 0) { ++ dev_warn(dev, "unsupported data width/format combination\n"); ++ return ret; ++ } + + snd_soc_component_update_bits(component, CTRL1, FMT_MASK, priv->ctrl1); + snd_soc_component_update_bits(component, CTRL2, DFS_MASK, ctrl2); +@@ -645,10 +646,6 @@ static int ak4613_dai_hw_params(struct snd_pcm_substream *substream, + snd_soc_component_update_bits(component, ICTRL, ICTRL_MASK, priv->ic); + snd_soc_component_update_bits(component, OCTRL, OCTRL_MASK, priv->oc); + +-hw_params_end: +- if (ret < 0) +- dev_warn(dev, "unsupported data width/format combination\n"); +- + return ret; + } + +diff --git a/sound/soc/codecs/arizona-jack.c b/sound/soc/codecs/arizona-jack.c +--- a/sound/soc/codecs/arizona-jack.c ++++ b/sound/soc/codecs/arizona-jack.c +@@ -5,6 +5,7 @@ + * Copyright (C) 2012-2014 Wolfson Microelectronics plc + */ + ++#include + #include + #include + #include +@@ -707,15 +708,13 @@ static void arizona_micd_timeout_work(struct work_struct *work) + struct arizona_priv, + micd_timeout_work.work); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + + dev_dbg(info->arizona->dev, "MICD timed out, reporting HP\n"); + + info->detecting = false; + + arizona_identify_headphone(info); +- +- mutex_unlock(&info->lock); + } + + static int arizona_micd_adc_read(struct arizona_priv *info) +@@ -921,12 +920,11 @@ static void arizona_micd_detect(struct work_struct *work) + + cancel_delayed_work_sync(&info->micd_timeout_work); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + + /* If the cable was removed while measuring ignore the result */ + if (!(info->jack->status & SND_JACK_MECHANICAL)) { + dev_dbg(arizona->dev, "Ignoring MICDET for removed cable\n"); +- mutex_unlock(&info->lock); + return; + } + +@@ -936,7 +934,6 @@ static void arizona_micd_detect(struct work_struct *work) + arizona_button_reading(info); + + pm_runtime_mark_last_busy(arizona->dev); +- mutex_unlock(&info->lock); + } + + static irqreturn_t arizona_micdet(int irq, void *data) +@@ -948,10 +945,10 @@ static irqreturn_t arizona_micdet(int irq, void *data) + cancel_delayed_work_sync(&info->micd_detect_work); + cancel_delayed_work_sync(&info->micd_timeout_work); + +- mutex_lock(&info->lock); +- if (!info->detecting) +- debounce = 0; +- mutex_unlock(&info->lock); ++ scoped_guard(mutex, &info->lock) { ++ if (!info->detecting) ++ debounce = 0; ++ } + + if (debounce) + queue_delayed_work(system_power_efficient_wq, +@@ -969,9 +966,8 @@ static void arizona_hpdet_work(struct work_struct *work) + struct arizona_priv, + hpdet_work.work); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + arizona_start_hpdet_acc_id(info); +- mutex_unlock(&info->lock); + } + + static int arizona_hpdet_wait(struct arizona_priv *info) +@@ -1018,9 +1014,9 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + cancelled_hp = cancel_delayed_work_sync(&info->hpdet_work); + cancelled_mic = cancel_delayed_work_sync(&info->micd_timeout_work); + +- pm_runtime_get_sync(arizona->dev); ++ guard(pm_runtime_active_auto)(arizona->dev); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + + if (info->micd_clamp) { + mask = ARIZONA_MICD_CLAMP_STS; +@@ -1036,8 +1032,6 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + ret = regmap_read(arizona->regmap, ARIZONA_AOD_IRQ_RAW_STATUS, &val); + if (ret) { + dev_err(arizona->dev, "Failed to read jackdet status: %d\n", ret); +- mutex_unlock(&info->lock); +- pm_runtime_put_autosuspend(arizona->dev); + return IRQ_NONE; + } + +@@ -1056,62 +1050,61 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + &info->micd_timeout_work, + msecs_to_jiffies(micd_timeout)); + } ++ } else { ++ info->last_jackdet = val; + +- goto out; +- } +- info->last_jackdet = val; ++ if (info->last_jackdet == present) { ++ dev_dbg(arizona->dev, "Detected jack\n"); ++ snd_soc_jack_report(info->jack, SND_JACK_MECHANICAL, SND_JACK_MECHANICAL); + +- if (info->last_jackdet == present) { +- dev_dbg(arizona->dev, "Detected jack\n"); +- snd_soc_jack_report(info->jack, SND_JACK_MECHANICAL, SND_JACK_MECHANICAL); ++ info->detecting = true; ++ info->mic = false; ++ info->jack_flips = 0; + +- info->detecting = true; +- info->mic = false; +- info->jack_flips = 0; ++ if (!arizona->pdata.hpdet_acc_id) { ++ arizona_start_mic(info); ++ } else { ++ queue_delayed_work(system_power_efficient_wq, ++ &info->hpdet_work, ++ msecs_to_jiffies(HPDET_DEBOUNCE)); ++ } + +- if (!arizona->pdata.hpdet_acc_id) { +- arizona_start_mic(info); ++ if (info->micd_clamp || !arizona->pdata.jd_invert) ++ regmap_update_bits(arizona->regmap, ++ ARIZONA_JACK_DETECT_DEBOUNCE, ++ ARIZONA_MICD_CLAMP_DB | ++ ARIZONA_JD1_DB, 0); + } else { +- queue_delayed_work(system_power_efficient_wq, +- &info->hpdet_work, +- msecs_to_jiffies(HPDET_DEBOUNCE)); +- } ++ dev_dbg(arizona->dev, "Detected jack removal\n"); ++ ++ arizona_stop_mic(info); ++ ++ info->num_hpdet_res = 0; ++ for (i = 0; i < ARRAY_SIZE(info->hpdet_res); i++) ++ info->hpdet_res[i] = 0; ++ info->mic = false; ++ info->hpdet_done = false; ++ info->hpdet_retried = false; ++ ++ snd_soc_jack_report(info->jack, 0, ++ ARIZONA_JACK_MASK | info->micd_button_mask); ++ ++ /* ++ * If the jack was removed during a headphone detection we ++ * need to wait for the headphone detection to finish, as ++ * it can not be aborted. We don't want to be able to start ++ * a new headphone detection from a fresh insert until this ++ * one is finished. ++ */ ++ arizona_hpdet_wait(info); + +- if (info->micd_clamp || !arizona->pdata.jd_invert) + regmap_update_bits(arizona->regmap, + ARIZONA_JACK_DETECT_DEBOUNCE, +- ARIZONA_MICD_CLAMP_DB | +- ARIZONA_JD1_DB, 0); +- } else { +- dev_dbg(arizona->dev, "Detected jack removal\n"); +- +- arizona_stop_mic(info); +- +- info->num_hpdet_res = 0; +- for (i = 0; i < ARRAY_SIZE(info->hpdet_res); i++) +- info->hpdet_res[i] = 0; +- info->mic = false; +- info->hpdet_done = false; +- info->hpdet_retried = false; +- +- snd_soc_jack_report(info->jack, 0, ARIZONA_JACK_MASK | info->micd_button_mask); +- +- /* +- * If the jack was removed during a headphone detection we +- * need to wait for the headphone detection to finish, as +- * it can not be aborted. We don't want to be able to start +- * a new headphone detection from a fresh insert until this +- * one is finished. +- */ +- arizona_hpdet_wait(info); +- +- regmap_update_bits(arizona->regmap, +- ARIZONA_JACK_DETECT_DEBOUNCE, +- ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB, +- ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB); ++ ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB, ++ ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB); ++ } + } + +-out: + /* Clear trig_sts to make sure DCVDD is not forced up */ + regmap_write(arizona->regmap, ARIZONA_AOD_WKUP_AND_TRIG, + ARIZONA_MICD_CLAMP_FALL_TRIG_STS | +@@ -1119,10 +1112,6 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + ARIZONA_JD1_FALL_TRIG_STS | + ARIZONA_JD1_RISE_TRIG_STS); + +- mutex_unlock(&info->lock); +- +- pm_runtime_put_autosuspend(arizona->dev); +- + return IRQ_HANDLED; + } + +diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c +--- a/sound/soc/codecs/arizona.c ++++ b/sound/soc/codecs/arizona.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -1158,17 +1159,16 @@ int arizona_dvfs_up(struct snd_soc_component *component, unsigned int flags) + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&priv->dvfs_lock); ++ guard(mutex)(&priv->dvfs_lock); + + if (!priv->dvfs_cached && !priv->dvfs_reqs) { + ret = arizona_dvfs_enable(component); + if (ret) +- goto err; ++ return ret; + } + + priv->dvfs_reqs |= flags; +-err: +- mutex_unlock(&priv->dvfs_lock); ++ + return ret; + } + EXPORT_SYMBOL_GPL(arizona_dvfs_up); +@@ -1179,7 +1179,7 @@ int arizona_dvfs_down(struct snd_soc_component *component, unsigned int flags) + unsigned int old_reqs; + int ret = 0; + +- mutex_lock(&priv->dvfs_lock); ++ guard(mutex)(&priv->dvfs_lock); + + old_reqs = priv->dvfs_reqs; + priv->dvfs_reqs &= ~flags; +@@ -1187,7 +1187,6 @@ int arizona_dvfs_down(struct snd_soc_component *component, unsigned int flags) + if (!priv->dvfs_cached && old_reqs && !priv->dvfs_reqs) + ret = arizona_dvfs_disable(component); + +- mutex_unlock(&priv->dvfs_lock); + return ret; + } + EXPORT_SYMBOL_GPL(arizona_dvfs_down); +@@ -1199,7 +1198,7 @@ int arizona_dvfs_sysclk_ev(struct snd_soc_dapm_widget *w, + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&priv->dvfs_lock); ++ guard(mutex)(&priv->dvfs_lock); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +@@ -1222,7 +1221,6 @@ int arizona_dvfs_sysclk_ev(struct snd_soc_dapm_widget *w, + break; + } + +- mutex_unlock(&priv->dvfs_lock); + return ret; + } + EXPORT_SYMBOL_GPL(arizona_dvfs_sysclk_ev); +@@ -1657,13 +1655,11 @@ static void arizona_wm5102_set_dac_comp(struct snd_soc_component *component, + { 0x80, 0x0 }, + }; + +- mutex_lock(&arizona->dac_comp_lock); +- +- dac_comp[1].def = arizona->dac_comp_coeff; +- if (rate >= 176400) +- dac_comp[2].def = arizona->dac_comp_enabled; +- +- mutex_unlock(&arizona->dac_comp_lock); ++ scoped_guard(mutex, &arizona->dac_comp_lock) { ++ dac_comp[1].def = arizona->dac_comp_coeff; ++ if (rate >= 176400) ++ dac_comp[2].def = arizona->dac_comp_enabled; ++ } + + regmap_multi_reg_write(arizona->regmap, + dac_comp, +diff --git a/sound/soc/codecs/aw87390.c b/sound/soc/codecs/aw87390.c +--- a/sound/soc/codecs/aw87390.c ++++ b/sound/soc/codecs/aw87390.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -225,11 +226,10 @@ static int aw87390_profile_set(struct snd_kcontrol *kcontrol, + struct aw87390 *aw87390 = snd_soc_component_get_drvdata(codec); + int ret; + +- mutex_lock(&aw87390->lock); ++ guard(mutex)(&aw87390->lock); + ret = aw87390_dev_set_profile_index(aw87390->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change\n"); +- mutex_unlock(&aw87390->lock); + return 0; + } + +@@ -238,8 +238,6 @@ static int aw87390_profile_set(struct snd_kcontrol *kcontrol, + aw87390_power_on(aw87390->aw_pa); + } + +- mutex_unlock(&aw87390->lock); +- + return 1; + } + +@@ -250,7 +248,7 @@ static const struct snd_kcontrol_new aw87390_controls[] = { + + static int aw87390_request_firmware_file(struct aw87390 *aw87390) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + int ret; + + aw87390->aw_pa->fw_status = AW87390_DEV_FW_FAILED; +@@ -265,14 +263,11 @@ static int aw87390_request_firmware_file(struct aw87390 *aw87390) + + aw87390->aw_cfg = devm_kzalloc(aw87390->aw_pa->dev, + struct_size(aw87390->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw87390->aw_cfg) { +- release_firmware(cont); ++ if (!aw87390->aw_cfg) + return -ENOMEM; +- } + + aw87390->aw_cfg->len = cont->size; + memcpy(aw87390->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + ret = aw88395_dev_load_acf_check(aw87390->aw_pa, aw87390->aw_cfg); + if (ret) { +@@ -280,14 +275,12 @@ static int aw87390_request_firmware_file(struct aw87390 *aw87390) + return ret; + } + +- mutex_lock(&aw87390->lock); ++ guard(mutex)(&aw87390->lock); + + ret = aw88395_dev_cfg_load(aw87390->aw_pa, aw87390->aw_cfg); + if (ret) + dev_err(aw87390->aw_pa->dev, "aw_dev acf parse failed\n"); + +- mutex_unlock(&aw87390->lock); +- + return ret; + } + +diff --git a/sound/soc/codecs/aw88081.c b/sound/soc/codecs/aw88081.c +--- a/sound/soc/codecs/aw88081.c ++++ b/sound/soc/codecs/aw88081.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -763,9 +764,8 @@ static void aw88081_startup_work(struct work_struct *work) + struct aw88081 *aw88081 = + container_of(work, struct aw88081, start_work.work); + +- mutex_lock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + aw88081_start_pa(aw88081); +- mutex_unlock(&aw88081->lock); + } + + static void aw88081_start(struct aw88081 *aw88081, bool sync_start) +@@ -942,11 +942,10 @@ static int aw88081_profile_set(struct snd_kcontrol *kcontrol, + int ret; + + /* pa stop or stopping just set profile */ +- mutex_lock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + ret = aw88081_dev_set_profile_index(aw88081->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88081->lock); + return 0; + } + +@@ -955,8 +954,6 @@ static int aw88081_profile_set(struct snd_kcontrol *kcontrol, + aw88081_start(aw88081, AW88081_SYNC_START); + } + +- mutex_unlock(&aw88081->lock); +- + return 1; + } + +@@ -1136,7 +1133,7 @@ static int aw88081_dev_init(struct aw88081 *aw88081, struct aw_container *aw_cfg + + static int aw88081_request_firmware_file(struct aw88081 *aw88081) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + int ret; + +@@ -1150,26 +1147,21 @@ static int aw88081_request_firmware_file(struct aw88081 *aw88081) + AW88081_ACF_FILE, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88081->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw_cfg) { +- release_firmware(cont); ++ if (!aw_cfg) + return -ENOMEM; +- } ++ + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); + + aw88081->aw_cfg = aw_cfg; + +- release_firmware(cont); +- + ret = aw88395_dev_load_acf_check(aw88081->aw_pa, aw88081->aw_cfg); + if (ret) + return ret; + +- mutex_lock(&aw88081->lock); +- ret = aw88081_dev_init(aw88081, aw88081->aw_cfg); +- mutex_unlock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + +- return ret; ++ return aw88081_dev_init(aw88081, aw88081->aw_cfg); + } + + static int aw88081_playback_event(struct snd_soc_dapm_widget *w, +@@ -1178,7 +1170,7 @@ static int aw88081_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88081 *aw88081 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88081_start(aw88081, AW88081_ASYNC_START); +@@ -1189,7 +1181,6 @@ static int aw88081_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88081->lock); + + return 0; + } +diff --git a/sound/soc/codecs/aw88166.c b/sound/soc/codecs/aw88166.c +--- a/sound/soc/codecs/aw88166.c ++++ b/sound/soc/codecs/aw88166.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -1173,9 +1174,8 @@ static void aw88166_startup_work(struct work_struct *work) + struct aw88166 *aw88166 = + container_of(work, struct aw88166, start_work.work); + +- mutex_lock(&aw88166->lock); ++ guard(mutex)(&aw88166->lock); + aw88166_start_pa(aw88166); +- mutex_unlock(&aw88166->lock); + } + + static void aw88166_start(struct aw88166 *aw88166, bool sync_start) +@@ -1413,11 +1413,10 @@ static int aw88166_profile_set(struct snd_kcontrol *kcontrol, + struct aw88166 *aw88166 = snd_soc_component_get_drvdata(codec); + int ret; + +- mutex_lock(&aw88166->lock); ++ guard(mutex)(&aw88166->lock); + ret = aw88166_dev_set_profile_index(aw88166->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88166->lock); + return 0; + } + +@@ -1426,8 +1425,6 @@ static int aw88166_profile_set(struct snd_kcontrol *kcontrol, + aw88166_start(aw88166, AW88166_SYNC_START); + } + +- mutex_unlock(&aw88166->lock); +- + return 1; + } + +@@ -1573,7 +1570,7 @@ static int aw88166_dev_init(struct aw88166 *aw88166, struct aw_container *aw_cfg + + static int aw88166_request_firmware_file(struct aw88166 *aw88166) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + const char *fw_name; + int ret; + +@@ -1593,13 +1590,11 @@ static int aw88166_request_firmware_file(struct aw88166 *aw88166) + + aw88166->aw_cfg = devm_kzalloc(aw88166->aw_pa->dev, + struct_size(aw88166->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw88166->aw_cfg) { +- release_firmware(cont); ++ if (!aw88166->aw_cfg) + return -ENOMEM; +- } ++ + aw88166->aw_cfg->len = (int)cont->size; + memcpy(aw88166->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + ret = aw88395_dev_load_acf_check(aw88166->aw_pa, aw88166->aw_cfg); + if (ret) { +@@ -1607,12 +1602,12 @@ static int aw88166_request_firmware_file(struct aw88166 *aw88166) + return ret; + } + +- mutex_lock(&aw88166->lock); +- /* aw device init */ +- ret = aw88166_dev_init(aw88166, aw88166->aw_cfg); +- if (ret) +- dev_err(aw88166->aw_pa->dev, "dev init failed\n"); +- mutex_unlock(&aw88166->lock); ++ scoped_guard(mutex, &aw88166->lock) { ++ /* aw device init */ ++ ret = aw88166_dev_init(aw88166, aw88166->aw_cfg); ++ if (ret) ++ dev_err(aw88166->aw_pa->dev, "dev init failed\n"); ++ } + + return ret; + } +@@ -1639,7 +1634,7 @@ static int aw88166_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88166 *aw88166 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88166->lock); ++ guard(mutex)(&aw88166->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88166_start(aw88166, AW88166_ASYNC_START); +@@ -1650,7 +1645,6 @@ static int aw88166_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88166->lock); + + return 0; + } +diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c +--- a/sound/soc/codecs/aw88261.c ++++ b/sound/soc/codecs/aw88261.c +@@ -8,6 +8,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -960,11 +961,10 @@ static int aw88261_profile_set(struct snd_kcontrol *kcontrol, + int ret; + + /* pa stop or stopping just set profile */ +- mutex_lock(&aw88261->lock); ++ guard(mutex)(&aw88261->lock); + ret = aw88261_dev_set_profile_index(aw88261->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88261->lock); + return 0; + } + +@@ -973,8 +973,6 @@ static int aw88261_profile_set(struct snd_kcontrol *kcontrol, + aw88261_start(aw88261); + } + +- mutex_unlock(&aw88261->lock); +- + return 1; + } + +@@ -1038,7 +1036,7 @@ static int aw88261_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88261->lock); ++ guard(mutex)(&aw88261->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88261_start(aw88261); +@@ -1049,7 +1047,6 @@ static int aw88261_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88261->lock); + + return 0; + } +@@ -1152,7 +1149,7 @@ static int aw88261_dev_init(struct aw88261 *aw88261, struct aw_container *aw_cfg + + static int aw88261_request_firmware_file(struct aw88261 *aw88261) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + const char *fw_name; + int ret; +@@ -1172,13 +1169,11 @@ static int aw88261_request_firmware_file(struct aw88261 *aw88261) + fw_name, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88261->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw_cfg) { +- release_firmware(cont); ++ if (!aw_cfg) + return -ENOMEM; +- } ++ + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + aw88261->aw_cfg = aw_cfg; + +@@ -1188,12 +1183,12 @@ static int aw88261_request_firmware_file(struct aw88261 *aw88261) + return ret; + } + +- mutex_lock(&aw88261->lock); +- /* aw device init */ +- ret = aw88261_dev_init(aw88261, aw88261->aw_cfg); +- if (ret) +- dev_err(aw88261->aw_pa->dev, "dev init failed"); +- mutex_unlock(&aw88261->lock); ++ scoped_guard(mutex, &aw88261->lock) { ++ /* aw device init */ ++ ret = aw88261_dev_init(aw88261, aw88261->aw_cfg); ++ if (ret) ++ dev_err(aw88261->aw_pa->dev, "dev init failed"); ++ } + + return ret; + } +diff --git a/sound/soc/codecs/aw88395/aw88395.c b/sound/soc/codecs/aw88395/aw88395.c +--- a/sound/soc/codecs/aw88395/aw88395.c ++++ b/sound/soc/codecs/aw88395/aw88395.c +@@ -8,6 +8,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -51,9 +52,8 @@ static void aw88395_startup_work(struct work_struct *work) + struct aw88395 *aw88395 = + container_of(work, struct aw88395, start_work.work); + +- mutex_lock(&aw88395->lock); ++ guard(mutex)(&aw88395->lock); + aw88395_start_pa(aw88395); +- mutex_unlock(&aw88395->lock); + } + + static void aw88395_start(struct aw88395 *aw88395, bool sync_start) +@@ -224,11 +224,10 @@ static int aw88395_profile_set(struct snd_kcontrol *kcontrol, + int ret; + + /* pa stop or stopping just set profile */ +- mutex_lock(&aw88395->lock); ++ guard(mutex)(&aw88395->lock); + ret = aw88395_dev_set_profile_index(aw88395->aw_pa, ucontrol->value.integer.value[0]); + if (ret < 0) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88395->lock); + return 0; + } + +@@ -237,8 +236,6 @@ static int aw88395_profile_set(struct snd_kcontrol *kcontrol, + aw88395_start(aw88395, AW88395_SYNC_START); + } + +- mutex_unlock(&aw88395->lock); +- + return 1; + } + +@@ -366,7 +363,7 @@ static int aw88395_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88395 *aw88395 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88395->lock); ++ guard(mutex)(&aw88395->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88395_start(aw88395, AW88395_ASYNC_START); +@@ -377,7 +374,6 @@ static int aw88395_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88395->lock); + + return 0; + } +@@ -461,7 +457,7 @@ static void aw88395_hw_reset(struct aw88395 *aw88395) + + static int aw88395_request_firmware_file(struct aw88395 *aw88395) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + int ret; + +@@ -477,13 +473,11 @@ static int aw88395_request_firmware_file(struct aw88395 *aw88395) + AW88395_ACF_FILE, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88395->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw_cfg) { +- release_firmware(cont); ++ if (!aw_cfg) + return -ENOMEM; +- } ++ + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + aw88395->aw_cfg = aw_cfg; + +@@ -495,12 +489,12 @@ static int aw88395_request_firmware_file(struct aw88395 *aw88395) + + dev_dbg(aw88395->aw_pa->dev, "%s : bin load success\n", __func__); + +- mutex_lock(&aw88395->lock); +- /* aw device init */ +- ret = aw88395_dev_init(aw88395->aw_pa, aw88395->aw_cfg); +- if (ret < 0) +- dev_err(aw88395->aw_pa->dev, "dev init failed"); +- mutex_unlock(&aw88395->lock); ++ scoped_guard(mutex, &aw88395->lock) { ++ /* aw device init */ ++ ret = aw88395_dev_init(aw88395->aw_pa, aw88395->aw_cfg); ++ if (ret < 0) ++ dev_err(aw88395->aw_pa->dev, "dev init failed"); ++ } + + return ret; + } +diff --git a/sound/soc/codecs/aw88395/aw88395_device.c b/sound/soc/codecs/aw88395/aw88395_device.c +--- a/sound/soc/codecs/aw88395/aw88395_device.c ++++ b/sound/soc/codecs/aw88395/aw88395_device.c +@@ -8,6 +8,7 @@ + // Author: Ben Yi + // + ++#include + #include + #include + #include +@@ -70,7 +71,7 @@ int aw_dev_dsp_write(struct aw_device *aw_dev, + u32 reg_value; + int ret; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + switch (data_type) { + case AW_DSP_16_DATA: + ret = aw_dev_dsp_write_16bit(aw_dev, dsp_addr, dsp_data); +@@ -93,7 +94,6 @@ int aw_dev_dsp_write(struct aw_device *aw_dev, + /* clear dsp chip select state*/ + if (regmap_read(aw_dev->regmap, AW88395_ID_REG, ®_value)) + dev_err(aw_dev->dev, "%s fail to clear chip state. Err=%d\n", __func__, ret); +- mutex_unlock(&aw_dev->dsp_lock); + + return ret; + } +@@ -156,7 +156,7 @@ int aw_dev_dsp_read(struct aw_device *aw_dev, + u32 reg_value; + int ret; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + switch (data_type) { + case AW_DSP_16_DATA: + ret = aw_dev_dsp_read_16bit(aw_dev, dsp_addr, dsp_data); +@@ -179,7 +179,6 @@ int aw_dev_dsp_read(struct aw_device *aw_dev, + /* clear dsp chip select state*/ + if (regmap_read(aw_dev->regmap, AW88395_ID_REG, ®_value)) + dev_err(aw_dev->dev, "%s fail to clear chip state. Err=%d\n", __func__, ret); +- mutex_unlock(&aw_dev->dsp_lock); + + return ret; + } +@@ -1110,42 +1109,36 @@ static int aw_dev_dsp_update_container(struct aw_device *aw_dev, + #ifdef AW88395_DSP_I2C_WRITES + u32 tmp_len; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + ret = regmap_write(aw_dev->regmap, AW88395_DSPMADD_REG, base); + if (ret) +- goto error_operation; ++ return ret; + + for (i = 0; i < len; i += AW88395_MAX_RAM_WRITE_BYTE_SIZE) { + tmp_len = min(len - i, AW88395_MAX_RAM_WRITE_BYTE_SIZE); + ret = regmap_raw_write(aw_dev->regmap, AW88395_DSPMDAT_REG, + &data[i], tmp_len); + if (ret) +- goto error_operation; ++ return ret; + } +- mutex_unlock(&aw_dev->dsp_lock); + #else + __be16 reg_val; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + /* i2c write */ + ret = regmap_write(aw_dev->regmap, AW88395_DSPMADD_REG, base); + if (ret) +- goto error_operation; ++ return ret; + for (i = 0; i < len; i += 2) { + reg_val = cpu_to_be16p((u16 *)(data + i)); + ret = regmap_write(aw_dev->regmap, AW88395_DSPMDAT_REG, + (u16)reg_val); + if (ret) +- goto error_operation; ++ return ret; + } +- mutex_unlock(&aw_dev->dsp_lock); + #endif + + return 0; +- +-error_operation: +- mutex_unlock(&aw_dev->dsp_lock); +- return ret; + } + + static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, +@@ -1231,14 +1224,14 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + { + unsigned int reg_val; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + /* check the odd bits of reg 0x40 */ + regmap_write(aw_dev->regmap, AW88395_DSPMADD_REG, AW88395_DSP_ODD_NUM_BIT_TEST); + regmap_read(aw_dev->regmap, AW88395_DSPMADD_REG, ®_val); + if (reg_val != AW88395_DSP_ODD_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check reg 0x40 odd bit failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_ODD_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } + + /* check the even bits of reg 0x40 */ +@@ -1247,7 +1240,7 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + if (reg_val != AW88395_DSP_EVEN_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check reg 0x40 even bit failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_EVEN_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } + + /* check dsp_fw_base_addr */ +@@ -1256,7 +1249,7 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + if (reg_val != AW88395_DSP_EVEN_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check dsp fw addr failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_EVEN_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } + + /* check dsp_cfg_base_addr */ +@@ -1265,15 +1258,10 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + if (reg_val != AW88395_DSP_ODD_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_ODD_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } +- mutex_unlock(&aw_dev->dsp_lock); + + return 0; +- +-error: +- mutex_unlock(&aw_dev->dsp_lock); +- return -EPERM; + } + + int aw88395_dev_fw_update(struct aw_device *aw_dev, bool up_dsp_fw_en, bool force_up_en) +diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/aw88399-lib.c +@@ -0,0 +1,1409 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// aw88399-lib.c -- AW88399 Common functions for ASoC and HDA audio drivers ++// ++// Copyright (c) 2023 AWINIC Technology CO., LTD ++// ++// Author: Weidong Wang ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "aw88395/aw88395_device.h" ++ ++const struct regmap_config aw88399_remap_config = { ++ .val_bits = 16, ++ .reg_bits = 8, ++ .max_register = AW88399_REG_MAX, ++ .reg_format_endian = REGMAP_ENDIAN_LITTLE, ++ .val_format_endian = REGMAP_ENDIAN_BIG, ++}; ++EXPORT_SYMBOL_GPL(aw88399_remap_config); ++ ++static void aw_dev_pwd(struct aw_device *aw_dev, bool pwd) ++{ ++ int ret; ++ ++ if (pwd) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_PWDN_MASK, AW88399_PWDN_POWER_DOWN_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_PWDN_MASK, AW88399_PWDN_WORKING_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++} ++ ++static void aw_dev_get_int_status(struct aw_device *aw_dev, unsigned short *int_status) ++{ ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSINT_REG, ®_val); ++ if (ret) ++ dev_err(aw_dev->dev, "read interrupt reg fail, ret=%d", ret); ++ else ++ *int_status = reg_val; ++ ++ dev_dbg(aw_dev->dev, "read interrupt reg=0x%04x", *int_status); ++} ++ ++static void aw_dev_clear_int_status(struct aw_device *aw_dev) ++{ ++ u16 int_status; ++ ++ /* read int status and clear */ ++ aw_dev_get_int_status(aw_dev, &int_status); ++ /* make sure int status is clear */ ++ aw_dev_get_int_status(aw_dev, &int_status); ++ if (int_status) ++ dev_dbg(aw_dev->dev, "int status(%d) is not cleaned.\n", int_status); ++} ++ ++static int aw_dev_get_iis_status(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); ++ if (ret) ++ return ret; ++ if ((reg_val & AW88399_BIT_PLL_CHECK) != AW88399_BIT_PLL_CHECK) { ++ dev_err(aw_dev->dev, "check pll lock fail, reg_val:0x%04x", reg_val); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int aw_dev_check_mode1_pll(struct aw_device *aw_dev) ++{ ++ int ret, i; ++ ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = aw_dev_get_iis_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode1 iis signal check error"); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ return 0; ++ } ++ } ++ ++ return -EPERM; ++} ++ ++static int aw_dev_check_mode2_pll(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ int ret, i; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_PLLCTRL2_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ reg_val &= (~AW88399_CCO_MUX_MASK); ++ if (reg_val == AW88399_CCO_MUX_DIVIDED_VALUE) { ++ dev_dbg(aw_dev->dev, "CCO_MUX is already divider"); ++ return -EPERM; ++ } ++ ++ /* change mode2 */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, ++ ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_DIVIDED_VALUE); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = aw_dev_get_iis_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode2 iis signal check error"); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ break; ++ } ++ } ++ ++ /* change mode1 */ ++ regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, ++ ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_BYPASS_VALUE); ++ if (ret == 0) { ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = aw_dev_get_iis_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode2 switch to mode1, iis signal check error"); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ break; ++ } ++ } ++ } ++ ++ return ret; ++} ++ ++int aw_dev_check_syspll(struct aw_device *aw_dev) ++{ ++ int ret; ++ ++ ret = aw_dev_check_mode1_pll(aw_dev); ++ if (ret) { ++ dev_dbg(aw_dev->dev, "mode1 check iis failed try switch to mode2 check"); ++ ret = aw_dev_check_mode2_pll(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode2 check iis failed"); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw_dev_check_syspll); ++ ++static int aw_dev_check_sysst(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int check_val; ++ unsigned int reg_val; ++ int ret, i; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_PWMCTRL3_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ if (reg_val & (~AW88399_NOISE_GATE_EN_MASK)) ++ check_val = AW88399_BIT_SYSST_NOSWS_CHECK; ++ else ++ check_val = AW88399_BIT_SYSST_SWS_CHECK; ++ ++ /* ++ * On some hardware the BSTS (boost-finished) status bit does not ++ * reliably assert even when audio output is working normally. ++ * Allow per-instance bypass when flagged by the side-codec driver. ++ */ ++ if (aw88399->bsts_unreliable) ++ check_val &= ~AW88399_BSTS_FINISHED_VALUE; ++ ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ if ((reg_val & (~AW88399_BIT_SYSST_CHECK_MASK) & check_val) != check_val) { ++ dev_err(aw_dev->dev, "check sysst fail, cnt=%d, reg_val=0x%04x, check:0x%x", ++ i, reg_val, AW88399_BIT_SYSST_NOSWS_CHECK); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ return 0; ++ } ++ } ++ ++ return -EPERM; ++} ++ ++static void aw_dev_amppd(struct aw_device *aw_dev, bool amppd) ++{ ++ int ret; ++ ++ if (amppd) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_AMPPD_MASK, AW88399_AMPPD_POWER_DOWN_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_AMPPD_MASK, AW88399_AMPPD_WORKING_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++} ++ ++void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable) ++{ ++ int ret; ++ ++ if (is_enable) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_DSPBY_MASK, AW88399_DSPBY_WORKING_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_DSPBY_MASK, AW88399_DSPBY_BYPASS_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed\n", __func__); ++} ++EXPORT_SYMBOL_GPL(aw_dev_dsp_enable); ++ ++static int aw88399_dev_get_icalk(struct aw88399 *aw88399, int16_t *icalk) ++{ ++ uint16_t icalkh_val, icalkl_val, icalk_val; ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRH4_REG, ®_val); ++ if (ret) ++ return ret; ++ icalkh_val = reg_val & (~AW88399_EF_ISN_GESLP_H_MASK); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRL4_REG, ®_val); ++ if (ret) ++ return ret; ++ icalkl_val = reg_val & (~AW88399_EF_ISN_GESLP_L_MASK); ++ ++ if (aw88399->check_val == AW_EF_AND_CHECK) ++ icalk_val = icalkh_val & icalkl_val; ++ else ++ icalk_val = icalkh_val | icalkl_val; ++ ++ if (icalk_val & (~AW88399_EF_ISN_GESLP_SIGN_MASK)) ++ icalk_val = icalk_val | AW88399_EF_ISN_GESLP_SIGN_NEG; ++ *icalk = (int16_t)icalk_val; ++ ++ return 0; ++} ++ ++static int aw88399_dev_get_vcalk(struct aw88399 *aw88399, int16_t *vcalk) ++{ ++ uint16_t vcalkh_val, vcalkl_val, vcalk_val; ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRH3_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ vcalkh_val = reg_val & (~AW88399_EF_VSN_GESLP_H_MASK); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRL3_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ vcalkl_val = reg_val & (~AW88399_EF_VSN_GESLP_L_MASK); ++ ++ if (aw88399->check_val == AW_EF_AND_CHECK) ++ vcalk_val = vcalkh_val & vcalkl_val; ++ else ++ vcalk_val = vcalkh_val | vcalkl_val; ++ ++ if (vcalk_val & AW88399_EF_VSN_GESLP_SIGN_MASK) ++ vcalk_val = vcalk_val | AW88399_EF_VSN_GESLP_SIGN_NEG; ++ *vcalk = (int16_t)vcalk_val; ++ ++ return 0; ++} ++ ++static int aw88399_dev_get_internal_vcalk(struct aw88399 *aw88399, int16_t *vcalk) ++{ ++ uint16_t vcalkh_val, vcalkl_val, vcalk_val; ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRH2_REG, ®_val); ++ if (ret) ++ return ret; ++ vcalkh_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_H_MASK); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRL2_REG, ®_val); ++ if (ret) ++ return ret; ++ vcalkl_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_L_MASK); ++ ++ if (aw88399->check_val == AW_EF_AND_CHECK) ++ vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) & ++ (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); ++ else ++ vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) | ++ (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); ++ ++ if (vcalk_val & (~AW88399_TEM4_SIGN_MASK)) ++ vcalk_val = vcalk_val | AW88399_TEM4_SIGN_NEG; ++ ++ *vcalk = (int16_t)vcalk_val; ++ ++ return 0; ++} ++ ++static int aw_dev_set_vcalb(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int vsense_select, vsense_value; ++ int32_t ical_k, vcal_k, vcalb; ++ int16_t icalk, vcalk; ++ uint16_t reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_VSNCTRL1_REG, &vsense_value); ++ if (ret) ++ return ret; ++ ++ vsense_select = vsense_value & (~AW88399_VDSEL_MASK); ++ ++ ret = aw88399_dev_get_icalk(aw88399, &icalk); ++ if (ret) { ++ dev_err(aw_dev->dev, "get icalk failed\n"); ++ return ret; ++ } ++ ++ ical_k = icalk * AW88399_ICABLK_FACTOR + AW88399_CABL_BASE_VALUE; ++ ++ switch (vsense_select) { ++ case AW88399_DEV_VDSEL_VSENSE: ++ ret = aw88399_dev_get_vcalk(aw88399, &vcalk); ++ vcal_k = vcalk * AW88399_VCABLK_FACTOR + AW88399_CABL_BASE_VALUE; ++ vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_FACTOR / AW88399_ISCAL_FACTOR * ++ ical_k / vcal_k * aw88399->vcalb_init_val; ++ break; ++ case AW88399_DEV_VDSEL_DAC: ++ ret = aw88399_dev_get_internal_vcalk(aw88399, &vcalk); ++ vcal_k = vcalk * AW88399_VCABLK_DAC_FACTOR + AW88399_CABL_BASE_VALUE; ++ vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_DAC_FACTOR / ++ AW88399_ISCAL_DAC_FACTOR * ical_k / ++ vcal_k * aw88399->vcalb_init_val; ++ break; ++ default: ++ dev_err(aw_dev->dev, "%s: unsupported vsense\n", __func__); ++ ret = -EINVAL; ++ break; ++ } ++ if (ret) ++ return ret; ++ ++ vcalb = vcalb >> AW88399_VCALB_ADJ_FACTOR; ++ reg_val = (uint32_t)vcalb; ++ ++ regmap_write(aw_dev->regmap, AW88399_DSPVCALB_REG, reg_val); ++ ++ return 0; ++} ++ ++int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc) ++{ ++ struct aw_device *aw_dev = ++ container_of(cali_desc, struct aw_device, cali_desc); ++ uint16_t re_lbits, re_hbits; ++ u32 cali_re; ++ int ret; ++ ++ if ((aw_dev->cali_desc.cali_re >= AW88399_CALI_RE_MAX) || ++ (aw_dev->cali_desc.cali_re <= AW88399_CALI_RE_MIN)) ++ return -EINVAL; ++ ++ cali_re = AW88399_SHOW_RE_TO_DSP_RE((aw_dev->cali_desc.cali_re + ++ aw_dev->cali_desc.ra), AW88399_DSP_RE_SHIFT); ++ ++ re_hbits = (cali_re & (~AW88399_CALI_RE_HBITS_MASK)) >> AW88399_CALI_RE_HBITS_SHIFT; ++ re_lbits = (cali_re & (~AW88399_CALI_RE_LBITS_MASK)) >> AW88399_CALI_RE_LBITS_SHIFT; ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_ACR1_REG, re_hbits); ++ if (ret) { ++ dev_err(aw_dev->dev, "set cali re error"); ++ return ret; ++ } ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_ACR2_REG, re_lbits); ++ if (ret) ++ dev_err(aw_dev->dev, "set cali re error"); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aw_dev_update_cali_re); ++ ++static int aw_dev_fw_crc_check(struct aw_device *aw_dev) ++{ ++ uint16_t check_val, fw_len_val; ++ unsigned int reg_val; ++ int ret; ++ ++ /* calculate fw_end_addr */ ++ fw_len_val = ((aw_dev->dsp_fw_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_FW_BASE_ADDR; ++ ++ /* write fw_end_addr to crc_end_addr */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_END_ADDR_MASK, fw_len_val); ++ if (ret) ++ return ret; ++ /* enable fw crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_ENABLE_VALUE); ++ ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ ++ /* read crc check result */ ++ regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; ++ ++ /* disable fw crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_DISABLE_VALUE); ++ if (ret) ++ return ret; ++ ++ if (check_val != AW88399_CRC_CHECK_PASS_VAL) { ++ dev_err(aw_dev->dev, "%s failed, check_val 0x%x != 0x%x", ++ __func__, check_val, AW88399_CRC_CHECK_PASS_VAL); ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int aw_dev_cfg_crc_check(struct aw_device *aw_dev) ++{ ++ uint16_t check_val, cfg_len_val; ++ unsigned int reg_val; ++ int ret; ++ ++ /* calculate cfg end addr */ ++ cfg_len_val = ((aw_dev->dsp_cfg_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_CFG_BASE_ADDR; ++ ++ /* write cfg_end_addr to crc_end_addr */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_END_ADDR_MASK, cfg_len_val); ++ if (ret) ++ return ret; ++ ++ /* enable cfg crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_ENABLE_VALUE); ++ if (ret) ++ return ret; ++ ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ ++ /* read crc check result */ ++ ret = regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; ++ ++ /* disable cfg crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_DISABLE_VALUE); ++ if (ret) ++ return ret; ++ ++ if (check_val != AW88399_CRC_CHECK_PASS_VAL) { ++ dev_err(aw_dev->dev, "crc_check failed, check val 0x%x != 0x%x", ++ check_val, AW88399_CRC_CHECK_PASS_VAL); ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int aw_dev_hw_crc_check(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ int ret; ++ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_BYPASS_VALUE); ++ if (ret) ++ return ret; ++ ++ ret = aw_dev_fw_crc_check(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "fw_crc_check failed\n"); ++ goto crc_check_failed; ++ } ++ ++ ret = aw_dev_cfg_crc_check(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "cfg_crc_check failed\n"); ++ goto crc_check_failed; ++ } ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_CRCCTRL_REG, aw88399->crc_init_val); ++ if (ret) ++ return ret; ++ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); ++ ++ return ret; ++ ++crc_check_failed: ++ regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); ++ return ret; ++} ++ ++static void aw_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag) ++{ ++ int ret; ++ ++ if (flag) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCTRL3_REG, ++ ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_ENABLE_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_DISABLE_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++} ++ ++int aw_dev_get_dsp_status(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_WDT_REG, ®_val); ++ if (ret) ++ return ret; ++ if (!(reg_val & (~AW88399_WDT_CNT_MASK))) ++ return -EPERM; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw_dev_get_dsp_status); ++ ++static int aw_dev_dsp_check(struct aw_device *aw_dev) ++{ ++ int ret, i; ++ ++ switch (aw_dev->dsp_cfg) { ++ case AW88399_DEV_DSP_BYPASS: ++ dev_dbg(aw_dev->dev, "dsp bypass"); ++ ret = 0; ++ break; ++ case AW88399_DEV_DSP_WORK: ++ aw_dev_dsp_enable(aw_dev, false); ++ aw_dev_dsp_enable(aw_dev, true); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ for (i = 0; i < AW88399_DEV_DSP_CHECK_MAX; i++) { ++ ret = aw_dev_get_dsp_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "dsp wdt status error=%d", ret); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } ++ } ++ break; ++ default: ++ dev_err(aw_dev->dev, "unknown dsp cfg=%d", aw_dev->dsp_cfg); ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value) ++{ ++ struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; ++ unsigned int reg_value; ++ u16 real_value; ++ int ret; ++ ++ real_value = min((value + vol_desc->init_volume), (unsigned int)AW88399_MUTE_VOL); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL2_REG, ®_value); ++ if (ret) ++ return ret; ++ ++ dev_dbg(aw_dev->dev, "value 0x%x , reg:0x%x", value, real_value); ++ ++ real_value = (real_value << AW88399_VOL_START_BIT) | (reg_value & AW88399_VOL_MASK); ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_SYSCTRL2_REG, real_value); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aw_dev_set_volume); ++ ++static void aw_dev_fade_in(struct aw_device *aw_dev) ++{ ++ struct aw_volume_desc *desc = &aw_dev->volume_desc; ++ u16 fade_in_vol = desc->ctl_volume; ++ int fade_step = aw_dev->fade_step; ++ int i; ++ ++ if (fade_step == 0 || aw_dev->fade_in_time == 0) { ++ aw_dev_set_volume(aw_dev, fade_in_vol); ++ return; ++ } ++ ++ for (i = AW88399_MUTE_VOL; i >= fade_in_vol; i -= fade_step) { ++ aw_dev_set_volume(aw_dev, i); ++ usleep_range(aw_dev->fade_in_time, aw_dev->fade_in_time + 10); ++ } ++ ++ if (i != fade_in_vol) ++ aw_dev_set_volume(aw_dev, fade_in_vol); ++} ++ ++static void aw_dev_fade_out(struct aw_device *aw_dev) ++{ ++ struct aw_volume_desc *desc = &aw_dev->volume_desc; ++ int fade_step = aw_dev->fade_step; ++ int i; ++ ++ if (fade_step == 0 || aw_dev->fade_out_time == 0) { ++ aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); ++ return; ++ } ++ ++ for (i = desc->ctl_volume; i <= AW88399_MUTE_VOL; i += fade_step) { ++ aw_dev_set_volume(aw_dev, i); ++ usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); ++ } ++ ++ if (i != AW88399_MUTE_VOL) { ++ aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); ++ usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); ++ } ++} ++ ++void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute) ++{ ++ if (is_mute) { ++ aw_dev_fade_out(aw_dev); ++ regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_HMUTE_MASK, AW88399_HMUTE_ENABLE_VALUE); ++ } else { ++ regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_HMUTE_MASK, AW88399_HMUTE_DISABLE_VALUE); ++ aw_dev_fade_in(aw_dev); ++ } ++} ++EXPORT_SYMBOL_GPL(aw88399_dev_mute); ++ ++static void aw88399_dev_set_dither(struct aw88399 *aw88399, bool dither) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ ++ if (dither) ++ regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_ENABLE_VALUE); ++ else ++ regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_DISABLE_VALUE); ++} ++ ++static int aw88399_dev_start(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ int ret; ++ ++ if (aw_dev->status == AW88399_DEV_PW_ON) { ++ dev_dbg(aw_dev->dev, "already power on"); ++ return 0; ++ } ++ ++ aw88399_dev_set_dither(aw88399, false); ++ ++ /* power on */ ++ aw_dev_pwd(aw_dev, false); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ ++ ret = aw_dev_check_syspll(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "pll check failed cannot start"); ++ goto pll_check_fail; ++ } ++ ++ /* amppd on */ ++ aw_dev_amppd(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 50); ++ ++ /* check i2s status */ ++ ret = aw_dev_check_sysst(aw88399); ++ if (ret) { ++ dev_err(aw_dev->dev, "sysst check failed"); ++ goto sysst_check_fail; ++ } ++ ++ if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) { ++ ret = aw_dev_hw_crc_check(aw88399); ++ if (ret) { ++ dev_err(aw_dev->dev, "dsp crc check failed"); ++ goto crc_check_fail; ++ } ++ aw_dev_dsp_enable(aw_dev, false); ++ aw_dev_set_vcalb(aw88399); ++ aw_dev_update_cali_re(&aw_dev->cali_desc); ++ ++ ret = aw_dev_dsp_check(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "dsp status check failed"); ++ goto dsp_check_fail; ++ } ++ } else { ++ dev_dbg(aw_dev->dev, "start pa with dsp bypass"); ++ } ++ ++ /* enable tx feedback */ ++ aw_dev_i2s_tx_enable(aw_dev, true); ++ ++ if (aw88399->dither_st == AW88399_DITHER_EN_ENABLE_VALUE) ++ aw88399_dev_set_dither(aw88399, true); ++ ++ /* close mute */ ++ aw88399_dev_mute(aw_dev, false); ++ /* clear inturrupt */ ++ aw_dev_clear_int_status(aw_dev); ++ aw_dev->status = AW88399_DEV_PW_ON; ++ ++ return 0; ++ ++dsp_check_fail: ++crc_check_fail: ++ aw_dev_dsp_enable(aw_dev, false); ++sysst_check_fail: ++ aw_dev_clear_int_status(aw_dev); ++ aw_dev_amppd(aw_dev, true); ++pll_check_fail: ++ aw_dev_pwd(aw_dev, true); ++ aw_dev->status = AW88399_DEV_PW_OFF; ++ ++ return ret; ++} ++ ++static int aw_dev_dsp_update_container(struct aw_device *aw_dev, ++ unsigned char *data, unsigned int len, unsigned short base) ++{ ++ u32 tmp_len; ++ int i, ret; ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_DSPMADD_REG, base); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < len; i += AW88399_MAX_RAM_WRITE_BYTE_SIZE) { ++ tmp_len = min(len - i, AW88399_MAX_RAM_WRITE_BYTE_SIZE); ++ ret = regmap_raw_write(aw_dev->regmap, AW88399_DSPMDAT_REG, ++ &data[i], tmp_len); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int aw_dev_get_ra(struct aw_cali_desc *cali_desc) ++{ ++ struct aw_device *aw_dev = ++ container_of(cali_desc, struct aw_device, cali_desc); ++ u32 dsp_ra; ++ int ret; ++ ++ ret = aw_dev_dsp_read(aw_dev, AW88399_DSP_REG_CFG_ADPZ_RA, ++ &dsp_ra, AW_DSP_32_DATA); ++ if (ret) { ++ dev_err(aw_dev->dev, "read ra error"); ++ return ret; ++ } ++ ++ cali_desc->ra = AW88399_DSP_RE_TO_SHOW_RE(dsp_ra, ++ AW88399_DSP_RE_SHIFT); ++ ++ return 0; ++} ++ ++static int aw_dev_dsp_update_cfg(struct aw_device *aw_dev, ++ unsigned char *data, unsigned int len) ++{ ++ int ret; ++ ++ dev_dbg(aw_dev->dev, "dsp config len:%d", len); ++ ++ if (!len || !data) { ++ dev_err(aw_dev->dev, "dsp config data is null or len is 0"); ++ return -EINVAL; ++ } ++ ++ ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_CFG_ADDR); ++ if (ret) ++ return ret; ++ ++ aw_dev->dsp_cfg_len = len; ++ ++ ret = aw_dev_get_ra(&aw_dev->cali_desc); ++ ++ return ret; ++} ++ ++static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, ++ unsigned char *data, unsigned int len) ++{ ++ int ret; ++ ++ dev_dbg(aw_dev->dev, "dsp firmware len:%d", len); ++ ++ if (!len || !data) { ++ dev_err(aw_dev->dev, "dsp firmware data is null or len is 0"); ++ return -EINVAL; ++ } ++ ++ aw_dev->dsp_fw_len = len; ++ ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_FW_ADDR); ++ ++ return ret; ++} ++ ++static int aw_dev_check_sram(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ ++ /* read dsp_rom_check_reg */ ++ aw_dev_dsp_read(aw_dev, AW88399_DSP_ROM_CHECK_ADDR, ®_val, AW_DSP_16_DATA); ++ if (reg_val != AW88399_DSP_ROM_CHECK_DATA) { ++ dev_err(aw_dev->dev, "check dsp rom failed, read[0x%x] != check[0x%x]", ++ reg_val, AW88399_DSP_ROM_CHECK_DATA); ++ return -EPERM; ++ } ++ ++ /* check dsp_cfg_base_addr */ ++ aw_dev_dsp_write(aw_dev, AW88399_DSP_CFG_ADDR, ++ AW88399_DSP_ODD_NUM_BIT_TEST, AW_DSP_16_DATA); ++ aw_dev_dsp_read(aw_dev, AW88399_DSP_CFG_ADDR, ®_val, AW_DSP_16_DATA); ++ if (reg_val != AW88399_DSP_ODD_NUM_BIT_TEST) { ++ dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", ++ reg_val, AW88399_DSP_ODD_NUM_BIT_TEST); ++ return -EPERM; ++ } ++ ++ return 0; ++} ++ ++static void aw_dev_select_memclk(struct aw_device *aw_dev, unsigned char flag) ++{ ++ int ret; ++ ++ switch (flag) { ++ case AW88399_DEV_MEMCLK_PLL: ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_MEM_CLKSEL_MASK, ++ AW88399_MEM_CLKSEL_DAPHCLK_VALUE); ++ if (ret) ++ dev_err(aw_dev->dev, "memclk select pll failed"); ++ break; ++ case AW88399_DEV_MEMCLK_OSC: ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_MEM_CLKSEL_MASK, ++ AW88399_MEM_CLKSEL_OSCCLK_VALUE); ++ if (ret) ++ dev_err(aw_dev->dev, "memclk select OSC failed"); ++ break; ++ default: ++ dev_err(aw_dev->dev, "unknown memclk config, flag=0x%x", flag); ++ break; ++ } ++} ++ ++static void aw_dev_get_cur_mode_st(struct aw_device *aw_dev) ++{ ++ struct aw_profctrl_desc *profctrl_desc = &aw_dev->profctrl_desc; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL_REG, ®_val); ++ if (ret) { ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++ return; ++ } ++ if ((reg_val & (~AW88399_RCV_MODE_MASK)) == AW88399_RCV_MODE_RECEIVER_VALUE) ++ profctrl_desc->cur_mode = AW88399_RCV_MODE; ++ else ++ profctrl_desc->cur_mode = AW88399_NOT_RCV_MODE; ++} ++ ++static int aw_dev_update_reg_container(struct aw88399 *aw88399, ++ unsigned char *data, unsigned int len) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; ++ u16 read_vol, reg_val; ++ int data_len, i, ret; ++ int16_t *reg_data; ++ u8 reg_addr; ++ ++ reg_data = (int16_t *)data; ++ data_len = len >> 1; ++ ++ if (data_len & 0x1) { ++ dev_err(aw_dev->dev, "data len:%d unsupported", data_len); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < data_len; i += 2) { ++ reg_addr = reg_data[i]; ++ reg_val = reg_data[i + 1]; ++ ++ if (reg_addr == AW88399_DSPVCALB_REG) { ++ aw88399->vcalb_init_val = reg_val; ++ continue; ++ } ++ ++ if (reg_addr == AW88399_SYSCTRL_REG) { ++ if (reg_val & (~AW88399_DSPBY_MASK)) ++ aw_dev->dsp_cfg = AW88399_DEV_DSP_BYPASS; ++ else ++ aw_dev->dsp_cfg = AW88399_DEV_DSP_WORK; ++ ++ reg_val &= (AW88399_HMUTE_MASK | AW88399_PWDN_MASK | ++ AW88399_DSPBY_MASK); ++ reg_val |= (AW88399_HMUTE_ENABLE_VALUE | AW88399_PWDN_POWER_DOWN_VALUE | ++ AW88399_DSPBY_BYPASS_VALUE); ++ } ++ ++ if (reg_addr == AW88399_I2SCTRL3_REG) { ++ reg_val &= AW88399_I2STXEN_MASK; ++ reg_val |= AW88399_I2STXEN_DISABLE_VALUE; ++ } ++ ++ if (reg_addr == AW88399_SYSCTRL2_REG) { ++ read_vol = (reg_val & (~AW88399_VOL_MASK)) >> ++ AW88399_VOL_START_BIT; ++ aw_dev->volume_desc.init_volume = read_vol; ++ } ++ ++ if (reg_addr == AW88399_DBGCTRL_REG) { ++ if ((reg_val & (~AW88399_EF_DBMD_MASK)) == AW88399_EF_DBMD_OR_VALUE) ++ aw88399->check_val = AW_EF_OR_CHECK; ++ else ++ aw88399->check_val = AW_EF_AND_CHECK; ++ ++ aw88399->dither_st = reg_val & (~AW88399_DITHER_EN_MASK); ++ } ++ ++ if (reg_addr == AW88399_CRCCTRL_REG) ++ aw88399->crc_init_val = reg_val; ++ ++ ret = regmap_write(aw_dev->regmap, reg_addr, reg_val); ++ if (ret) ++ return ret; ++ } ++ ++ aw_dev_pwd(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ ++ aw_dev_get_cur_mode_st(aw_dev); ++ ++ if (aw_dev->prof_cur != aw_dev->prof_index) ++ vol_desc->ctl_volume = 0; ++ else ++ aw_dev_set_volume(aw_dev, vol_desc->ctl_volume); ++ ++ return 0; ++} ++ ++static int aw_dev_reg_update(struct aw88399 *aw88399, ++ unsigned char *data, unsigned int len) ++{ ++ int ret; ++ ++ if (!len || !data) { ++ dev_err(aw88399->aw_pa->dev, "reg data is null or len is 0"); ++ return -EINVAL; ++ } ++ ++ ret = aw_dev_update_reg_container(aw88399, data, len); ++ if (ret) ++ dev_err(aw88399->aw_pa->dev, "reg update failed"); ++ ++ return ret; ++} ++ ++int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name) ++{ ++ struct aw_prof_info *prof_info = &aw_dev->prof_info; ++ struct aw_prof_desc *prof_desc; ++ ++ if ((index >= aw_dev->prof_info.count) || (index < 0)) { ++ dev_err(aw_dev->dev, "index[%d] overflow count[%d]", ++ index, aw_dev->prof_info.count); ++ return -EINVAL; ++ } ++ ++ prof_desc = &aw_dev->prof_info.prof_desc[index]; ++ ++ *prof_name = prof_info->prof_name_list[prof_desc->id]; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw88399_dev_get_prof_name); ++ ++static int aw88399_dev_get_prof_data(struct aw_device *aw_dev, int index, ++ struct aw_prof_desc **prof_desc) ++{ ++ if ((index >= aw_dev->prof_info.count) || (index < 0)) { ++ dev_err(aw_dev->dev, "%s: index[%d] overflow count[%d]\n", ++ __func__, index, aw_dev->prof_info.count); ++ return -EINVAL; ++ } ++ ++ *prof_desc = &aw_dev->prof_info.prof_desc[index]; ++ ++ return 0; ++} ++ ++static int aw88399_dev_fw_update(struct aw88399 *aw88399, bool up_dsp_fw_en, bool force_up_en) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ struct aw_prof_desc *prof_index_desc; ++ struct aw_sec_data_desc *sec_desc; ++ char *prof_name; ++ int ret; ++ ++ if ((aw_dev->prof_cur == aw_dev->prof_index) && ++ (force_up_en == AW88399_FORCE_UPDATE_OFF)) { ++ dev_dbg(aw_dev->dev, "scene no change, not update"); ++ return 0; ++ } ++ ++ if (aw_dev->fw_status == AW88399_DEV_FW_FAILED) { ++ dev_err(aw_dev->dev, "fw status[%d] error", aw_dev->fw_status); ++ return -EPERM; ++ } ++ ++ ret = aw88399_dev_get_prof_name(aw_dev, aw_dev->prof_index, &prof_name); ++ if (ret) ++ return ret; ++ ++ dev_dbg(aw_dev->dev, "start update %s", prof_name); ++ ++ ret = aw88399_dev_get_prof_data(aw_dev, aw_dev->prof_index, &prof_index_desc); ++ if (ret) ++ return ret; ++ ++ /* update reg */ ++ sec_desc = prof_index_desc->sec_desc; ++ ret = aw_dev_reg_update(aw88399, sec_desc[AW88395_DATA_TYPE_REG].data, ++ sec_desc[AW88395_DATA_TYPE_REG].len); ++ if (ret) { ++ dev_err(aw_dev->dev, "update reg failed"); ++ return ret; ++ } ++ ++ aw88399_dev_mute(aw_dev, true); ++ ++ if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) ++ aw_dev_dsp_enable(aw_dev, false); ++ ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); ++ ++ ret = aw_dev_check_sram(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "check sram failed"); ++ goto error; ++ } ++ ++ if (up_dsp_fw_en) { ++ dev_dbg(aw_dev->dev, "fw_ver: [%x]", prof_index_desc->fw_ver); ++ ret = aw_dev_dsp_update_fw(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_FW].data, ++ sec_desc[AW88395_DATA_TYPE_DSP_FW].len); ++ if (ret) { ++ dev_err(aw_dev->dev, "update dsp fw failed"); ++ goto error; ++ } ++ } ++ ++ /* update dsp config */ ++ ret = aw_dev_dsp_update_cfg(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_CFG].data, ++ sec_desc[AW88395_DATA_TYPE_DSP_CFG].len); ++ if (ret) { ++ dev_err(aw_dev->dev, "update dsp cfg failed"); ++ goto error; ++ } ++ ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); ++ ++ aw_dev->prof_cur = aw_dev->prof_index; ++ ++ return 0; ++ ++error: ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); ++ return ret; ++} ++ ++static void aw88399_start_pa(struct aw88399 *aw88399) ++{ ++ int ret, i; ++ ++ for (i = 0; i < AW88399_START_RETRIES; i++) { ++ ret = aw88399_dev_start(aw88399); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "aw88399 device start failed. retry = %d", i); ++ ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_ON, true); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "fw update failed"); ++ continue; ++ } ++ } else { ++ dev_dbg(aw88399->aw_pa->dev, "start success\n"); ++ break; ++ } ++ } ++} ++ ++void aw88399_startup_work(struct work_struct *work) ++{ ++ struct aw88399 *aw88399 = ++ container_of(work, struct aw88399, start_work.work); ++ ++ mutex_lock(&aw88399->lock); ++ aw88399_start_pa(aw88399); ++ mutex_unlock(&aw88399->lock); ++} ++EXPORT_SYMBOL_GPL(aw88399_startup_work); ++ ++void aw88399_start(struct aw88399 *aw88399, bool sync_start) ++{ ++ int ret; ++ ++ if (aw88399->aw_pa->fw_status != AW88399_DEV_FW_OK) ++ return; ++ ++ if (aw88399->aw_pa->status == AW88399_DEV_PW_ON) ++ return; ++ ++ ret = aw88399_dev_fw_update(aw88399, aw88399->fw_needs_reload ? ++ AW88399_DSP_FW_UPDATE_ON : AW88399_DSP_FW_UPDATE_OFF, true); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "fw update failed."); ++ return; ++ } ++ ++ aw88399->fw_needs_reload = false; ++ ++ if (sync_start == AW88399_SYNC_START) ++ aw88399_start_pa(aw88399); ++ else ++ queue_delayed_work(system_dfl_wq, ++ &aw88399->start_work, ++ AW88399_START_WORK_DELAY_MS); ++} ++EXPORT_SYMBOL_GPL(aw88399_start); ++ ++static int aw_dev_check_sysint(struct aw_device *aw_dev) ++{ ++ u16 reg_val; ++ ++ aw_dev_get_int_status(aw_dev, ®_val); ++ if (reg_val & AW88399_BIT_SYSINT_CHECK) { ++ dev_err(aw_dev->dev, "pa stop check fail:0x%04x", reg_val); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++int aw88399_stop(struct aw_device *aw_dev) ++{ ++ struct aw_sec_data_desc *dsp_cfg = ++ &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_CFG]; ++ struct aw_sec_data_desc *dsp_fw = ++ &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_FW]; ++ int int_st; ++ ++ if (aw_dev->status == AW88399_DEV_PW_OFF) { ++ dev_dbg(aw_dev->dev, "already power off"); ++ return 0; ++ } ++ ++ aw_dev->status = AW88399_DEV_PW_OFF; ++ ++ aw88399_dev_mute(aw_dev, true); ++ usleep_range(AW88399_4000_US, AW88399_4000_US + 100); ++ ++ aw_dev_i2s_tx_enable(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 100); ++ ++ int_st = aw_dev_check_sysint(aw_dev); ++ ++ aw_dev_dsp_enable(aw_dev, false); ++ ++ aw_dev_amppd(aw_dev, true); ++ ++ if (int_st) { ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); ++ aw_dev_dsp_update_fw(aw_dev, dsp_fw->data, dsp_fw->len); ++ aw_dev_dsp_update_cfg(aw_dev, dsp_cfg->data, dsp_cfg->len); ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); ++ } ++ ++ aw_dev_pwd(aw_dev, true); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw88399_stop); ++ ++static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ int ret; ++ ++ ret = aw88395_dev_cfg_load(aw_dev, aw_cfg); ++ if (ret) { ++ dev_err(aw_dev->dev, "aw_dev acf parse failed"); ++ return -EINVAL; ++ } ++ aw_dev->fade_in_time = AW88399_1000_US / 10; ++ aw_dev->fade_out_time = AW88399_1000_US >> 1; ++ aw_dev->prof_cur = aw_dev->prof_info.prof_desc[0].id; ++ aw_dev->prof_index = aw_dev->prof_info.prof_desc[0].id; ++ ++ ret = aw88399_dev_fw_update(aw88399, AW88399_FORCE_UPDATE_ON, AW88399_DSP_FW_UPDATE_ON); ++ if (ret) { ++ dev_err(aw_dev->dev, "fw update failed ret = %d\n", ret); ++ return ret; ++ } ++ ++ aw88399_dev_mute(aw_dev, true); ++ ++ /* close tx feedback */ ++ aw_dev_i2s_tx_enable(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 100); ++ ++ /* enable amppd */ ++ aw_dev_amppd(aw_dev, true); ++ ++ /* close dsp */ ++ aw_dev_dsp_enable(aw_dev, false); ++ /* set power down */ ++ aw_dev_pwd(aw_dev, true); ++ ++ return 0; ++} ++ ++int aw88399_request_firmware_file(struct aw88399 *aw88399) ++{ ++ const struct firmware *cont __free(firmware) = NULL; ++ int ret; ++ ++ aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; ++ ++ ret = request_firmware(&cont, AW88399_ACF_FILE, aw88399->aw_pa->dev); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "request [%s] failed!", AW88399_ACF_FILE); ++ return ret; ++ } ++ ++ dev_dbg(aw88399->aw_pa->dev, "loaded %s - size: %zu\n", ++ AW88399_ACF_FILE, cont ? cont->size : 0); ++ ++ aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, ++ struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); ++ if (!aw88399->aw_cfg) ++ return -ENOMEM; ++ ++ aw88399->aw_cfg->len = (int)cont->size; ++ memcpy(aw88399->aw_cfg->data, cont->data, cont->size); ++ ++ ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "load [%s] failed!", AW88399_ACF_FILE); ++ return ret; ++ } ++ ++ mutex_lock(&aw88399->lock); ++ /* aw device init */ ++ ret = aw88399_dev_init(aw88399, aw88399->aw_cfg); ++ if (ret) ++ dev_err(aw88399->aw_pa->dev, "dev init failed"); ++ mutex_unlock(&aw88399->lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aw88399_request_firmware_file); ++ ++void aw88399_hw_reset(struct aw88399 *aw88399) ++{ ++ if (aw88399->reset_gpio) { ++ gpiod_set_value_cansleep(aw88399->reset_gpio, 1); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ gpiod_set_value_cansleep(aw88399->reset_gpio, 0); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ gpiod_set_value_cansleep(aw88399->reset_gpio, 1); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ } ++} ++EXPORT_SYMBOL_GPL(aw88399_hw_reset); ++ ++static void aw88399_parse_channel_dt(struct aw_device *aw_dev) ++{ ++ struct device_node *np = aw_dev->dev->of_node; ++ u32 channel_value; ++ int ret; ++ ++ ret = of_property_read_u32(np, "awinic,audio-channel", &channel_value); ++ if (ret) { ++ /* ++ * On ACPI systems, DT properties don't exist. Derive channel ++ * from I2C address: 0x34 -> channel 0 (left), 0x35 -> channel 1 (right) ++ */ ++ aw_dev->channel = aw_dev->i2c->addr - 0x34; ++ dev_dbg(aw_dev->dev, ++ "DT channel property not found, using I2C address-based channel %d (addr 0x%02x)\n", ++ aw_dev->channel, aw_dev->i2c->addr); ++ return; ++ } ++ aw_dev->channel = channel_value; ++} ++ ++int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap) ++{ ++ struct aw_device *aw_dev; ++ unsigned int chip_id; ++ int ret; ++ ++ ret = regmap_read(regmap, AW88399_ID_REG, &chip_id); ++ if (ret) { ++ dev_err(&i2c->dev, "%s read chipid error. ret = %d", __func__, ret); ++ return ret; ++ } ++ if (chip_id != AW88399_CHIP_ID) { ++ dev_err(&i2c->dev, "unsupported device"); ++ return -ENXIO; ++ } ++ dev_dbg(&i2c->dev, "chip id = %x\n", chip_id); ++ ++ aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL); ++ if (!aw_dev) ++ return -ENOMEM; ++ aw88399->aw_pa = aw_dev; ++ ++ aw_dev->i2c = i2c; ++ aw_dev->dev = &i2c->dev; ++ aw_dev->regmap = regmap; ++ mutex_init(&aw_dev->dsp_lock); ++ ++ aw_dev->chip_id = chip_id; ++ aw_dev->acf = NULL; ++ aw_dev->prof_info.prof_desc = NULL; ++ aw_dev->prof_info.count = 0; ++ aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID; ++ aw_dev->channel = AW88399_DEV_DEFAULT_CH; ++ aw_dev->fw_status = AW88399_DEV_FW_FAILED; ++ ++ aw_dev->fade_step = AW88399_VOLUME_STEP_DB; ++ aw_dev->volume_desc.ctl_volume = AW88399_VOL_DEFAULT_VALUE; ++ ++ aw88399_parse_channel_dt(aw_dev); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw88399_init); ++ ++void aw88399_dev_set_channel(struct aw88399 *aw88399, int channel) ++{ ++ aw88399->aw_pa->channel = channel; ++} ++EXPORT_SYMBOL_GPL(aw88399_dev_set_channel); ++ ++MODULE_DESCRIPTION("AW88399 common device library"); ++MODULE_LICENSE("GPL"); +diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c +--- a/sound/soc/codecs/aw88399.c ++++ b/sound/soc/codecs/aw88399.c +@@ -7,1220 +7,16 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +-#include +-#include + #include + #include + #include + #include "aw88399.h" + #include "aw88395/aw88395_device.h" + +-static const struct regmap_config aw88399_remap_config = { +- .val_bits = 16, +- .reg_bits = 8, +- .max_register = AW88399_REG_MAX, +- .reg_format_endian = REGMAP_ENDIAN_LITTLE, +- .val_format_endian = REGMAP_ENDIAN_BIG, +-}; +- +-static void aw_dev_pwd(struct aw_device *aw_dev, bool pwd) +-{ +- int ret; +- +- if (pwd) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_PWDN_MASK, AW88399_PWDN_POWER_DOWN_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_PWDN_MASK, AW88399_PWDN_WORKING_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed", __func__); +-} +- +-static void aw_dev_get_int_status(struct aw_device *aw_dev, unsigned short *int_status) +-{ +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSINT_REG, ®_val); +- if (ret) +- dev_err(aw_dev->dev, "read interrupt reg fail, ret=%d", ret); +- else +- *int_status = reg_val; +- +- dev_dbg(aw_dev->dev, "read interrupt reg=0x%04x", *int_status); +-} +- +-static void aw_dev_clear_int_status(struct aw_device *aw_dev) +-{ +- u16 int_status; +- +- /* read int status and clear */ +- aw_dev_get_int_status(aw_dev, &int_status); +- /* make sure int status is clear */ +- aw_dev_get_int_status(aw_dev, &int_status); +- if (int_status) +- dev_dbg(aw_dev->dev, "int status(%d) is not cleaned.\n", int_status); +-} +- +-static int aw_dev_get_iis_status(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); +- if (ret) +- return ret; +- if ((reg_val & AW88399_BIT_PLL_CHECK) != AW88399_BIT_PLL_CHECK) { +- dev_err(aw_dev->dev, "check pll lock fail, reg_val:0x%04x", reg_val); +- return -EINVAL; +- } +- +- return 0; +-} +- +-static int aw_dev_check_mode1_pll(struct aw_device *aw_dev) +-{ +- int ret, i; +- +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = aw_dev_get_iis_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode1 iis signal check error"); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- return 0; +- } +- } +- +- return -EPERM; +-} +- +-static int aw_dev_check_mode2_pll(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- int ret, i; +- +- ret = regmap_read(aw_dev->regmap, AW88399_PLLCTRL2_REG, ®_val); +- if (ret) +- return ret; +- +- reg_val &= (~AW88399_CCO_MUX_MASK); +- if (reg_val == AW88399_CCO_MUX_DIVIDED_VALUE) { +- dev_dbg(aw_dev->dev, "CCO_MUX is already divider"); +- return -EPERM; +- } +- +- /* change mode2 */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, +- ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_DIVIDED_VALUE); +- if (ret) +- return ret; +- +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = aw_dev_get_iis_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode2 iis signal check error"); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- break; +- } +- } +- +- /* change mode1 */ +- regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, +- ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_BYPASS_VALUE); +- if (ret == 0) { +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = aw_dev_get_iis_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode2 switch to mode1, iis signal check error"); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- break; +- } +- } +- } +- +- return ret; +-} +- +-static int aw_dev_check_syspll(struct aw_device *aw_dev) +-{ +- int ret; +- +- ret = aw_dev_check_mode1_pll(aw_dev); +- if (ret) { +- dev_dbg(aw_dev->dev, "mode1 check iis failed try switch to mode2 check"); +- ret = aw_dev_check_mode2_pll(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode2 check iis failed"); +- return ret; +- } +- } +- +- return 0; +-} +- +-static int aw_dev_check_sysst(struct aw_device *aw_dev) +-{ +- unsigned int check_val; +- unsigned int reg_val; +- int ret, i; +- +- ret = regmap_read(aw_dev->regmap, AW88399_PWMCTRL3_REG, ®_val); +- if (ret) +- return ret; +- +- if (reg_val & (~AW88399_NOISE_GATE_EN_MASK)) +- check_val = AW88399_BIT_SYSST_NOSWS_CHECK; +- else +- check_val = AW88399_BIT_SYSST_SWS_CHECK; +- +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); +- if (ret) +- return ret; +- +- if ((reg_val & (~AW88399_BIT_SYSST_CHECK_MASK) & check_val) != check_val) { +- dev_err(aw_dev->dev, "check sysst fail, cnt=%d, reg_val=0x%04x, check:0x%x", +- i, reg_val, AW88399_BIT_SYSST_NOSWS_CHECK); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- return 0; +- } +- } +- +- return -EPERM; +-} +- +-static void aw_dev_amppd(struct aw_device *aw_dev, bool amppd) +-{ +- int ret; +- +- if (amppd) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_AMPPD_MASK, AW88399_AMPPD_POWER_DOWN_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_AMPPD_MASK, AW88399_AMPPD_WORKING_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed", __func__); +-} +- +-static void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable) +-{ +- int ret; +- +- if (is_enable) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_DSPBY_MASK, AW88399_DSPBY_WORKING_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_DSPBY_MASK, AW88399_DSPBY_BYPASS_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed\n", __func__); +-} +- +-static int aw88399_dev_get_icalk(struct aw88399 *aw88399, int16_t *icalk) +-{ +- uint16_t icalkh_val, icalkl_val, icalk_val; +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRH4_REG, ®_val); +- if (ret) +- return ret; +- icalkh_val = reg_val & (~AW88399_EF_ISN_GESLP_H_MASK); +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRL4_REG, ®_val); +- if (ret) +- return ret; +- icalkl_val = reg_val & (~AW88399_EF_ISN_GESLP_L_MASK); +- +- if (aw88399->check_val == AW_EF_AND_CHECK) +- icalk_val = icalkh_val & icalkl_val; +- else +- icalk_val = icalkh_val | icalkl_val; +- +- if (icalk_val & (~AW88399_EF_ISN_GESLP_SIGN_MASK)) +- icalk_val = icalk_val | AW88399_EF_ISN_GESLP_SIGN_NEG; +- *icalk = (int16_t)icalk_val; +- +- return 0; +-} +- +-static int aw88399_dev_get_vcalk(struct aw88399 *aw88399, int16_t *vcalk) +-{ +- uint16_t vcalkh_val, vcalkl_val, vcalk_val; +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRH3_REG, ®_val); +- if (ret) +- return ret; +- +- vcalkh_val = reg_val & (~AW88399_EF_VSN_GESLP_H_MASK); +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRL3_REG, ®_val); +- if (ret) +- return ret; +- +- vcalkl_val = reg_val & (~AW88399_EF_VSN_GESLP_L_MASK); +- +- if (aw88399->check_val == AW_EF_AND_CHECK) +- vcalk_val = vcalkh_val & vcalkl_val; +- else +- vcalk_val = vcalkh_val | vcalkl_val; +- +- if (vcalk_val & AW88399_EF_VSN_GESLP_SIGN_MASK) +- vcalk_val = vcalk_val | AW88399_EF_VSN_GESLP_SIGN_NEG; +- *vcalk = (int16_t)vcalk_val; +- +- return 0; +-} +- +-static int aw88399_dev_get_internal_vcalk(struct aw88399 *aw88399, int16_t *vcalk) +-{ +- uint16_t vcalkh_val, vcalkl_val, vcalk_val; +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRH2_REG, ®_val); +- if (ret) +- return ret; +- vcalkh_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_H_MASK); +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRL2_REG, ®_val); +- if (ret) +- return ret; +- vcalkl_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_L_MASK); +- +- if (aw88399->check_val == AW_EF_AND_CHECK) +- vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) & +- (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); +- else +- vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) | +- (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); +- +- if (vcalk_val & (~AW88399_TEM4_SIGN_MASK)) +- vcalk_val = vcalk_val | AW88399_TEM4_SIGN_NEG; +- +- *vcalk = (int16_t)vcalk_val; +- +- return 0; +-} +- +-static int aw_dev_set_vcalb(struct aw88399 *aw88399) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int vsense_select, vsense_value; +- int32_t ical_k, vcal_k, vcalb; +- int16_t icalk, vcalk; +- uint16_t reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_VSNCTRL1_REG, &vsense_value); +- if (ret) +- return ret; +- +- vsense_select = vsense_value & (~AW88399_VDSEL_MASK); +- +- ret = aw88399_dev_get_icalk(aw88399, &icalk); +- if (ret) { +- dev_err(aw_dev->dev, "get icalk failed\n"); +- return ret; +- } +- +- ical_k = icalk * AW88399_ICABLK_FACTOR + AW88399_CABL_BASE_VALUE; +- +- switch (vsense_select) { +- case AW88399_DEV_VDSEL_VSENSE: +- ret = aw88399_dev_get_vcalk(aw88399, &vcalk); +- vcal_k = vcalk * AW88399_VCABLK_FACTOR + AW88399_CABL_BASE_VALUE; +- vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_FACTOR / AW88399_ISCAL_FACTOR * +- ical_k / vcal_k * aw88399->vcalb_init_val; +- break; +- case AW88399_DEV_VDSEL_DAC: +- ret = aw88399_dev_get_internal_vcalk(aw88399, &vcalk); +- vcal_k = vcalk * AW88399_VCABLK_DAC_FACTOR + AW88399_CABL_BASE_VALUE; +- vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_DAC_FACTOR / +- AW88399_ISCAL_DAC_FACTOR * ical_k / +- vcal_k * aw88399->vcalb_init_val; +- break; +- default: +- dev_err(aw_dev->dev, "%s: unsupported vsense\n", __func__); +- ret = -EINVAL; +- break; +- } +- if (ret) +- return ret; +- +- vcalb = vcalb >> AW88399_VCALB_ADJ_FACTOR; +- reg_val = (uint32_t)vcalb; +- +- regmap_write(aw_dev->regmap, AW88399_DSPVCALB_REG, reg_val); +- +- return 0; +-} +- +-static int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc) +-{ +- struct aw_device *aw_dev = +- container_of(cali_desc, struct aw_device, cali_desc); +- uint16_t re_lbits, re_hbits; +- u32 cali_re; +- int ret; +- +- if ((aw_dev->cali_desc.cali_re >= AW88399_CALI_RE_MAX) || +- (aw_dev->cali_desc.cali_re <= AW88399_CALI_RE_MIN)) +- return -EINVAL; +- +- cali_re = AW88399_SHOW_RE_TO_DSP_RE((aw_dev->cali_desc.cali_re + +- aw_dev->cali_desc.ra), AW88399_DSP_RE_SHIFT); +- +- re_hbits = (cali_re & (~AW88399_CALI_RE_HBITS_MASK)) >> AW88399_CALI_RE_HBITS_SHIFT; +- re_lbits = (cali_re & (~AW88399_CALI_RE_LBITS_MASK)) >> AW88399_CALI_RE_LBITS_SHIFT; +- +- ret = regmap_write(aw_dev->regmap, AW88399_ACR1_REG, re_hbits); +- if (ret) { +- dev_err(aw_dev->dev, "set cali re error"); +- return ret; +- } +- +- ret = regmap_write(aw_dev->regmap, AW88399_ACR2_REG, re_lbits); +- if (ret) +- dev_err(aw_dev->dev, "set cali re error"); +- +- return ret; +-} +- +-static int aw_dev_fw_crc_check(struct aw_device *aw_dev) +-{ +- uint16_t check_val, fw_len_val; +- unsigned int reg_val; +- int ret; +- +- /* calculate fw_end_addr */ +- fw_len_val = ((aw_dev->dsp_fw_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_FW_BASE_ADDR; +- +- /* write fw_end_addr to crc_end_addr */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_END_ADDR_MASK, fw_len_val); +- if (ret) +- return ret; +- /* enable fw crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_ENABLE_VALUE); +- +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- +- /* read crc check result */ +- regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); +- if (ret) +- return ret; +- +- check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; +- +- /* disable fw crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_DISABLE_VALUE); +- if (ret) +- return ret; +- +- if (check_val != AW88399_CRC_CHECK_PASS_VAL) { +- dev_err(aw_dev->dev, "%s failed, check_val 0x%x != 0x%x", +- __func__, check_val, AW88399_CRC_CHECK_PASS_VAL); +- ret = -EINVAL; +- } +- +- return ret; +-} +- +-static int aw_dev_cfg_crc_check(struct aw_device *aw_dev) +-{ +- uint16_t check_val, cfg_len_val; +- unsigned int reg_val; +- int ret; +- +- /* calculate cfg end addr */ +- cfg_len_val = ((aw_dev->dsp_cfg_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_CFG_BASE_ADDR; +- +- /* write cfg_end_addr to crc_end_addr */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_END_ADDR_MASK, cfg_len_val); +- if (ret) +- return ret; +- +- /* enable cfg crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_ENABLE_VALUE); +- if (ret) +- return ret; +- +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- +- /* read crc check result */ +- ret = regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); +- if (ret) +- return ret; +- +- check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; +- +- /* disable cfg crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_DISABLE_VALUE); +- if (ret) +- return ret; +- +- if (check_val != AW88399_CRC_CHECK_PASS_VAL) { +- dev_err(aw_dev->dev, "crc_check failed, check val 0x%x != 0x%x", +- check_val, AW88399_CRC_CHECK_PASS_VAL); +- ret = -EINVAL; +- } +- +- return ret; +-} +- +-static int aw_dev_hw_crc_check(struct aw88399 *aw88399) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- int ret; +- +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_BYPASS_VALUE); +- if (ret) +- return ret; +- +- ret = aw_dev_fw_crc_check(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "fw_crc_check failed\n"); +- goto crc_check_failed; +- } +- +- ret = aw_dev_cfg_crc_check(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "cfg_crc_check failed\n"); +- goto crc_check_failed; +- } +- +- ret = regmap_write(aw_dev->regmap, AW88399_CRCCTRL_REG, aw88399->crc_init_val); +- if (ret) +- return ret; +- +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); +- +- return ret; +- +-crc_check_failed: +- regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); +- return ret; +-} +- +-static void aw_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag) +-{ +- int ret; +- +- if (flag) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCTRL3_REG, +- ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_ENABLE_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_DISABLE_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed", __func__); +-} +- +-static int aw_dev_get_dsp_status(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_WDT_REG, ®_val); +- if (ret) +- return ret; +- if (!(reg_val & (~AW88399_WDT_CNT_MASK))) +- return -EPERM; +- +- return 0; +-} +- +-static int aw_dev_dsp_check(struct aw_device *aw_dev) +-{ +- int ret, i; +- +- switch (aw_dev->dsp_cfg) { +- case AW88399_DEV_DSP_BYPASS: +- dev_dbg(aw_dev->dev, "dsp bypass"); +- ret = 0; +- break; +- case AW88399_DEV_DSP_WORK: +- aw_dev_dsp_enable(aw_dev, false); +- aw_dev_dsp_enable(aw_dev, true); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- for (i = 0; i < AW88399_DEV_DSP_CHECK_MAX; i++) { +- ret = aw_dev_get_dsp_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "dsp wdt status error=%d", ret); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } +- } +- break; +- default: +- dev_err(aw_dev->dev, "unknown dsp cfg=%d", aw_dev->dsp_cfg); +- ret = -EINVAL; +- break; +- } +- +- return ret; +-} +- +-static int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value) +-{ +- struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; +- unsigned int reg_value; +- u16 real_value; +- int ret; +- +- real_value = min((value + vol_desc->init_volume), (unsigned int)AW88399_MUTE_VOL); +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL2_REG, ®_value); +- if (ret) +- return ret; +- +- dev_dbg(aw_dev->dev, "value 0x%x , reg:0x%x", value, real_value); +- +- real_value = (real_value << AW88399_VOL_START_BIT) | (reg_value & AW88399_VOL_MASK); +- +- ret = regmap_write(aw_dev->regmap, AW88399_SYSCTRL2_REG, real_value); +- +- return ret; +-} +- +-static void aw_dev_fade_in(struct aw_device *aw_dev) +-{ +- struct aw_volume_desc *desc = &aw_dev->volume_desc; +- u16 fade_in_vol = desc->ctl_volume; +- int fade_step = aw_dev->fade_step; +- int i; +- +- if (fade_step == 0 || aw_dev->fade_in_time == 0) { +- aw_dev_set_volume(aw_dev, fade_in_vol); +- return; +- } +- +- for (i = AW88399_MUTE_VOL; i >= fade_in_vol; i -= fade_step) { +- aw_dev_set_volume(aw_dev, i); +- usleep_range(aw_dev->fade_in_time, aw_dev->fade_in_time + 10); +- } +- +- if (i != fade_in_vol) +- aw_dev_set_volume(aw_dev, fade_in_vol); +-} +- +-static void aw_dev_fade_out(struct aw_device *aw_dev) +-{ +- struct aw_volume_desc *desc = &aw_dev->volume_desc; +- int fade_step = aw_dev->fade_step; +- int i; +- +- if (fade_step == 0 || aw_dev->fade_out_time == 0) { +- aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); +- return; +- } +- +- for (i = desc->ctl_volume; i <= AW88399_MUTE_VOL; i += fade_step) { +- aw_dev_set_volume(aw_dev, i); +- usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); +- } +- +- if (i != AW88399_MUTE_VOL) { +- aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); +- usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); +- } +-} +- +-static void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute) +-{ +- if (is_mute) { +- aw_dev_fade_out(aw_dev); +- regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_HMUTE_MASK, AW88399_HMUTE_ENABLE_VALUE); +- } else { +- regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_HMUTE_MASK, AW88399_HMUTE_DISABLE_VALUE); +- aw_dev_fade_in(aw_dev); +- } +-} +- +-static void aw88399_dev_set_dither(struct aw88399 *aw88399, bool dither) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- +- if (dither) +- regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_ENABLE_VALUE); +- else +- regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_DISABLE_VALUE); +-} +- +-static int aw88399_dev_start(struct aw88399 *aw88399) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- int ret; +- +- if (aw_dev->status == AW88399_DEV_PW_ON) { +- dev_dbg(aw_dev->dev, "already power on"); +- return 0; +- } +- +- aw88399_dev_set_dither(aw88399, false); +- +- /* power on */ +- aw_dev_pwd(aw_dev, false); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- +- ret = aw_dev_check_syspll(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "pll check failed cannot start"); +- goto pll_check_fail; +- } +- +- /* amppd on */ +- aw_dev_amppd(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 50); +- +- /* check i2s status */ +- ret = aw_dev_check_sysst(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "sysst check failed"); +- goto sysst_check_fail; +- } +- +- if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) { +- ret = aw_dev_hw_crc_check(aw88399); +- if (ret) { +- dev_err(aw_dev->dev, "dsp crc check failed"); +- goto crc_check_fail; +- } +- aw_dev_dsp_enable(aw_dev, false); +- aw_dev_set_vcalb(aw88399); +- aw_dev_update_cali_re(&aw_dev->cali_desc); +- +- ret = aw_dev_dsp_check(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "dsp status check failed"); +- goto dsp_check_fail; +- } +- } else { +- dev_dbg(aw_dev->dev, "start pa with dsp bypass"); +- } +- +- /* enable tx feedback */ +- aw_dev_i2s_tx_enable(aw_dev, true); +- +- if (aw88399->dither_st == AW88399_DITHER_EN_ENABLE_VALUE) +- aw88399_dev_set_dither(aw88399, true); +- +- /* close mute */ +- aw88399_dev_mute(aw_dev, false); +- /* clear inturrupt */ +- aw_dev_clear_int_status(aw_dev); +- aw_dev->status = AW88399_DEV_PW_ON; +- +- return 0; +- +-dsp_check_fail: +-crc_check_fail: +- aw_dev_dsp_enable(aw_dev, false); +-sysst_check_fail: +- aw_dev_clear_int_status(aw_dev); +- aw_dev_amppd(aw_dev, true); +-pll_check_fail: +- aw_dev_pwd(aw_dev, true); +- aw_dev->status = AW88399_DEV_PW_OFF; +- +- return ret; +-} +- +-static int aw_dev_dsp_update_container(struct aw_device *aw_dev, +- unsigned char *data, unsigned int len, unsigned short base) +-{ +- u32 tmp_len; +- int i, ret; +- +- ret = regmap_write(aw_dev->regmap, AW88399_DSPMADD_REG, base); +- if (ret) +- return ret; +- +- for (i = 0; i < len; i += AW88399_MAX_RAM_WRITE_BYTE_SIZE) { +- tmp_len = min(len - i, AW88399_MAX_RAM_WRITE_BYTE_SIZE); +- ret = regmap_raw_write(aw_dev->regmap, AW88399_DSPMDAT_REG, +- &data[i], tmp_len); +- if (ret) +- return ret; +- } +- +- return 0; +-} +- +-static int aw_dev_get_ra(struct aw_cali_desc *cali_desc) +-{ +- struct aw_device *aw_dev = +- container_of(cali_desc, struct aw_device, cali_desc); +- u32 dsp_ra; +- int ret; +- +- ret = aw_dev_dsp_read(aw_dev, AW88399_DSP_REG_CFG_ADPZ_RA, +- &dsp_ra, AW_DSP_32_DATA); +- if (ret) { +- dev_err(aw_dev->dev, "read ra error"); +- return ret; +- } +- +- cali_desc->ra = AW88399_DSP_RE_TO_SHOW_RE(dsp_ra, +- AW88399_DSP_RE_SHIFT); +- +- return 0; +-} +- +-static int aw_dev_dsp_update_cfg(struct aw_device *aw_dev, +- unsigned char *data, unsigned int len) +-{ +- int ret; +- +- dev_dbg(aw_dev->dev, "dsp config len:%d", len); +- +- if (!len || !data) { +- dev_err(aw_dev->dev, "dsp config data is null or len is 0"); +- return -EINVAL; +- } +- +- ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_CFG_ADDR); +- if (ret) +- return ret; +- +- aw_dev->dsp_cfg_len = len; +- +- ret = aw_dev_get_ra(&aw_dev->cali_desc); +- +- return ret; +-} +- +-static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, +- unsigned char *data, unsigned int len) +-{ +- int ret; +- +- dev_dbg(aw_dev->dev, "dsp firmware len:%d", len); +- +- if (!len || !data) { +- dev_err(aw_dev->dev, "dsp firmware data is null or len is 0"); +- return -EINVAL; +- } +- +- aw_dev->dsp_fw_len = len; +- ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_FW_ADDR); +- +- return ret; +-} +- +-static int aw_dev_check_sram(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- +- /* read dsp_rom_check_reg */ +- aw_dev_dsp_read(aw_dev, AW88399_DSP_ROM_CHECK_ADDR, ®_val, AW_DSP_16_DATA); +- if (reg_val != AW88399_DSP_ROM_CHECK_DATA) { +- dev_err(aw_dev->dev, "check dsp rom failed, read[0x%x] != check[0x%x]", +- reg_val, AW88399_DSP_ROM_CHECK_DATA); +- return -EPERM; +- } +- +- /* check dsp_cfg_base_addr */ +- aw_dev_dsp_write(aw_dev, AW88399_DSP_CFG_ADDR, +- AW88399_DSP_ODD_NUM_BIT_TEST, AW_DSP_16_DATA); +- aw_dev_dsp_read(aw_dev, AW88399_DSP_CFG_ADDR, ®_val, AW_DSP_16_DATA); +- if (reg_val != AW88399_DSP_ODD_NUM_BIT_TEST) { +- dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", +- reg_val, AW88399_DSP_ODD_NUM_BIT_TEST); +- return -EPERM; +- } +- +- return 0; +-} +- +-static void aw_dev_select_memclk(struct aw_device *aw_dev, unsigned char flag) +-{ +- int ret; +- +- switch (flag) { +- case AW88399_DEV_MEMCLK_PLL: +- ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_MEM_CLKSEL_MASK, +- AW88399_MEM_CLKSEL_DAPHCLK_VALUE); +- if (ret) +- dev_err(aw_dev->dev, "memclk select pll failed"); +- break; +- case AW88399_DEV_MEMCLK_OSC: +- ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_MEM_CLKSEL_MASK, +- AW88399_MEM_CLKSEL_OSCCLK_VALUE); +- if (ret) +- dev_err(aw_dev->dev, "memclk select OSC failed"); +- break; +- default: +- dev_err(aw_dev->dev, "unknown memclk config, flag=0x%x", flag); +- break; +- } +-} +- +-static void aw_dev_get_cur_mode_st(struct aw_device *aw_dev) +-{ +- struct aw_profctrl_desc *profctrl_desc = &aw_dev->profctrl_desc; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL_REG, ®_val); +- if (ret) { +- dev_dbg(aw_dev->dev, "%s failed", __func__); +- return; +- } +- if ((reg_val & (~AW88399_RCV_MODE_MASK)) == AW88399_RCV_MODE_RECEIVER_VALUE) +- profctrl_desc->cur_mode = AW88399_RCV_MODE; +- else +- profctrl_desc->cur_mode = AW88399_NOT_RCV_MODE; +-} +- +-static int aw_dev_update_reg_container(struct aw88399 *aw88399, +- unsigned char *data, unsigned int len) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; +- u16 read_vol, reg_val; +- int data_len, i, ret; +- int16_t *reg_data; +- u8 reg_addr; +- +- reg_data = (int16_t *)data; +- data_len = len >> 1; +- +- if (data_len & 0x1) { +- dev_err(aw_dev->dev, "data len:%d unsupported", data_len); +- return -EINVAL; +- } +- +- for (i = 0; i < data_len; i += 2) { +- reg_addr = reg_data[i]; +- reg_val = reg_data[i + 1]; +- +- if (reg_addr == AW88399_DSPVCALB_REG) { +- aw88399->vcalb_init_val = reg_val; +- continue; +- } +- +- if (reg_addr == AW88399_SYSCTRL_REG) { +- if (reg_val & (~AW88399_DSPBY_MASK)) +- aw_dev->dsp_cfg = AW88399_DEV_DSP_BYPASS; +- else +- aw_dev->dsp_cfg = AW88399_DEV_DSP_WORK; +- +- reg_val &= (AW88399_HMUTE_MASK | AW88399_PWDN_MASK | +- AW88399_DSPBY_MASK); +- reg_val |= (AW88399_HMUTE_ENABLE_VALUE | AW88399_PWDN_POWER_DOWN_VALUE | +- AW88399_DSPBY_BYPASS_VALUE); +- } +- +- if (reg_addr == AW88399_I2SCTRL3_REG) { +- reg_val &= AW88399_I2STXEN_MASK; +- reg_val |= AW88399_I2STXEN_DISABLE_VALUE; +- } +- +- if (reg_addr == AW88399_SYSCTRL2_REG) { +- read_vol = (reg_val & (~AW88399_VOL_MASK)) >> +- AW88399_VOL_START_BIT; +- aw_dev->volume_desc.init_volume = read_vol; +- } +- +- if (reg_addr == AW88399_DBGCTRL_REG) { +- if ((reg_val & (~AW88399_EF_DBMD_MASK)) == AW88399_EF_DBMD_OR_VALUE) +- aw88399->check_val = AW_EF_OR_CHECK; +- else +- aw88399->check_val = AW_EF_AND_CHECK; +- +- aw88399->dither_st = reg_val & (~AW88399_DITHER_EN_MASK); +- } +- +- if (reg_addr == AW88399_CRCCTRL_REG) +- aw88399->crc_init_val = reg_val; +- +- ret = regmap_write(aw_dev->regmap, reg_addr, reg_val); +- if (ret) +- return ret; +- } +- +- aw_dev_pwd(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- +- aw_dev_get_cur_mode_st(aw_dev); +- +- if (aw_dev->prof_cur != aw_dev->prof_index) +- vol_desc->ctl_volume = 0; +- else +- aw_dev_set_volume(aw_dev, vol_desc->ctl_volume); +- +- return 0; +-} +- +-static int aw_dev_reg_update(struct aw88399 *aw88399, +- unsigned char *data, unsigned int len) +-{ +- int ret; +- +- if (!len || !data) { +- dev_err(aw88399->aw_pa->dev, "reg data is null or len is 0"); +- return -EINVAL; +- } +- +- ret = aw_dev_update_reg_container(aw88399, data, len); +- if (ret) +- dev_err(aw88399->aw_pa->dev, "reg update failed"); +- +- return ret; +-} +- +-static int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name) +-{ +- struct aw_prof_info *prof_info = &aw_dev->prof_info; +- struct aw_prof_desc *prof_desc; +- +- if ((index >= aw_dev->prof_info.count) || (index < 0)) { +- dev_err(aw_dev->dev, "index[%d] overflow count[%d]", +- index, aw_dev->prof_info.count); +- return -EINVAL; +- } +- +- prof_desc = &aw_dev->prof_info.prof_desc[index]; +- +- *prof_name = prof_info->prof_name_list[prof_desc->id]; +- +- return 0; +-} +- +-static int aw88399_dev_get_prof_data(struct aw_device *aw_dev, int index, +- struct aw_prof_desc **prof_desc) +-{ +- if ((index >= aw_dev->prof_info.count) || (index < 0)) { +- dev_err(aw_dev->dev, "%s: index[%d] overflow count[%d]\n", +- __func__, index, aw_dev->prof_info.count); +- return -EINVAL; +- } +- +- *prof_desc = &aw_dev->prof_info.prof_desc[index]; +- +- return 0; +-} +- +-static int aw88399_dev_fw_update(struct aw88399 *aw88399, bool up_dsp_fw_en, bool force_up_en) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- struct aw_prof_desc *prof_index_desc; +- struct aw_sec_data_desc *sec_desc; +- char *prof_name; +- int ret; +- +- if ((aw_dev->prof_cur == aw_dev->prof_index) && +- (force_up_en == AW88399_FORCE_UPDATE_OFF)) { +- dev_dbg(aw_dev->dev, "scene no change, not update"); +- return 0; +- } +- +- if (aw_dev->fw_status == AW88399_DEV_FW_FAILED) { +- dev_err(aw_dev->dev, "fw status[%d] error", aw_dev->fw_status); +- return -EPERM; +- } +- +- ret = aw88399_dev_get_prof_name(aw_dev, aw_dev->prof_index, &prof_name); +- if (ret) +- return ret; +- +- dev_dbg(aw_dev->dev, "start update %s", prof_name); +- +- ret = aw88399_dev_get_prof_data(aw_dev, aw_dev->prof_index, &prof_index_desc); +- if (ret) +- return ret; +- +- /* update reg */ +- sec_desc = prof_index_desc->sec_desc; +- ret = aw_dev_reg_update(aw88399, sec_desc[AW88395_DATA_TYPE_REG].data, +- sec_desc[AW88395_DATA_TYPE_REG].len); +- if (ret) { +- dev_err(aw_dev->dev, "update reg failed"); +- return ret; +- } +- +- aw88399_dev_mute(aw_dev, true); +- +- if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) +- aw_dev_dsp_enable(aw_dev, false); +- +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); +- +- ret = aw_dev_check_sram(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "check sram failed"); +- goto error; +- } +- +- if (up_dsp_fw_en) { +- dev_dbg(aw_dev->dev, "fw_ver: [%x]", prof_index_desc->fw_ver); +- ret = aw_dev_dsp_update_fw(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_FW].data, +- sec_desc[AW88395_DATA_TYPE_DSP_FW].len); +- if (ret) { +- dev_err(aw_dev->dev, "update dsp fw failed"); +- goto error; +- } +- } +- +- /* update dsp config */ +- ret = aw_dev_dsp_update_cfg(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_CFG].data, +- sec_desc[AW88395_DATA_TYPE_DSP_CFG].len); +- if (ret) { +- dev_err(aw_dev->dev, "update dsp cfg failed"); +- goto error; +- } +- +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); +- +- aw_dev->prof_cur = aw_dev->prof_index; +- +- return 0; +- +-error: +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); +- return ret; +-} +- +-static void aw88399_start_pa(struct aw88399 *aw88399) +-{ +- int ret, i; +- +- for (i = 0; i < AW88399_START_RETRIES; i++) { +- ret = aw88399_dev_start(aw88399); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "aw88399 device start failed. retry = %d", i); +- ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_ON, true); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "fw update failed"); +- continue; +- } +- } else { +- dev_dbg(aw88399->aw_pa->dev, "start success\n"); +- break; +- } +- } +-} +- +-static void aw88399_startup_work(struct work_struct *work) +-{ +- struct aw88399 *aw88399 = +- container_of(work, struct aw88399, start_work.work); +- +- mutex_lock(&aw88399->lock); +- aw88399_start_pa(aw88399); +- mutex_unlock(&aw88399->lock); +-} +- +-static void aw88399_start(struct aw88399 *aw88399, bool sync_start) +-{ +- int ret; +- +- if (aw88399->aw_pa->fw_status != AW88399_DEV_FW_OK) +- return; +- +- if (aw88399->aw_pa->status == AW88399_DEV_PW_ON) +- return; +- +- ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_OFF, true); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "fw update failed."); +- return; +- } +- +- if (sync_start == AW88399_SYNC_START) +- aw88399_start_pa(aw88399); +- else +- queue_delayed_work(system_dfl_wq, +- &aw88399->start_work, +- AW88399_START_WORK_DELAY_MS); +-} +- +-static int aw_dev_check_sysint(struct aw_device *aw_dev) +-{ +- u16 reg_val; +- +- aw_dev_get_int_status(aw_dev, ®_val); +- if (reg_val & AW88399_BIT_SYSINT_CHECK) { +- dev_err(aw_dev->dev, "pa stop check fail:0x%04x", reg_val); +- return -EINVAL; +- } +- +- return 0; +-} +- +-static int aw88399_stop(struct aw_device *aw_dev) +-{ +- struct aw_sec_data_desc *dsp_cfg = +- &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_CFG]; +- struct aw_sec_data_desc *dsp_fw = +- &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_FW]; +- int int_st; +- +- if (aw_dev->status == AW88399_DEV_PW_OFF) { +- dev_dbg(aw_dev->dev, "already power off"); +- return 0; +- } +- +- aw_dev->status = AW88399_DEV_PW_OFF; +- +- aw88399_dev_mute(aw_dev, true); +- usleep_range(AW88399_4000_US, AW88399_4000_US + 100); +- +- aw_dev_i2s_tx_enable(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 100); +- +- int_st = aw_dev_check_sysint(aw_dev); +- +- aw_dev_dsp_enable(aw_dev, false); +- +- aw_dev_amppd(aw_dev, true); +- +- if (int_st) { +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); +- aw_dev_dsp_update_fw(aw_dev, dsp_fw->data, dsp_fw->len); +- aw_dev_dsp_update_cfg(aw_dev, dsp_cfg->data, dsp_cfg->len); +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); +- } +- +- aw_dev_pwd(aw_dev, true); +- +- return 0; +-} +- + static struct snd_soc_dai_driver aw88399_dai[] = { + { + .name = "aw88399-aif", +@@ -1702,11 +498,10 @@ static int aw88399_profile_set(struct snd_kcontrol *kcontrol, + struct aw88399 *aw88399 = snd_soc_component_get_drvdata(codec); + int ret; + +- mutex_lock(&aw88399->lock); ++ guard(mutex)(&aw88399->lock); + ret = aw88399_dev_set_profile_index(aw88399->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88399->lock); + return 0; + } + +@@ -1715,8 +510,6 @@ static int aw88399_profile_set(struct snd_kcontrol *kcontrol, + aw88399_start(aw88399, AW88399_SYNC_START); + } + +- mutex_unlock(&aw88399->lock); +- + return 1; + } + +@@ -1869,86 +662,6 @@ static int aw88399_calib_set(struct snd_kcontrol *kcontrol, + return 0; + } + +-static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- int ret; +- +- ret = aw88395_dev_cfg_load(aw_dev, aw_cfg); +- if (ret) { +- dev_err(aw_dev->dev, "aw_dev acf parse failed"); +- return -EINVAL; +- } +- aw_dev->fade_in_time = AW88399_1000_US / 10; +- aw_dev->fade_out_time = AW88399_1000_US >> 1; +- aw_dev->prof_cur = aw_dev->prof_info.prof_desc[0].id; +- aw_dev->prof_index = aw_dev->prof_info.prof_desc[0].id; +- +- ret = aw88399_dev_fw_update(aw88399, AW88399_FORCE_UPDATE_ON, AW88399_DSP_FW_UPDATE_ON); +- if (ret) { +- dev_err(aw_dev->dev, "fw update failed ret = %d\n", ret); +- return ret; +- } +- +- aw88399_dev_mute(aw_dev, true); +- +- /* close tx feedback */ +- aw_dev_i2s_tx_enable(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 100); +- +- /* enable amppd */ +- aw_dev_amppd(aw_dev, true); +- +- /* close dsp */ +- aw_dev_dsp_enable(aw_dev, false); +- /* set power down */ +- aw_dev_pwd(aw_dev, true); +- +- return 0; +-} +- +-static int aw88399_request_firmware_file(struct aw88399 *aw88399) +-{ +- const struct firmware *cont = NULL; +- int ret; +- +- aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; +- +- ret = request_firmware(&cont, AW88399_ACF_FILE, aw88399->aw_pa->dev); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "request [%s] failed!", AW88399_ACF_FILE); +- return ret; +- } +- +- dev_dbg(aw88399->aw_pa->dev, "loaded %s - size: %zu\n", +- AW88399_ACF_FILE, cont ? cont->size : 0); +- +- aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, +- struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw88399->aw_cfg) { +- release_firmware(cont); +- return -ENOMEM; +- } +- aw88399->aw_cfg->len = (int)cont->size; +- memcpy(aw88399->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); +- +- ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "load [%s] failed!", AW88399_ACF_FILE); +- return ret; +- } +- +- mutex_lock(&aw88399->lock); +- /* aw device init */ +- ret = aw88399_dev_init(aw88399, aw88399->aw_cfg); +- if (ret) +- dev_err(aw88399->aw_pa->dev, "dev init failed"); +- mutex_unlock(&aw88399->lock); +- +- return ret; +-} +- + static const struct snd_kcontrol_new aw88399_controls[] = { + SOC_SINGLE_EXT("PCM Playback Volume", AW88399_SYSCTRL2_REG, + 6, AW88399_MUTE_VOL, 0, aw88399_volume_get, +@@ -1975,7 +688,7 @@ static int aw88399_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88399 *aw88399 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88399->lock); ++ guard(mutex)(&aw88399->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88399_start(aw88399, AW88399_ASYNC_START); +@@ -1986,7 +699,6 @@ static int aw88399_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88399->lock); + + return 0; + } +@@ -2040,70 +752,6 @@ static const struct snd_soc_component_driver soc_codec_dev_aw88399 = { + .num_controls = ARRAY_SIZE(aw88399_controls), + }; + +-static void aw88399_hw_reset(struct aw88399 *aw88399) +-{ +- if (aw88399->reset_gpio) { +- gpiod_set_value_cansleep(aw88399->reset_gpio, 1); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- gpiod_set_value_cansleep(aw88399->reset_gpio, 0); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- gpiod_set_value_cansleep(aw88399->reset_gpio, 1); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- } +-} +- +-static void aw88399_parse_channel_dt(struct aw_device *aw_dev) +-{ +- struct device_node *np = aw_dev->dev->of_node; +- u32 channel_value; +- +- of_property_read_u32(np, "awinic,audio-channel", &channel_value); +- aw_dev->channel = channel_value; +-} +- +-static int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap) +-{ +- struct aw_device *aw_dev; +- unsigned int chip_id; +- int ret; +- +- ret = regmap_read(regmap, AW88399_ID_REG, &chip_id); +- if (ret) { +- dev_err(&i2c->dev, "%s read chipid error. ret = %d", __func__, ret); +- return ret; +- } +- if (chip_id != AW88399_CHIP_ID) { +- dev_err(&i2c->dev, "unsupported device"); +- return -ENXIO; +- } +- dev_dbg(&i2c->dev, "chip id = %x\n", chip_id); +- +- aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL); +- if (!aw_dev) +- return -ENOMEM; +- aw88399->aw_pa = aw_dev; +- +- aw_dev->i2c = i2c; +- aw_dev->dev = &i2c->dev; +- aw_dev->regmap = regmap; +- mutex_init(&aw_dev->dsp_lock); +- +- aw_dev->chip_id = chip_id; +- aw_dev->acf = NULL; +- aw_dev->prof_info.prof_desc = NULL; +- aw_dev->prof_info.count = 0; +- aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID; +- aw_dev->channel = AW88399_DEV_DEFAULT_CH; +- aw_dev->fw_status = AW88399_DEV_FW_FAILED; +- +- aw_dev->fade_step = AW88399_VOLUME_STEP_DB; +- aw_dev->volume_desc.ctl_volume = AW88399_VOL_DEFAULT_VALUE; +- +- aw88399_parse_channel_dt(aw_dev); +- +- return 0; +-} +- + static int aw88399_i2c_probe(struct i2c_client *i2c) + { + struct aw88399 *aw88399; +diff --git a/sound/soc/codecs/aw88399.h b/sound/soc/codecs/aw88399.h +--- a/sound/soc/codecs/aw88399.h ++++ b/sound/soc/codecs/aw88399.h +@@ -10,512 +10,10 @@ + #ifndef __AW88399_H__ + #define __AW88399_H__ + +-/* registers list */ +-#define AW88399_ID_REG (0x00) +-#define AW88399_SYSST_REG (0x01) +-#define AW88399_SYSINT_REG (0x02) +-#define AW88399_SYSINTM_REG (0x03) +-#define AW88399_SYSCTRL_REG (0x04) +-#define AW88399_SYSCTRL2_REG (0x05) +-#define AW88399_I2SCTRL1_REG (0x06) +-#define AW88399_I2SCTRL2_REG (0x07) +-#define AW88399_I2SCTRL3_REG (0x08) +-#define AW88399_DACCFG1_REG (0x09) +-#define AW88399_DACCFG2_REG (0x0A) +-#define AW88399_DACCFG3_REG (0x0B) +-#define AW88399_DACCFG4_REG (0x0C) +-#define AW88399_DACCFG5_REG (0x0D) +-#define AW88399_DACCFG6_REG (0x0E) +-#define AW88399_DACCFG7_REG (0x0F) +-#define AW88399_MPDCFG1_REG (0x10) +-#define AW88399_MPDCFG2_REG (0x11) +-#define AW88399_MPDCFG3_REG (0x12) +-#define AW88399_MPDCFG4_REG (0x13) +-#define AW88399_PWMCTRL1_REG (0x14) +-#define AW88399_PWMCTRL2_REG (0x15) +-#define AW88399_PWMCTRL3_REG (0x16) +-#define AW88399_I2SCFG1_REG (0x17) +-#define AW88399_DBGCTRL_REG (0x18) +-#define AW88399_HAGCST_REG (0x20) +-#define AW88399_VBAT_REG (0x21) +-#define AW88399_TEMP_REG (0x22) +-#define AW88399_PVDD_REG (0x23) +-#define AW88399_ISNDAT_REG (0x24) +-#define AW88399_VSNDAT_REG (0x25) +-#define AW88399_I2SINT_REG (0x26) +-#define AW88399_I2SCAPCNT_REG (0x27) +-#define AW88399_ANASTA1_REG (0x28) +-#define AW88399_ANASTA2_REG (0x29) +-#define AW88399_ANASTA3_REG (0x2A) +-#define AW88399_TESTDET_REG (0x2B) +-#define AW88399_DSMCFG1_REG (0x30) +-#define AW88399_DSMCFG2_REG (0x31) +-#define AW88399_DSMCFG3_REG (0x32) +-#define AW88399_DSMCFG4_REG (0x33) +-#define AW88399_DSMCFG5_REG (0x34) +-#define AW88399_DSMCFG6_REG (0x35) +-#define AW88399_DSMCFG7_REG (0x36) +-#define AW88399_DSMCFG8_REG (0x37) +-#define AW88399_TESTIN_REG (0x38) +-#define AW88399_TESTOUT_REG (0x39) +-#define AW88399_MEMTEST_REG (0x3A) +-#define AW88399_VSNCTRL1_REG (0x3B) +-#define AW88399_ISNCTRL1_REG (0x3C) +-#define AW88399_ISNCTRL2_REG (0x3D) +-#define AW88399_DSPMADD_REG (0x40) +-#define AW88399_DSPMDAT_REG (0x41) +-#define AW88399_WDT_REG (0x42) +-#define AW88399_ACR1_REG (0x43) +-#define AW88399_ACR2_REG (0x44) +-#define AW88399_ASR1_REG (0x45) +-#define AW88399_ASR2_REG (0x46) +-#define AW88399_DSPCFG_REG (0x47) +-#define AW88399_ASR3_REG (0x48) +-#define AW88399_ASR4_REG (0x49) +-#define AW88399_DSPVCALB_REG (0x4A) +-#define AW88399_CRCCTRL_REG (0x4B) +-#define AW88399_DSPDBG1_REG (0x4C) +-#define AW88399_DSPDBG2_REG (0x4D) +-#define AW88399_DSPDBG3_REG (0x4E) +-#define AW88399_PLLCTRL1_REG (0x50) +-#define AW88399_PLLCTRL2_REG (0x51) +-#define AW88399_PLLCTRL3_REG (0x52) +-#define AW88399_CDACTRL1_REG (0x53) +-#define AW88399_CDACTRL2_REG (0x54) +-#define AW88399_CDACTRL3_REG (0x55) +-#define AW88399_SADCCTRL1_REG (0x56) +-#define AW88399_SADCCTRL2_REG (0x57) +-#define AW88399_BOPCTRL1_REG (0x58) +-#define AW88399_BOPCTRL2_REG (0x5A) +-#define AW88399_BOPCTRL3_REG (0x5B) +-#define AW88399_BOPCTRL4_REG (0x5C) +-#define AW88399_BOPCTRL5_REG (0x5D) +-#define AW88399_BOPCTRL6_REG (0x5E) +-#define AW88399_BOPCTRL7_REG (0x5F) +-#define AW88399_BSTCTRL1_REG (0x60) +-#define AW88399_BSTCTRL2_REG (0x61) +-#define AW88399_BSTCTRL3_REG (0x62) +-#define AW88399_BSTCTRL4_REG (0x63) +-#define AW88399_BSTCTRL5_REG (0x64) +-#define AW88399_BSTCTRL6_REG (0x65) +-#define AW88399_BSTCTRL7_REG (0x66) +-#define AW88399_BSTCTRL8_REG (0x67) +-#define AW88399_BSTCTRL9_REG (0x68) +-#define AW88399_BSTCTRL10_REG (0x69) +-#define AW88399_CPCTRL_REG (0x6A) +-#define AW88399_EFWH_REG (0x6C) +-#define AW88399_EFWM2_REG (0x6D) +-#define AW88399_EFWM1_REG (0x6E) +-#define AW88399_EFWL_REG (0x6F) +-#define AW88399_TESTCTRL1_REG (0x70) +-#define AW88399_TESTCTRL2_REG (0x71) +-#define AW88399_EFCTRL1_REG (0x72) +-#define AW88399_EFCTRL2_REG (0x73) +-#define AW88399_EFRH4_REG (0x74) +-#define AW88399_EFRH3_REG (0x75) +-#define AW88399_EFRH2_REG (0x76) +-#define AW88399_EFRH1_REG (0x77) +-#define AW88399_EFRL4_REG (0x78) +-#define AW88399_EFRL3_REG (0x79) +-#define AW88399_EFRL2_REG (0x7A) +-#define AW88399_EFRL1_REG (0x7B) +-#define AW88399_TM_REG (0x7C) +-#define AW88399_TM2_REG (0x7D) +- +-#define AW88399_REG_MAX (0x7E) +-#define AW88399_MUTE_VOL (1023) +- +-#define AW88399_DSP_CFG_ADDR (0x9B00) +-#define AW88399_DSP_REG_CFG_ADPZ_RA (0x9B68) +-#define AW88399_DSP_FW_ADDR (0x8980) +-#define AW88399_DSP_ROM_CHECK_ADDR (0x1F40) +-#define AW88399_DSP_ROM_CHECK_DATA (0x4638) +- +-#define AW88399_CALI_RE_HBITS_MASK (~(0xFFFF0000)) +-#define AW88399_CALI_RE_HBITS_SHIFT (16) +- +-#define AW88399_CALI_RE_LBITS_MASK (~(0xFFFF)) +-#define AW88399_CALI_RE_LBITS_SHIFT (0) +- +-#define AW88399_I2STXEN_START_BIT (9) +-#define AW88399_I2STXEN_BITS_LEN (1) +-#define AW88399_I2STXEN_MASK \ +- (~(((1<> (shift)) +-#define AW88399_SHOW_RE_TO_DSP_RE(re, shift) (((re) << shift) / (1000)) +-#define AW88399_CRC_CHECK_PASS_VAL (0x4) +- +-#define AW88399_CRC_CFG_BASE_ADDR (0xD80) +-#define AW88399_CRC_FW_BASE_ADDR (0x4C0) +-#define AW88399_ACF_FILE "aw88399_acf.bin" +-#define AW88399_DEV_SYSST_CHECK_MAX (10) +-#define AW88399_CHIP_ID 0x2183 ++#include + + #define AW88399_I2C_NAME "aw88399" + +-#define AW88399_START_RETRIES (5) +-#define AW88399_START_WORK_DELAY_MS (0) +- + #define AW88399_RATES (SNDRV_PCM_RATE_8000_48000 | \ + SNDRV_PCM_RATE_96000) + #define AW88399_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ +@@ -550,80 +48,4 @@ + .put = profile_set, \ + } + +-enum { +- AW_EF_AND_CHECK = 0, +- AW_EF_OR_CHECK, +-}; +- +-enum { +- AW88399_DEV_VDSEL_DAC = 0, +- AW88399_DEV_VDSEL_VSENSE = 32, +-}; +- +-enum { +- AW88399_DSP_CRC_NA = 0, +- AW88399_DSP_CRC_OK = 1, +-}; +- +-enum { +- AW88399_DSP_FW_UPDATE_OFF = 0, +- AW88399_DSP_FW_UPDATE_ON = 1, +-}; +- +-enum { +- AW88399_FORCE_UPDATE_OFF = 0, +- AW88399_FORCE_UPDATE_ON = 1, +-}; +- +-enum { +- AW88399_1000_US = 1000, +- AW88399_2000_US = 2000, +- AW88399_3000_US = 3000, +- AW88399_4000_US = 4000, +-}; +- +-enum AW88399_DEV_STATUS { +- AW88399_DEV_PW_OFF = 0, +- AW88399_DEV_PW_ON, +-}; +- +-enum AW88399_DEV_FW_STATUS { +- AW88399_DEV_FW_FAILED = 0, +- AW88399_DEV_FW_OK, +-}; +- +-enum AW88399_DEV_MEMCLK { +- AW88399_DEV_MEMCLK_OSC = 0, +- AW88399_DEV_MEMCLK_PLL = 1, +-}; +- +-enum AW88399_DEV_DSP_CFG { +- AW88399_DEV_DSP_WORK = 0, +- AW88399_DEV_DSP_BYPASS = 1, +-}; +- +-enum { +- AW88399_NOT_RCV_MODE = 0, +- AW88399_RCV_MODE = 1, +-}; +- +-enum { +- AW88399_SYNC_START = 0, +- AW88399_ASYNC_START, +-}; +- +-struct aw88399 { +- struct aw_device *aw_pa; +- struct mutex lock; +- struct gpio_desc *reset_gpio; +- struct delayed_work start_work; +- struct regmap *regmap; +- struct aw_container *aw_cfg; +- +- unsigned int check_val; +- unsigned int crc_init_val; +- unsigned int vcalb_init_val; +- unsigned int dither_st; +-}; +- +-#endif ++#endif /* __AW88399_H__ */ +diff --git a/sound/soc/codecs/bt-sco.c b/sound/soc/codecs/bt-sco.c +--- a/sound/soc/codecs/bt-sco.c ++++ b/sound/soc/codecs/bt-sco.c +@@ -85,13 +85,9 @@ static int bt_sco_probe(struct platform_device *pdev) + } + + static const struct platform_device_id bt_sco_driver_ids[] = { +- { +- .name = "dfbmcs320", +- }, +- { +- .name = "bt-sco", +- }, +- {}, ++ { .name = "dfbmcs320" }, ++ { .name = "bt-sco" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, bt_sco_driver_ids); + +diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c +--- a/sound/soc/codecs/cpcap.c ++++ b/sound/soc/codecs/cpcap.c +@@ -26,14 +26,6 @@ + /* Register 9 - CPCAP_REG_INTS2 --- Interrupt Sense 2 */ + #define CPCAP_BIT_PTT_S 11 /* Push To Talk */ + +-/* Register 512 CPCAP_REG_VAUDIOC --- Audio Regulator and Bias Voltage */ +-#define CPCAP_BIT_AUDIO_LOW_PWR 6 +-#define CPCAP_BIT_AUD_LOWPWR_SPEED 5 +-#define CPCAP_BIT_VAUDIOPRISTBY 4 +-#define CPCAP_BIT_VAUDIO_MODE1 2 +-#define CPCAP_BIT_VAUDIO_MODE0 1 +-#define CPCAP_BIT_V_AUDIO_EN 0 +- + /* Register 513 CPCAP_REG_CC --- CODEC */ + #define CPCAP_BIT_CDC_CLK2 15 + #define CPCAP_BIT_CDC_CLK1 14 +@@ -239,7 +231,6 @@ struct cpcap_reg_info { + }; + + static const struct cpcap_reg_info cpcap_default_regs[] = { +- { CPCAP_REG_VAUDIOC, 0x003F, 0x0000 }, + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CDI, 0xBFFF, 0x0000 }, +@@ -1265,6 +1256,7 @@ static int cpcap_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_component *component = dai->component; + struct device *dev = component->dev; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); +@@ -1298,7 +1290,7 @@ static int cpcap_voice_hw_params(struct snd_pcm_substream *substream, + return err; + } + +- return 0; ++ return snd_soc_runtime_set_dai_fmt(rtd, rtd->dai_link->dai_fmt); + } + + static int cpcap_voice_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, +@@ -1391,121 +1383,8 @@ static int cpcap_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, + return 0; + } + +- +-/* +- * Configure codec for voice call if requested. +- * +- * We can configure most with snd_soc_dai_set_sysclk(), snd_soc_dai_set_fmt() +- * and snd_soc_dai_set_tdm_slot(). This function configures the rest of the +- * cpcap related hardware as CPU is not involved in the voice call. +- */ +-static int cpcap_voice_call(struct cpcap_audio *cpcap, struct snd_soc_dai *dai, +- bool voice_call) +-{ +- int mask, err; +- +- /* Modem to codec VAUDIO_MODE1 */ +- mask = BIT(CPCAP_BIT_VAUDIO_MODE1); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_VAUDIOC, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Clear MIC1_MUX for call */ +- mask = BIT(CPCAP_BIT_MIC1_MUX); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, +- mask, voice_call ? 0 : mask); +- if (err) +- return err; +- +- /* Set MIC2_MUX for call */ +- mask = BIT(CPCAP_BIT_MB_ON1L) | BIT(CPCAP_BIT_MB_ON1R) | +- BIT(CPCAP_BIT_MIC2_MUX) | BIT(CPCAP_BIT_MIC2_PGA_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Enable LDSP for call */ +- mask = BIT(CPCAP_BIT_A2_LDSP_L_EN) | BIT(CPCAP_BIT_A2_LDSP_R_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXOA, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Enable CPCAP_BIT_PGA_CDC_EN for call */ +- mask = BIT(CPCAP_BIT_PGA_CDC_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXCOA, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Unmute voice for call */ +- if (dai) { +- err = snd_soc_dai_digital_mute(dai, !voice_call, +- SNDRV_PCM_STREAM_PLAYBACK); +- if (err) +- return err; +- } +- +- /* Set modem to codec mic CDC and HPF for call */ +- mask = BIT(CPCAP_BIT_MIC2_CDC_EN) | BIT(CPCAP_BIT_CDC_EN_RX) | +- BIT(CPCAP_BIT_AUDOHPF_1) | BIT(CPCAP_BIT_AUDOHPF_0) | +- BIT(CPCAP_BIT_AUDIHPF_1) | BIT(CPCAP_BIT_AUDIHPF_0); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CC, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Enable modem to codec CDC for call*/ +- mask = BIT(CPCAP_BIT_CDC_CLK_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, +- mask, voice_call ? mask : 0); +- +- return err; +-} +- +-static int cpcap_voice_set_tdm_slot(struct snd_soc_dai *dai, +- unsigned int tx_mask, unsigned int rx_mask, +- int slots, int slot_width) +-{ +- struct snd_soc_component *component = dai->component; +- struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); +- int err, ts_mask, mask; +- bool voice_call; +- +- /* +- * Primitive test for voice call, probably needs more checks +- * later on for 16-bit calls detected, Bluetooth headset etc. +- */ +- if (tx_mask == 0 && rx_mask == 1 && slot_width == 8) +- voice_call = true; +- else +- voice_call = false; +- +- ts_mask = 0x7 << CPCAP_BIT_MIC2_TIMESLOT0; +- ts_mask |= 0x7 << CPCAP_BIT_MIC1_RX_TIMESLOT0; +- +- mask = (tx_mask & 0x7) << CPCAP_BIT_MIC2_TIMESLOT0; +- mask |= (rx_mask & 0x7) << CPCAP_BIT_MIC1_RX_TIMESLOT0; +- +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, +- ts_mask, mask); +- if (err) +- return err; +- +- err = cpcap_set_samprate(cpcap, CPCAP_DAI_VOICE, slot_width * 1000); +- if (err) +- return err; +- +- err = cpcap_voice_call(cpcap, dai, voice_call); +- if (err) +- return err; +- +- return 0; +-} +- +-static int cpcap_voice_set_mute(struct snd_soc_dai *dai, int mute, int direction) ++static int cpcap_voice_set_mute(struct snd_soc_dai *dai, ++ int mute, int direction) + { + struct snd_soc_component *component = dai->component; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); +@@ -1526,7 +1405,6 @@ static const struct snd_soc_dai_ops cpcap_dai_voice_ops = { + .hw_params = cpcap_voice_hw_params, + .set_sysclk = cpcap_voice_set_dai_sysclk, + .set_fmt = cpcap_voice_set_dai_fmt, +- .set_tdm_slot = cpcap_voice_set_tdm_slot, + .mute_stream = cpcap_voice_set_mute, + .no_capture_mute = 1, + }; +diff --git a/sound/soc/codecs/cros_ec_codec.c b/sound/soc/codecs/cros_ec_codec.c +--- a/sound/soc/codecs/cros_ec_codec.c ++++ b/sound/soc/codecs/cros_ec_codec.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -319,10 +320,18 @@ static int i2s_rx_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + (uint8_t *)&p, sizeof(p), NULL, 0); + } + ++static const u64 i2s_rx_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops i2s_rx_dai_ops = { + .hw_params = i2s_rx_hw_params, + .set_fmt = i2s_rx_set_fmt, + .set_bclk_ratio = i2s_rx_set_bclk_ratio, ++ .auto_selectable_formats = &i2s_rx_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static int i2s_rx_event(struct snd_soc_dapm_widget *w, +@@ -608,10 +617,10 @@ static void wov_copy_work(struct work_struct *w) + container_of(w, struct cros_ec_codec_priv, wov_copy_work.work); + int ret; + +- mutex_lock(&priv->wov_dma_lock); ++ guard(mutex)(&priv->wov_dma_lock); + if (!priv->wov_substream) { + dev_warn(priv->dev, "no pcm substream\n"); +- goto leave; ++ return; + } + + if (ec_codec_capable(priv, EC_CODEC_CAP_WOV_AUDIO_SHM)) +@@ -624,8 +633,6 @@ static void wov_copy_work(struct work_struct *w) + msecs_to_jiffies(10)); + else if (ret) + dev_err(priv->dev, "failed to read audio data\n"); +-leave: +- mutex_unlock(&priv->wov_dma_lock); + } + + static int wov_enable_get(struct snd_kcontrol *kcontrol, +@@ -895,12 +902,11 @@ static int wov_pcm_hw_params(struct snd_soc_component *component, + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->wov_dma_lock); ++ guard(mutex)(&priv->wov_dma_lock); + priv->wov_substream = substream; + priv->wov_rp = priv->wov_wp = 0; + priv->wov_dma_offset = 0; + priv->wov_burst_read = true; +- mutex_unlock(&priv->wov_dma_lock); + + return 0; + } +@@ -911,10 +917,10 @@ static int wov_pcm_hw_free(struct snd_soc_component *component, + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->wov_dma_lock); +- wov_queue_dequeue(priv, wov_queue_size(priv)); +- priv->wov_substream = NULL; +- mutex_unlock(&priv->wov_dma_lock); ++ scoped_guard(mutex, &priv->wov_dma_lock) { ++ wov_queue_dequeue(priv, wov_queue_size(priv)); ++ priv->wov_substream = NULL; ++ } + + cancel_delayed_work_sync(&priv->wov_copy_work); + +diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c +--- a/sound/soc/codecs/cs-amp-lib.c ++++ b/sound/soc/codecs/cs-amp-lib.c +@@ -7,6 +7,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -83,10 +84,12 @@ static int cs_amp_write_cal_coeff(struct cs_dsp *dsp, + KUNIT_STATIC_STUB_REDIRECT(cs_amp_write_cal_coeff, dsp, controls, ctl_name, val); + + if (IS_REACHABLE(CONFIG_FW_CS_DSP)) { +- mutex_lock(&dsp->pwr_lock); +- cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id); +- ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval)); +- mutex_unlock(&dsp->pwr_lock); ++ scoped_guard(mutex, &dsp->pwr_lock) { ++ cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, ++ controls->mem_region, ++ controls->alg_id); ++ ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval)); ++ } + + if (ret < 0) { + dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret); +diff --git a/sound/soc/codecs/cs35l32.c b/sound/soc/codecs/cs35l32.c +--- a/sound/soc/codecs/cs35l32.c ++++ b/sound/soc/codecs/cs35l32.c +@@ -538,7 +538,15 @@ static int cs35l32_runtime_resume(struct device *dev) + gpiod_set_value_cansleep(cs35l32->reset_gpio, 1); + + regcache_cache_only(cs35l32->regmap, false); +- regcache_sync(cs35l32->regmap); ++ ret = regcache_sync(cs35l32->regmap); ++ if (ret) { ++ regcache_cache_only(cs35l32->regmap, true); ++ regcache_mark_dirty(cs35l32->regmap); ++ gpiod_set_value_cansleep(cs35l32->reset_gpio, 0); ++ regulator_bulk_disable(ARRAY_SIZE(cs35l32->supplies), ++ cs35l32->supplies); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/cs35l56-i2c.c b/sound/soc/codecs/cs35l56-i2c.c +--- a/sound/soc/codecs/cs35l56-i2c.c ++++ b/sound/soc/codecs/cs35l56-i2c.c +@@ -51,15 +51,7 @@ static int cs35l56_i2c_probe(struct i2c_client *client) + return dev_err_probe(cs35l56->base.dev, ret, "Failed to allocate register map\n"); + } + +- ret = cs35l56_common_probe(cs35l56); +- if (ret != 0) +- return ret; +- +- ret = cs35l56_irq_request(&cs35l56->base, client->irq); +- if (ret < 0) +- cs35l56_remove(cs35l56); +- +- return ret; ++ return cs35l56_common_probe(cs35l56, client->irq); + } + + static void cs35l56_i2c_remove(struct i2c_client *client) +diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c +--- a/sound/soc/codecs/cs35l56-sdw.c ++++ b/sound/soc/codecs/cs35l56-sdw.c +@@ -231,7 +231,7 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral) + * a soft reset. + */ + if (cs35l56->base.init_done) +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); ++ cs35l56_unmask_soundwire_interrupts(cs35l56); + + out: + pm_runtime_put_autosuspend(cs35l56->base.dev); +@@ -240,47 +240,17 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral) + static int cs35l56_sdw_interrupt(struct sdw_slave *peripheral, + struct sdw_slave_intr_status *status) + { +- struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); +- +- /* SoundWire core holds our pm_runtime when calling this function. */ +- +- dev_dbg(cs35l56->base.dev, "int control_port=%#x\n", status->control_port); +- +- if ((status->control_port & SDW_SCP_INT1_IMPL_DEF) == 0) +- return 0; +- + /* +- * Prevent bus manager suspending and possibly issuing a +- * bus-reset before the queued work has run. ++ * The IRQ itself was handled through the regmap_irq handler, this is ++ * just clearing up the additional Cirrus SoundWire registers that are ++ * not covered by the SoundWire framework or the IRQ handler itself. + */ +- pm_runtime_get_noresume(cs35l56->base.dev); +- +- /* +- * Mask and clear until it has been handled. +- * None of the interrupts are time-critical so use the +- * power-efficient queue. +- */ +- cs35l56_mask_soundwire_interrupts(peripheral); +- queue_work(system_power_efficient_wq, &cs35l56->sdw_irq_work); ++ sdw_read_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1); ++ sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); + + return 0; + } + +-static void cs35l56_sdw_irq_work(struct work_struct *work) +-{ +- struct cs35l56_private *cs35l56 = container_of(work, +- struct cs35l56_private, +- sdw_irq_work); +- +- cs35l56_irq(-1, &cs35l56->base); +- +- /* unmask interrupts */ +- if (!cs35l56->sdw_irq_no_unmask) +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); +- +- pm_runtime_put_autosuspend(cs35l56->base.dev); +-} +- + static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) + { + struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); +@@ -302,6 +272,7 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) + prop->source_ports = BIT(CS35L56_SDW1_CAPTURE_PORT); + prop->sink_ports = BIT(CS35L56_SDW1_PLAYBACK_PORT); + prop->paging_support = true; ++ prop->use_domain_irq = true; + prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; + prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY | SDW_SCP_INT1_IMPL_DEF; + +@@ -406,7 +377,7 @@ static int __maybe_unused cs35l56_sdw_runtime_resume(struct device *dev) + if (ret) + return ret; + +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); ++ cs35l56_unmask_soundwire_interrupts(cs35l56); + + return 0; + } +@@ -418,21 +389,12 @@ static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev) + if (!cs35l56->base.init_done) + return 0; + +- cs35l56_disable_sdw_interrupts(cs35l56); ++ /* runtime_resume unmasks the interrupt */ ++ cs35l56_mask_soundwire_interrupts(cs35l56); + + return cs35l56_system_suspend(dev); + } + +-static int __maybe_unused cs35l56_sdw_system_resume(struct device *dev) +-{ +- struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); +- +- cs35l56->sdw_irq_no_unmask = false; +- /* runtime_resume re-enables the interrupt */ +- +- return cs35l56_system_resume(dev); +-} +- + static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_device_id *id) + { + struct device *dev = &peripheral->dev; +@@ -447,7 +409,6 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi + cs35l56->base.dev = dev; + cs35l56->sdw_peripheral = peripheral; + cs35l56->sdw_link_num = peripheral->bus->link_id; +- INIT_WORK(&cs35l56->sdw_irq_work, cs35l56_sdw_irq_work); + + dev_set_drvdata(dev, cs35l56); + +@@ -457,6 +418,7 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi + regmap_config = &cs35l56_regmap_sdw; + break; + case 0x3563: ++ case 0x3562: + regmap_config = &cs35l63_regmap_sdw; + break; + default: +@@ -483,25 +445,21 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi + /* Start in cache-only until device is enumerated */ + regcache_cache_only(cs35l56->base.regmap, true); + +- ret = cs35l56_common_probe(cs35l56); +- if (ret != 0) +- return ret; +- +- return 0; ++ return cs35l56_common_probe(cs35l56, peripheral->irq); + } + + static void cs35l56_sdw_remove(struct sdw_slave *peripheral) + { + struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); + +- cs35l56_disable_sdw_interrupts(cs35l56); ++ cs35l56_mask_soundwire_interrupts(cs35l56); + + cs35l56_remove(cs35l56); + } + + static const struct dev_pm_ops cs35l56_sdw_pm = { + SET_RUNTIME_PM_OPS(cs35l56_sdw_runtime_suspend, cs35l56_sdw_runtime_resume, NULL) +- SYSTEM_SLEEP_PM_OPS(cs35l56_sdw_system_suspend, cs35l56_sdw_system_resume) ++ SYSTEM_SLEEP_PM_OPS(cs35l56_sdw_system_suspend, cs35l56_system_resume) + LATE_SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend_late, cs35l56_system_resume_early) + /* NOIRQ stage not needed, SoundWire doesn't use a hard IRQ */ + }; +@@ -509,6 +467,7 @@ static const struct dev_pm_ops cs35l56_sdw_pm = { + static const struct sdw_device_id cs35l56_sdw_id[] = { + SDW_SLAVE_ENTRY(0x01FA, 0x3556, 0x3556), + SDW_SLAVE_ENTRY(0x01FA, 0x3557, 0x3557), ++ SDW_SLAVE_ENTRY(0x01FA, 0x3562, 0x3562), + SDW_SLAVE_ENTRY(0x01FA, 0x3563, 0x3563), + {}, + }; +diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c +--- a/sound/soc/codecs/cs35l56-shared.c ++++ b/sound/soc/codecs/cs35l56-shared.c +@@ -96,6 +96,7 @@ int cs35l56_set_patch(struct cs35l56_base *cs35l56_base) + ARRAY_SIZE(cs35l56_patch_fw)); + break; + case 0x63: ++ case 0x62: + ret = regmap_register_patch(cs35l56_base->regmap, cs35l63_patch_fw, + ARRAY_SIZE(cs35l63_patch_fw)); + break; +@@ -389,6 +390,7 @@ static void cs35l56_set_fw_reg_table(struct cs35l56_base *cs35l56_base) + } + break; + case 0x63: ++ case 0x62: + cs35l56_base->fw_reg = &cs35l63_fw_reg; + break; + } +@@ -595,6 +597,7 @@ void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire) + } + break; + case 0x63: ++ case 0x62: + regmap_multi_reg_write_bypassed(cs35l56_base->regmap, + cs35l63_system_reset_seq, + ARRAY_SIZE(cs35l63_system_reset_seq)); +@@ -613,26 +616,7 @@ void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire) + } + EXPORT_SYMBOL_NS_GPL(cs35l56_system_reset, "SND_SOC_CS35L56_SHARED"); + +-int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq) +-{ +- int ret; +- +- if (irq < 1) +- return 0; +- +- ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq, +- IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW, +- "cs35l56", cs35l56_base); +- if (!ret) +- cs35l56_base->irq = irq; +- else +- dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret); +- +- return ret; +-} +-EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, "SND_SOC_CS35L56_SHARED"); +- +-irqreturn_t cs35l56_irq(int irq, void *data) ++static irqreturn_t cs35l56_irq(int irq, void *data) + { + struct cs35l56_base *cs35l56_base = data; + unsigned int status1 = 0, status8 = 0, status20 = 0; +@@ -640,23 +624,22 @@ irqreturn_t cs35l56_irq(int irq, void *data) + unsigned int val; + int rv; + +- irqreturn_t ret = IRQ_NONE; +- + if (!cs35l56_base->init_done) + return IRQ_NONE; + +- mutex_lock(&cs35l56_base->irq_lock); ++ guard(mutex)(&cs35l56_base->irq_lock); + +- rv = pm_runtime_resume_and_get(cs35l56_base->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED(cs35l56_base->dev, pm); ++ rv = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (rv < 0) { + dev_err(cs35l56_base->dev, "irq: failed to get pm_runtime: %d\n", rv); +- goto err_unlock; ++ return IRQ_NONE; + } + + regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_STATUS, &val); + if ((val & CS35L56_IRQ1_STS_MASK) == 0) { + dev_dbg(cs35l56_base->dev, "Spurious IRQ: no pending interrupt\n"); +- goto err; ++ return IRQ_NONE; + } + + /* Ack interrupts */ +@@ -680,7 +663,7 @@ irqreturn_t cs35l56_irq(int irq, void *data) + + /* Check to see if unmasked bits are active */ + if (!status1 && !status8 && !status20) +- goto err; ++ return IRQ_NONE; + + if (status1 & CS35L56_AMP_SHORT_ERR_EINT1_MASK) + dev_crit(cs35l56_base->dev, "Amp short error\n"); +@@ -688,16 +671,27 @@ irqreturn_t cs35l56_irq(int irq, void *data) + if (status8 & CS35L56_TEMP_ERR_EINT1_MASK) + dev_crit(cs35l56_base->dev, "Overtemp error\n"); + +- ret = IRQ_HANDLED; ++ return IRQ_HANDLED; ++} + +-err: +- pm_runtime_put(cs35l56_base->dev); +-err_unlock: +- mutex_unlock(&cs35l56_base->irq_lock); ++int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq) ++{ ++ int ret; ++ ++ if (irq < 1) ++ return 0; ++ ++ ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq, ++ IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW, ++ "cs35l56", cs35l56_base); ++ if (!ret) ++ cs35l56_base->irq = irq; ++ else ++ dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret); + + return ret; + } +-EXPORT_SYMBOL_NS_GPL(cs35l56_irq, "SND_SOC_CS35L56_SHARED"); ++EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, "SND_SOC_CS35L56_SHARED"); + + int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base) + { +@@ -816,14 +810,10 @@ int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_sou + if (!cs35l56_base->init_done) + return 0; + +- if (!cs35l56_base->can_hibernate) +- goto out_sync; +- +- /* Must be done before releasing cache-only */ +- if (!is_soundwire) ++ /* Hibernate wake must be done before releasing cache-only */ ++ if (cs35l56_base->can_hibernate && !is_soundwire) + cs35l56_issue_wake_event(cs35l56_base); + +-out_sync: + ret = cs35l56_wait_for_firmware_boot(cs35l56_base); + if (ret) { + dev_err(cs35l56_base->dev, "Hibernate wake failed: %d\n", ret); +@@ -1470,6 +1460,7 @@ int cs35l56_hw_init(struct cs35l56_base *cs35l56_base) + cs35l56_base->calibration_controls = &cs35l56_calibration_controls; + break; + case 0x35A630: ++ case 0x35A620: + cs35l56_base->calibration_controls = &cs35l63_calibration_controls; + devid = devid >> 4; + break; +diff --git a/sound/soc/codecs/cs35l56-spi.c b/sound/soc/codecs/cs35l56-spi.c +--- a/sound/soc/codecs/cs35l56-spi.c ++++ b/sound/soc/codecs/cs35l56-spi.c +@@ -40,15 +40,7 @@ static int cs35l56_spi_probe(struct spi_device *spi) + if (ret) + return ret; + +- ret = cs35l56_common_probe(cs35l56); +- if (ret != 0) +- return ret; +- +- ret = cs35l56_irq_request(&cs35l56->base, spi->irq); +- if (ret < 0) +- cs35l56_remove(cs35l56); +- +- return ret; ++ return cs35l56_common_probe(cs35l56, spi->irq); + } + + static void cs35l56_spi_remove(struct spi_device *spi) +diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c +--- a/sound/soc/codecs/cs35l56.c ++++ b/sound/soc/codecs/cs35l56.c +@@ -37,48 +37,49 @@ + #include "wm_adsp.h" + #include "cs35l56.h" + +-void cs35l56_mask_soundwire_interrupts(struct sdw_slave *peripheral) ++void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56) + { + /* ++ * Mask unconditionally. ++ * + * The read of GEN_INT_STAT_1 is required as per the SoundWire spec + * for interrupt status bits to clear. + * GEN_INT_MASK_1 masks the _inputs_ to GEN_INT_STAT1. + */ +- sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0); +- sdw_read_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1); +- sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); ++ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0); ++ sdw_read_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1); ++ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); + } + EXPORT_SYMBOL_NS_GPL(cs35l56_mask_soundwire_interrupts, "SND_SOC_CS35L56_CORE"); + +-void cs35l56_unmask_soundwire_interrupts(struct sdw_slave *peripheral) ++void cs35l56_unmask_soundwire_interrupts(struct cs35l56_private *cs35l56) + { +- sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1, CS35L56_SDW_INT_MASK_CODEC_IRQ); ++ if (!cs35l56->base.irq) ++ return; ++ ++ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, ++ CS35L56_SDW_INT_MASK_CODEC_IRQ); + } + EXPORT_SYMBOL_NS_GPL(cs35l56_unmask_soundwire_interrupts, "SND_SOC_CS35L56_CORE"); + +-void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56) ++static void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56) + { + if (!cs35l56->sdw_peripheral) + return; + +- cs35l56->sdw_irq_no_unmask = true; +- flush_work(&cs35l56->sdw_irq_work); +- +- /* Mask interrupts and flush in case sdw_irq_work was queued again */ +- cs35l56_mask_soundwire_interrupts(cs35l56->sdw_peripheral); +- flush_work(&cs35l56->sdw_irq_work); ++ cs35l56_mask_soundwire_interrupts(cs35l56); ++ if (cs35l56->base.irq) ++ disable_irq(cs35l56->base.irq); + } +-EXPORT_SYMBOL_NS_GPL(cs35l56_disable_sdw_interrupts, "SND_SOC_CS35L56_CORE"); + +-void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56) ++static void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56) + { +- if (!cs35l56->sdw_peripheral) ++ if (!cs35l56->sdw_peripheral || !cs35l56->base.irq) + return; + +- cs35l56->sdw_irq_no_unmask = false; +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); ++ enable_irq(cs35l56->base.irq); ++ cs35l56_unmask_soundwire_interrupts(cs35l56); + } +-EXPORT_SYMBOL_NS_GPL(cs35l56_enable_sdw_interrupts, "SND_SOC_CS35L56_CORE"); + + static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); +@@ -828,11 +829,7 @@ static void cs35l56_patch(struct cs35l56_private *cs35l56, bool firmware_missing + { + int ret; + +- /* +- * Disable SoundWire interrupts to prevent race with IRQ work. +- * Setting sdw_irq_no_unmask prevents the handler re-enabling +- * the SoundWire interrupt. +- */ ++ /* Disable SoundWire interrupts to prevent race with IRQ handler thread */ + cs35l56_disable_sdw_interrupts(cs35l56); + + ret = cs35l56_firmware_shutdown(&cs35l56->base); +@@ -1402,6 +1399,7 @@ static int _cs35l56_component_probe(struct snd_soc_component *component) + ARRAY_SIZE(cs35l56_controls)); + break; + case 0x63: ++ case 0x62: + ret = snd_soc_add_component_controls(component, cs35l63_controls, + ARRAY_SIZE(cs35l63_controls)); + break; +@@ -1941,7 +1939,7 @@ static int cs35l56_try_get_broken_sdca_spkid_gpio(struct cs35l56_private *cs35l5 + return ret; + } + +-int cs35l56_common_probe(struct cs35l56_private *cs35l56) ++int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq) + { + int ret; + +@@ -2018,16 +2016,24 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56) + goto err_remove_wm_adsp; + } + ++ ret = cs35l56_irq_request(&cs35l56->base, irq); ++ if (ret) ++ goto err_remove_wm_adsp; ++ + ret = snd_soc_register_component(cs35l56->base.dev, + &soc_component_dev_cs35l56, + cs35l56_dai, ARRAY_SIZE(cs35l56_dai)); + if (ret < 0) { + dev_err_probe(cs35l56->base.dev, ret, "Register codec failed\n"); +- goto err_remove_wm_adsp; ++ goto err_free_irq; + } + + return 0; + ++err_free_irq: ++ if (cs35l56->base.irq) ++ devm_free_irq(cs35l56->base.dev, cs35l56->base.irq, &cs35l56->base); ++ + err_remove_wm_adsp: + wm_adsp2_remove(&cs35l56->dsp); + +diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h +--- a/sound/soc/codecs/cs35l56.h ++++ b/sound/soc/codecs/cs35l56.h +@@ -39,8 +39,6 @@ struct cs35l56_private { + struct sdw_slave *sdw_peripheral; + struct regmap *sdw_bus_regmap; + const char *fallback_fw_suffix; +- struct work_struct sdw_irq_work; +- bool sdw_irq_no_unmask; + bool soft_resetting; + bool sdw_attached; + struct completion init_completion; +@@ -65,10 +63,8 @@ static inline struct cs35l56_private *cs35l56_private_from_base(struct cs35l56_b + + extern const struct dev_pm_ops cs35l56_pm_ops_i2c_spi; + +-void cs35l56_mask_soundwire_interrupts(struct sdw_slave *peripheral); +-void cs35l56_unmask_soundwire_interrupts(struct sdw_slave *peripheral); +-void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56); +-void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56); ++void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56); ++void cs35l56_unmask_soundwire_interrupts(struct cs35l56_private *cs35l56); + + int cs35l56_system_suspend(struct device *dev); + int cs35l56_system_suspend_late(struct device *dev); +@@ -76,9 +72,8 @@ int cs35l56_system_suspend_no_irq(struct device *dev); + int cs35l56_system_resume_no_irq(struct device *dev); + int cs35l56_system_resume_early(struct device *dev); + int cs35l56_system_resume(struct device *dev); +-irqreturn_t cs35l56_irq(int irq, void *data); + int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq); +-int cs35l56_common_probe(struct cs35l56_private *cs35l56); ++int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq); + int cs35l56_init(struct cs35l56_private *cs35l56); + void cs35l56_remove(struct cs35l56_private *cs35l56); + +diff --git a/sound/soc/codecs/cs40l50-codec.c b/sound/soc/codecs/cs40l50-codec.c +--- a/sound/soc/codecs/cs40l50-codec.c ++++ b/sound/soc/codecs/cs40l50-codec.c +@@ -288,8 +288,8 @@ static int cs40l50_codec_driver_probe(struct platform_device *pdev) + } + + static const struct platform_device_id cs40l50_id[] = { +- { "cs40l50-codec", }, +- {} ++ { .name = "cs40l50-codec" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, cs40l50_id); + +diff --git a/sound/soc/codecs/cs42l42-sdw.c b/sound/soc/codecs/cs42l42-sdw.c +--- a/sound/soc/codecs/cs42l42-sdw.c ++++ b/sound/soc/codecs/cs42l42-sdw.c +@@ -481,10 +481,20 @@ static int cs42l42_sdw_runtime_resume(struct device *dev) + regcache_cache_only(cs42l42->regmap, false); + + /* Sync LATCH_TO_VP first so the VP domain registers sync correctly */ +- regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); +- regcache_sync(cs42l42->regmap); ++ ret = regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); ++ if (ret) ++ goto err_sync; ++ ++ ret = regcache_sync(cs42l42->regmap); ++ if (ret) ++ goto err_sync; + + return 0; ++ ++err_sync: ++ regcache_cache_only(cs42l42->regmap, true); ++ regcache_mark_dirty(cs42l42->regmap); ++ return ret; + } + + static int cs42l42_sdw_resume(struct device *dev) +diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c +--- a/sound/soc/codecs/cs42l42.c ++++ b/sound/soc/codecs/cs42l42.c +@@ -9,6 +9,7 @@ + * Author: Michael White + */ + ++#include + #include + #include + #include +@@ -565,7 +566,7 @@ static int cs42l42_set_jack(struct snd_soc_component *component, struct snd_soc_ + struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); + + /* Prevent race with interrupt handler */ +- mutex_lock(&cs42l42->irq_lock); ++ guard(mutex)(&cs42l42->irq_lock); + cs42l42->jack = jk; + + if (jk) { +@@ -581,7 +582,6 @@ static int cs42l42_set_jack(struct snd_soc_component *component, struct snd_soc_ + break; + } + } +- mutex_unlock(&cs42l42->irq_lock); + + return 0; + } +@@ -1668,13 +1668,10 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) + unsigned int current_button_status; + unsigned int i; + +- pm_runtime_get_sync(cs42l42->dev); +- mutex_lock(&cs42l42->irq_lock); +- if (cs42l42->suspended || !cs42l42->init_done) { +- mutex_unlock(&cs42l42->irq_lock); +- pm_runtime_put_autosuspend(cs42l42->dev); ++ guard(pm_runtime_active_auto)(cs42l42->dev); ++ guard(mutex)(&cs42l42->irq_lock); ++ if (cs42l42->suspended || !cs42l42->init_done) + return IRQ_NONE; +- } + + /* Read sticky registers to clear interurpt */ + for (i = 0; i < ARRAY_SIZE(stickies); i++) { +@@ -1774,9 +1771,6 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) + } + } + +- mutex_unlock(&cs42l42->irq_lock); +- pm_runtime_put_autosuspend(cs42l42->dev); +- + return IRQ_HANDLED; + } + EXPORT_SYMBOL_NS_GPL(cs42l42_irq_thread, "SND_SOC_CS42L42_CORE"); +@@ -2163,23 +2157,23 @@ int cs42l42_suspend(struct device *dev) + * future interrupts. This ensures a safe disable if the interrupt + * is shared. + */ +- mutex_lock(&cs42l42->irq_lock); +- cs42l42->suspended = true; ++ scoped_guard(mutex, &cs42l42->irq_lock) { ++ cs42l42->suspended = true; + +- /* Save register values that will be overwritten by shutdown sequence */ +- for (i = 0; i < ARRAY_SIZE(cs42l42_shutdown_seq); ++i) { +- regmap_read(cs42l42->regmap, cs42l42_shutdown_seq[i].reg, ®); +- save_regs[i] = (u8)reg; ++ /* Save register values that will be overwritten by shutdown sequence */ ++ for (i = 0; i < ARRAY_SIZE(cs42l42_shutdown_seq); ++i) { ++ regmap_read(cs42l42->regmap, cs42l42_shutdown_seq[i].reg, ®); ++ save_regs[i] = (u8)reg; ++ } ++ ++ /* Shutdown codec */ ++ regmap_multi_reg_write(cs42l42->regmap, ++ cs42l42_shutdown_seq, ++ ARRAY_SIZE(cs42l42_shutdown_seq)); ++ ++ /* All interrupt sources are now disabled */ + } + +- /* Shutdown codec */ +- regmap_multi_reg_write(cs42l42->regmap, +- cs42l42_shutdown_seq, +- ARRAY_SIZE(cs42l42_shutdown_seq)); +- +- /* All interrupt sources are now disabled */ +- mutex_unlock(&cs42l42->irq_lock); +- + /* Wait for power-down complete */ + msleep(CS42L42_PDN_DONE_TIME_MS); + ret = regmap_read_poll_timeout(cs42l42->regmap, +@@ -2250,13 +2244,13 @@ void cs42l42_resume_restore(struct device *dev) + regcache_cache_only(cs42l42->regmap, false); + regcache_mark_dirty(cs42l42->regmap); + +- mutex_lock(&cs42l42->irq_lock); +- /* Sync LATCH_TO_VP first so the VP domain registers sync correctly */ +- regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); +- regcache_sync(cs42l42->regmap); ++ scoped_guard(mutex, &cs42l42->irq_lock) { ++ /* Sync LATCH_TO_VP first so the VP domain registers sync correctly */ ++ regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); ++ regcache_sync(cs42l42->regmap); + +- cs42l42->suspended = false; +- mutex_unlock(&cs42l42->irq_lock); ++ cs42l42->suspended = false; ++ } + + dev_dbg(dev, "System resumed\n"); + } +diff --git a/sound/soc/codecs/cs42l43-jack.c b/sound/soc/codecs/cs42l43-jack.c +--- a/sound/soc/codecs/cs42l43-jack.c ++++ b/sound/soc/codecs/cs42l43-jack.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + + #include ++#include + #include + #include + #include +@@ -67,6 +68,21 @@ static int cs42l43_find_index(struct cs42l43_codec *priv, const char * const pro + return -EINVAL; + } + ++static void cs42l43_apply_accdet_config(struct cs42l43_codec *priv, ++ unsigned int autocontrol, ++ unsigned int pdncntl) ++{ ++ struct cs42l43 *cs42l43 = priv->core; ++ ++ regmap_update_bits(cs42l43->regmap, CS42L43_HS_BIAS_SENSE_AND_CLAMP_AUTOCONTROL, ++ CS42L43_JACKDET_MODE_MASK | CS42L43_S0_AUTO_ADCMUTE_DISABLE_MASK | ++ CS42L43_HSBIAS_SENSE_TRIP_MASK, autocontrol); ++ regmap_update_bits(cs42l43->regmap, CS42L43_PDNCNTL, ++ CS42L43_RING_SENSE_EN_MASK, pdncntl); ++ ++ dev_dbg(priv->dev, "Successfully configured accessory detect\n"); ++} ++ + int cs42l43_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, void *d) + { +@@ -80,31 +96,34 @@ int cs42l43_set_jack(struct snd_soc_component *component, + + dev_dbg(priv->dev, "Configure accessory detect\n"); + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for jack config: %d\n", ret); + return ret; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + + priv->jack_hp = jack; + +- if (!jack) +- goto done; ++ if (!jack) { ++ cs42l43_apply_accdet_config(priv, autocontrol, pdncntl); ++ return 0; ++ } + + ret = device_property_count_u32(cs42l43->dev, "cirrus,buttons-ohms"); + if (ret != -EINVAL) { + if (ret < 0) { + dev_err(priv->dev, "Property cirrus,buttons-ohms malformed: %d\n", + ret); +- goto error; ++ return ret; + } + + if (ret > CS42L43_N_BUTTONS) { + ret = -EINVAL; + dev_err(priv->dev, "Property cirrus,buttons-ohms too many entries\n"); +- goto error; ++ return ret; + } + + ret = device_property_read_u32_array(cs42l43->dev, "cirrus,buttons-ohms", +@@ -112,7 +131,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + if (ret < 0) { + dev_err(priv->dev, "Property cirrus,button-ohms malformed: %d\n", + ret); +- goto error; ++ return ret; + } + } else { + priv->buttons[0] = 70; +@@ -124,7 +143,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + ret = cs42l43_find_index(priv, "cirrus,detect-us", 50000, &priv->detect_us, + cs42l43_accdet_us, ARRAY_SIZE(cs42l43_accdet_us)); + if (ret < 0) +- goto error; ++ return ret; + + hs2 |= ret << CS42L43_AUTO_HSDET_TIME_SHIFT; + +@@ -134,7 +153,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->bias_ramp_ms, cs42l43_accdet_ramp_ms, + ARRAY_SIZE(cs42l43_accdet_ramp_ms)); + if (ret < 0) +- goto error; ++ return ret; + + hs2 |= ret << CS42L43_HSBIAS_RAMP_SHIFT; + +@@ -142,7 +161,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->bias_sense_ua, cs42l43_accdet_bias_sense, + ARRAY_SIZE(cs42l43_accdet_bias_sense)); + if (ret < 0) +- goto error; ++ return ret; + + if (priv->bias_sense_ua) + autocontrol |= ret << CS42L43_HSBIAS_SENSE_TRIP_SHIFT; +@@ -154,7 +173,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->tip_debounce_ms); + if (ret < 0 && ret != -EINVAL) { + dev_err(priv->dev, "Property cirrus,tip-debounce-ms malformed: %d\n", ret); +- goto error; ++ return ret; + } + + /* This tip sense invert is set normally, as TIPSENSE_INV already inverted */ +@@ -170,7 +189,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->tip_fall_db_ms, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + tip_deb |= ret << CS42L43_TIPSENSE_FALLING_DB_TIME_SHIFT; + +@@ -178,7 +197,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->tip_rise_db_ms, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + tip_deb |= ret << CS42L43_TIPSENSE_RISING_DB_TIME_SHIFT; + +@@ -199,7 +218,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + NULL, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + ring_deb |= ret << CS42L43_RINGSENSE_FALLING_DB_TIME_SHIFT; + +@@ -207,7 +226,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + NULL, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + ring_deb |= ret << CS42L43_RINGSENSE_RISING_DB_TIME_SHIFT; + pdncntl |= CS42L43_RING_SENSE_EN_MASK; +@@ -228,23 +247,9 @@ int cs42l43_set_jack(struct snd_soc_component *component, + CS42L43_HSBIAS_RAMP_MASK | CS42L43_HSDET_MODE_MASK | + CS42L43_AUTO_HSDET_TIME_MASK, hs2); + +-done: +- ret = 0; ++ cs42l43_apply_accdet_config(priv, autocontrol, pdncntl); + +- regmap_update_bits(cs42l43->regmap, CS42L43_HS_BIAS_SENSE_AND_CLAMP_AUTOCONTROL, +- CS42L43_JACKDET_MODE_MASK | CS42L43_S0_AUTO_ADCMUTE_DISABLE_MASK | +- CS42L43_HSBIAS_SENSE_TRIP_MASK, autocontrol); +- regmap_update_bits(cs42l43->regmap, CS42L43_PDNCNTL, +- CS42L43_RING_SENSE_EN_MASK, pdncntl); +- +- dev_dbg(priv->dev, "Successfully configured accessory detect\n"); +- +-error: +- mutex_unlock(&priv->jack_lock); +- +- pm_runtime_put_autosuspend(priv->dev); +- +- return ret; ++ return 0; + } + + static void cs42l43_start_hs_bias(struct cs42l43_codec *priv, bool type_detect) +@@ -370,22 +375,22 @@ irqreturn_t cs42l43_button_press(int irq, void *data) + { + struct cs42l43_codec *priv = data; + struct cs42l43 *cs42l43 = priv->core; +- irqreturn_t iret = IRQ_NONE; + unsigned int buttons = 0; + unsigned int val = 0; + int i, ret; + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for button press: %d\n", ret); +- return iret; ++ return IRQ_NONE; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + + if (!priv->button_detect_running) { + dev_dbg(priv->dev, "Spurious button press IRQ\n"); +- goto error; ++ return IRQ_NONE; + } + + // Wait for 2 full cycles of comb filter to ensure good reading +@@ -396,12 +401,12 @@ irqreturn_t cs42l43_button_press(int irq, void *data) + /* Bail if jack removed, the button is irrelevant and likely invalid */ + if (!cs42l43_jack_present(priv)) { + dev_dbg(priv->dev, "Button ignored due to removal\n"); +- goto error; ++ return IRQ_NONE; + } + + if (val & CS42L43_HSBIAS_CLAMP_STS_MASK) { + dev_dbg(priv->dev, "Button ignored due to bias sense\n"); +- goto error; ++ return IRQ_NONE; + } + + val = (val & CS42L43_HSDET_DC_STS_MASK) >> CS42L43_HSDET_DC_STS_SHIFT; +@@ -424,45 +429,32 @@ irqreturn_t cs42l43_button_press(int irq, void *data) + + snd_soc_jack_report(priv->jack_hp, buttons, CS42L43_JACK_BUTTONS); + +- iret = IRQ_HANDLED; +- +-error: +- mutex_unlock(&priv->jack_lock); +- +- pm_runtime_put_autosuspend(priv->dev); +- +- return iret; ++ return IRQ_HANDLED; + } + + irqreturn_t cs42l43_button_release(int irq, void *data) + { + struct cs42l43_codec *priv = data; +- irqreturn_t iret = IRQ_NONE; + int ret; + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for button release: %d\n", ret); +- return iret; ++ return IRQ_NONE; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + +- if (priv->button_detect_running) { +- dev_dbg(priv->dev, "Button release IRQ\n"); +- +- snd_soc_jack_report(priv->jack_hp, 0, CS42L43_JACK_BUTTONS); +- +- iret = IRQ_HANDLED; +- } else { ++ if (!priv->button_detect_running) { + dev_dbg(priv->dev, "Spurious button release IRQ\n"); ++ return IRQ_NONE; + } + +- mutex_unlock(&priv->jack_lock); ++ dev_dbg(priv->dev, "Button release IRQ\n"); ++ snd_soc_jack_report(priv->jack_hp, 0, CS42L43_JACK_BUTTONS); + +- pm_runtime_put_autosuspend(priv->dev); +- +- return iret; ++ return IRQ_HANDLED; + } + + void cs42l43_bias_sense_timeout(struct work_struct *work) +@@ -472,13 +464,14 @@ void cs42l43_bias_sense_timeout(struct work_struct *work) + struct cs42l43 *cs42l43 = priv->core; + int ret; + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for bias sense: %d\n", ret); + return; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + + if (cs42l43_jack_present(priv) && priv->button_detect_running) { + dev_dbg(priv->dev, "Bias sense timeout out, restore bias\n"); +@@ -491,10 +484,6 @@ void cs42l43_bias_sense_timeout(struct work_struct *work) + CS42L43_AUTO_HSBIAS_CLAMP_EN_MASK, + CS42L43_AUTO_HSBIAS_CLAMP_EN_MASK); + } +- +- mutex_unlock(&priv->jack_lock); +- +- pm_runtime_put_autosuspend(priv->dev); + } + + static const struct reg_sequence cs42l43_3pole_patch[] = { +@@ -896,9 +885,8 @@ int cs42l43_jack_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct cs42l43_codec *priv = snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + ucontrol->value.integer.value[0] = priv->jack_override; +- mutex_unlock(&priv->jack_lock); + + return 0; + } +@@ -914,17 +902,13 @@ int cs42l43_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u + if (override >= e->items) + return -EINVAL; + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + +- if (!cs42l43_jack_present(priv)) { +- mutex_unlock(&priv->jack_lock); ++ if (!cs42l43_jack_present(priv)) + return -EBUSY; +- } + +- if (override == priv->jack_override) { +- mutex_unlock(&priv->jack_lock); ++ if (override == priv->jack_override) + return 0; +- } + + priv->jack_override = override; + +@@ -984,7 +968,5 @@ int cs42l43_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u + cs42l43_jack_override_modes[override].report); + } + +- mutex_unlock(&priv->jack_lock); +- + return 1; + } +diff --git a/sound/soc/codecs/cs42l43.c b/sound/soc/codecs/cs42l43.c +--- a/sound/soc/codecs/cs42l43.c ++++ b/sound/soc/codecs/cs42l43.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1207,14 +1208,12 @@ static void cs42l43_spk_vu_sync(struct cs42l43_codec *priv) + { + struct cs42l43 *cs42l43 = priv->core; + +- mutex_lock(&priv->spk_vu_lock); ++ guard(mutex)(&priv->spk_vu_lock); + + regmap_update_bits(cs42l43->regmap, CS42L43_INTP_VOLUME_CTRL1, + CS42L43_AMP1_2_VU_MASK, CS42L43_AMP1_2_VU_MASK); + regmap_update_bits(cs42l43->regmap, CS42L43_INTP_VOLUME_CTRL1, + CS42L43_AMP1_2_VU_MASK, 0); +- +- mutex_unlock(&priv->spk_vu_lock); + } + + static int cs42l43_shutter_get(struct cs42l43_codec *priv, unsigned int shift) +@@ -1601,7 +1600,7 @@ static int cs42l43_pll_ev(struct snd_soc_dapm_widget *w, + struct cs42l43 *cs42l43 = priv->core; + int ret; + +- mutex_lock(&cs42l43->pll_lock); ++ guard(mutex)(&cs42l43->pll_lock); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +@@ -1626,8 +1625,6 @@ static int cs42l43_pll_ev(struct snd_soc_dapm_widget *w, + break; + } + +- mutex_unlock(&cs42l43->pll_lock); +- + return ret; + } + +@@ -2565,13 +2562,10 @@ static int cs42l43_set_sysclk(struct snd_soc_component *component, int clk_id, + { + struct cs42l43_codec *priv = snd_soc_component_get_drvdata(component); + struct cs42l43 *cs42l43 = priv->core; +- int ret; + +- mutex_lock(&cs42l43->pll_lock); +- ret = cs42l43_set_pll(priv, src, freq); +- mutex_unlock(&cs42l43->pll_lock); ++ guard(mutex)(&cs42l43->pll_lock); + +- return ret; ++ return cs42l43_set_pll(priv, src, freq); + } + + static int cs42l43_component_probe(struct snd_soc_component *component) +@@ -2941,8 +2935,8 @@ static const struct dev_pm_ops cs42l43_codec_pm_ops = { + }; + + static const struct platform_device_id cs42l43_codec_id_table[] = { +- { "cs42l43-codec", }, +- {} ++ { .name = "cs42l43-codec" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, cs42l43_codec_id_table); + +diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c +--- a/sound/soc/codecs/cs42l84.c ++++ b/sound/soc/codecs/cs42l84.c +@@ -10,6 +10,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -281,10 +282,9 @@ static int cs42l84_set_jack(struct snd_soc_component *component, struct snd_soc_ + struct cs42l84_private *cs42l84 = snd_soc_component_get_drvdata(component); + + /* Prevent race with interrupt handler */ +- mutex_lock(&cs42l84->irq_lock); ++ guard(mutex)(&cs42l84->irq_lock); + cs42l84->jack = jk; + snd_soc_jack_report(jk, cs42l84->hs_type, SND_JACK_HEADSET); +- mutex_unlock(&cs42l84->irq_lock); + + return 0; + } +@@ -831,7 +831,7 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data) + u8 current_ring_state; + int i; + +- mutex_lock(&cs42l84->irq_lock); ++ guard(mutex)(&cs42l84->irq_lock); + /* Read sticky registers to clear interrupt */ + for (i = 0; i < ARRAY_SIZE(stickies); i++) { + regmap_read(cs42l84->regmap, irq_params_table[i].status_addr, +@@ -902,8 +902,6 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data) + break; + } + +- mutex_unlock(&cs42l84->irq_lock); +- + return IRQ_HANDLED; + } + +@@ -919,8 +917,6 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data) + } + } + +- mutex_unlock(&cs42l84->irq_lock); +- + return IRQ_HANDLED; + } + +diff --git a/sound/soc/codecs/cs43130.c b/sound/soc/codecs/cs43130.c +--- a/sound/soc/codecs/cs43130.c ++++ b/sound/soc/codecs/cs43130.c +@@ -6,6 +6,7 @@ + * + * Authors: Li Xu + */ ++#include + #include + #include + #include +@@ -818,26 +819,26 @@ static int cs43130_dsd_hw_params(struct snd_pcm_substream *substream, + unsigned int required_clk; + u8 dsd_speed; + +- mutex_lock(&cs43130->clk_mutex); +- if (!cs43130->clk_req) { +- /* no DAI is currently using clk */ +- if (!(CS43130_MCLK_22M % params_rate(params))) +- required_clk = CS43130_MCLK_22M; +- else +- required_clk = CS43130_MCLK_24M; ++ scoped_guard(mutex, &cs43130->clk_mutex) { ++ if (!cs43130->clk_req) { ++ /* no DAI is currently using clk */ ++ if (!(CS43130_MCLK_22M % params_rate(params))) ++ required_clk = CS43130_MCLK_22M; ++ else ++ required_clk = CS43130_MCLK_24M; + +- cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); +- if (cs43130->pll_bypass) +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); +- else +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); ++ if (cs43130->pll_bypass) ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); ++ else ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ } ++ ++ cs43130->clk_req++; ++ if (cs43130->clk_req == 2) ++ cs43130_pcm_dsd_mix(true, cs43130->regmap); + } + +- cs43130->clk_req++; +- if (cs43130->clk_req == 2) +- cs43130_pcm_dsd_mix(true, cs43130->regmap); +- mutex_unlock(&cs43130->clk_mutex); +- + switch (params_rate(params)) { + case 176400: + dsd_speed = 0; +@@ -881,26 +882,26 @@ static int cs43130_hw_params(struct snd_pcm_substream *substream, + unsigned int required_clk; + u8 dsd_speed; + +- mutex_lock(&cs43130->clk_mutex); +- if (!cs43130->clk_req) { +- /* no DAI is currently using clk */ +- if (!(CS43130_MCLK_22M % params_rate(params))) +- required_clk = CS43130_MCLK_22M; +- else +- required_clk = CS43130_MCLK_24M; ++ scoped_guard(mutex, &cs43130->clk_mutex) { ++ if (!cs43130->clk_req) { ++ /* no DAI is currently using clk */ ++ if (!(CS43130_MCLK_22M % params_rate(params))) ++ required_clk = CS43130_MCLK_22M; ++ else ++ required_clk = CS43130_MCLK_24M; + +- cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); +- if (cs43130->pll_bypass) +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); +- else +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); ++ if (cs43130->pll_bypass) ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); ++ else ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ } ++ ++ cs43130->clk_req++; ++ if (cs43130->clk_req == 2) ++ cs43130_pcm_dsd_mix(true, cs43130->regmap); + } + +- cs43130->clk_req++; +- if (cs43130->clk_req == 2) +- cs43130_pcm_dsd_mix(true, cs43130->regmap); +- mutex_unlock(&cs43130->clk_mutex); +- + switch (dai->id) { + case CS43130_ASP_DOP_DAI: + case CS43130_XSP_DOP_DAI: +@@ -988,14 +989,13 @@ static int cs43130_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&cs43130->clk_mutex); ++ guard(mutex)(&cs43130->clk_mutex); + cs43130->clk_req--; + if (!cs43130->clk_req) { + /* no DAI is currently using clk */ + cs43130_change_clksrc(component, CS43130_MCLK_SRC_RCO); + cs43130_pcm_dsd_mix(false, cs43130->regmap); + } +- mutex_unlock(&cs43130->clk_mutex); + + return 0; + } +diff --git a/sound/soc/codecs/cs4349.c b/sound/soc/codecs/cs4349.c +--- a/sound/soc/codecs/cs4349.c ++++ b/sound/soc/codecs/cs4349.c +@@ -340,7 +340,13 @@ static int cs4349_runtime_resume(struct device *dev) + gpiod_set_value_cansleep(cs4349->reset_gpio, 1); + + regcache_cache_only(cs4349->regmap, false); +- regcache_sync(cs4349->regmap); ++ ret = regcache_sync(cs4349->regmap); ++ if (ret) { ++ regcache_cache_only(cs4349->regmap, true); ++ regcache_mark_dirty(cs4349->regmap); ++ gpiod_set_value_cansleep(cs4349->reset_gpio, 0); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/cs47l15.c b/sound/soc/codecs/cs47l15.c +--- a/sound/soc/codecs/cs47l15.c ++++ b/sound/soc/codecs/cs47l15.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -1285,9 +1286,8 @@ static int cs47l15_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -1317,9 +1317,8 @@ static void cs47l15_component_remove(struct snd_soc_component *component) + struct cs47l15 *cs47l15 = snd_soc_component_get_drvdata(component); + struct madera *madera = cs47l15->core.madera; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + wm_adsp2_component_remove(&cs47l15->core.adsp[0], component); + } +diff --git a/sound/soc/codecs/cs47l35.c b/sound/soc/codecs/cs47l35.c +--- a/sound/soc/codecs/cs47l35.c ++++ b/sound/soc/codecs/cs47l35.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -1566,9 +1567,8 @@ static int cs47l35_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -1600,9 +1600,8 @@ static void cs47l35_component_remove(struct snd_soc_component *component) + struct madera *madera = cs47l35->core.madera; + int i; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + for (i = 0; i < CS47L35_NUM_ADSP; i++) + wm_adsp2_component_remove(&cs47l35->core.adsp[i], component); +diff --git a/sound/soc/codecs/cs47l85.c b/sound/soc/codecs/cs47l85.c +--- a/sound/soc/codecs/cs47l85.c ++++ b/sound/soc/codecs/cs47l85.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -2504,9 +2505,8 @@ static int cs47l85_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -2537,9 +2537,8 @@ static void cs47l85_component_remove(struct snd_soc_component *component) + struct madera *madera = cs47l85->core.madera; + int i; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + for (i = 0; i < CS47L85_NUM_ADSP; i++) + wm_adsp2_component_remove(&cs47l85->core.adsp[i], component); +diff --git a/sound/soc/codecs/cs47l90.c b/sound/soc/codecs/cs47l90.c +--- a/sound/soc/codecs/cs47l90.c ++++ b/sound/soc/codecs/cs47l90.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -2423,9 +2424,8 @@ static int cs47l90_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -2456,9 +2456,8 @@ static void cs47l90_component_remove(struct snd_soc_component *component) + struct madera *madera = cs47l90->core.madera; + int i; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + for (i = 0; i < CS47L90_NUM_ADSP; i++) + wm_adsp2_component_remove(&cs47l90->core.adsp[i], component); +diff --git a/sound/soc/codecs/cs47l92.c b/sound/soc/codecs/cs47l92.c +--- a/sound/soc/codecs/cs47l92.c ++++ b/sound/soc/codecs/cs47l92.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -1892,9 +1893,8 @@ static int cs47l92_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -1922,9 +1922,8 @@ static void cs47l92_component_remove(struct snd_soc_component *component) + struct cs47l92 *cs47l92 = snd_soc_component_get_drvdata(component); + struct madera *madera = cs47l92->core.madera; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + wm_adsp2_component_remove(&cs47l92->core.adsp[0], component); + } +diff --git a/sound/soc/codecs/cs48l32.c b/sound/soc/codecs/cs48l32.c +--- a/sound/soc/codecs/cs48l32.c ++++ b/sound/soc/codecs/cs48l32.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -236,15 +237,13 @@ static int cs48l32_rate_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v + int ret; + + /* Prevent any mixer mux changes while we do this */ +- mutex_lock(&cs48l32_codec->rate_lock); ++ guard(mutex)(&cs48l32_codec->rate_lock); + + /* The write must be guarded by a number of SYSCLK cycles */ + cs48l32_spin_sysclk(cs48l32_codec); + ret = snd_soc_put_enum_double(kcontrol, ucontrol); + cs48l32_spin_sysclk(cs48l32_codec); + +- mutex_unlock(&cs48l32_codec->rate_lock); +- + return ret; + } + +@@ -2242,7 +2241,6 @@ static int cs48l32_dai_set_sysclk(struct snd_soc_dai *dai, + struct cs48l32_dai_priv *dai_priv = &cs48l32_codec->dai[dai->id - 1]; + unsigned int base = dai->driver->base; + unsigned int current_asp_rate, target_asp_rate; +- bool change_rate_domain = false; + int ret; + + if (clk_id == dai_priv->clk) +@@ -2284,19 +2282,15 @@ static int cs48l32_dai_set_sysclk(struct snd_soc_dai *dai, + + if ((current_asp_rate & CS48L32_ASP_RATE_MASK) != + (target_asp_rate & CS48L32_ASP_RATE_MASK)) { +- change_rate_domain = true; +- +- mutex_lock(&cs48l32_codec->rate_lock); + /* Guard the rate change with SYSCLK cycles */ +- cs48l32_spin_sysclk(cs48l32_codec); +- } +- +- snd_soc_component_update_bits(component, base + CS48L32_ASP_CONTROL1, +- CS48L32_ASP_RATE_MASK, target_asp_rate); +- +- if (change_rate_domain) { +- cs48l32_spin_sysclk(cs48l32_codec); +- mutex_unlock(&cs48l32_codec->rate_lock); ++ scoped_guard(mutex, &cs48l32_codec->rate_lock) { ++ cs48l32_spin_sysclk(cs48l32_codec); ++ snd_soc_component_update_bits(component, ++ base + CS48L32_ASP_CONTROL1, ++ CS48L32_ASP_RATE_MASK, ++ target_asp_rate); ++ cs48l32_spin_sysclk(cs48l32_codec); ++ } + } + } + +diff --git a/sound/soc/codecs/cx2072x.c b/sound/soc/codecs/cx2072x.c +--- a/sound/soc/codecs/cx2072x.c ++++ b/sound/soc/codecs/cx2072x.c +@@ -9,6 +9,7 @@ + // + + #include ++#include + #include + #include + #include +@@ -1408,7 +1409,7 @@ static int cx2072x_jack_status_check(void *data) + unsigned int type = 0; + int state = 0; + +- mutex_lock(&cx2072x->lock); ++ guard(mutex)(&cx2072x->lock); + + regmap_read(cx2072x->regmap, CX2072X_PORTA_PIN_SENSE, &jack); + jack = jack >> 24; +@@ -1434,8 +1435,6 @@ static int cx2072x_jack_status_check(void *data) + /* clear interrupt */ + regmap_write(cx2072x->regmap, CX2072X_UM_INTERRUPT_CRTL_E, 0x12 << 24); + +- mutex_unlock(&cx2072x->lock); +- + dev_dbg(codec->dev, "CX2072X_HSDETECT type=0x%X,Jack state = %x\n", + type, state); + return state; +diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c +--- a/sound/soc/codecs/da7213.c ++++ b/sound/soc/codecs/da7213.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -216,13 +217,10 @@ static int da7213_volsw_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_get_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_get_volsw(kcontrol, ucontrol); + } + + static int da7213_volsw_locked_put(struct snd_kcontrol *kcontrol, +@@ -230,13 +228,10 @@ static int da7213_volsw_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_put_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_put_volsw(kcontrol, ucontrol); + } + + static int da7213_enum_locked_get(struct snd_kcontrol *kcontrol, +@@ -244,13 +239,10 @@ static int da7213_enum_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_get_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_get_enum_double(kcontrol, ucontrol); + } + + static int da7213_enum_locked_put(struct snd_kcontrol *kcontrol, +@@ -258,13 +250,10 @@ static int da7213_enum_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_put_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_put_enum_double(kcontrol, ucontrol); + } + + /* ALC */ +@@ -465,9 +454,8 @@ static int da7213_tonegen_freq_get(struct snd_kcontrol *kcontrol, + __le16 val; + int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = regmap_raw_read(da7213->regmap, reg, &val, sizeof(val)); +- mutex_unlock(&da7213->ctrl_lock); ++ scoped_guard(mutex, &da7213->ctrl_lock) ++ ret = regmap_raw_read(da7213->regmap, reg, &val, sizeof(val)); + + if (ret) + return ret; +@@ -499,12 +487,11 @@ static int da7213_tonegen_freq_put(struct snd_kcontrol *kcontrol, + */ + val_new = cpu_to_le16(ucontrol->value.integer.value[0]); + +- mutex_lock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + ret = regmap_raw_read(da7213->regmap, reg, &val_old, sizeof(val_old)); + if (ret == 0 && (val_old != val_new)) + ret = regmap_raw_write(da7213->regmap, reg, + &val_new, sizeof(val_new)); +- mutex_unlock(&da7213->ctrl_lock); + + if (ret < 0) + return ret; +diff --git a/sound/soc/codecs/da7219.c b/sound/soc/codecs/da7219.c +--- a/sound/soc/codecs/da7219.c ++++ b/sound/soc/codecs/da7219.c +@@ -8,6 +8,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -256,13 +257,10 @@ static int da7219_volsw_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_get_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_get_volsw(kcontrol, ucontrol); + } + + static int da7219_volsw_locked_put(struct snd_kcontrol *kcontrol, +@@ -270,13 +268,10 @@ static int da7219_volsw_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_put_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_put_volsw(kcontrol, ucontrol); + } + + static int da7219_enum_locked_get(struct snd_kcontrol *kcontrol, +@@ -284,13 +279,10 @@ static int da7219_enum_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_get_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_get_enum_double(kcontrol, ucontrol); + } + + static int da7219_enum_locked_put(struct snd_kcontrol *kcontrol, +@@ -298,13 +290,10 @@ static int da7219_enum_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_put_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_put_enum_double(kcontrol, ucontrol); + } + + /* ALC */ +@@ -422,9 +411,8 @@ static int da7219_tonegen_freq_get(struct snd_kcontrol *kcontrol, + __le16 val; + int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = regmap_raw_read(da7219->regmap, reg, &val, sizeof(val)); +- mutex_unlock(&da7219->ctrl_lock); ++ scoped_guard(mutex, &da7219->ctrl_lock) ++ ret = regmap_raw_read(da7219->regmap, reg, &val, sizeof(val)); + + if (ret) + return ret; +@@ -456,12 +444,11 @@ static int da7219_tonegen_freq_put(struct snd_kcontrol *kcontrol, + */ + val_new = cpu_to_le16(ucontrol->value.integer.value[0]); + +- mutex_lock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + ret = regmap_raw_read(da7219->regmap, reg, &val_old, sizeof(val_old)); + if (ret == 0 && (val_old != val_new)) + ret = regmap_raw_write(da7219->regmap, reg, +- &val_new, sizeof(val_new)); +- mutex_unlock(&da7219->ctrl_lock); ++ &val_new, sizeof(val_new)); + + if (ret < 0) + return ret; +@@ -1167,15 +1154,12 @@ static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&da7219->pll_lock); ++ guard(mutex)(&da7219->pll_lock); + +- if ((da7219->clk_src == clk_id) && (da7219->mclk_rate == freq)) { +- mutex_unlock(&da7219->pll_lock); ++ if (da7219->clk_src == clk_id && da7219->mclk_rate == freq) + return 0; +- } + +- if ((freq < 2000000) || (freq > 54000000)) { +- mutex_unlock(&da7219->pll_lock); ++ if (freq < 2000000 || freq > 54000000) { + dev_err(codec_dai->dev, "Unsupported MCLK value %d\n", + freq); + return -EINVAL; +@@ -1193,7 +1177,6 @@ static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + break; + default: + dev_err(codec_dai->dev, "Unknown clock source %d\n", clk_id); +- mutex_unlock(&da7219->pll_lock); + return -EINVAL; + } + +@@ -1203,17 +1186,13 @@ static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + freq = clk_round_rate(da7219->mclk, freq); + ret = clk_set_rate(da7219->mclk, freq); + if (ret) { +- dev_err(codec_dai->dev, "Failed to set clock rate %d\n", +- freq); +- mutex_unlock(&da7219->pll_lock); ++ dev_err(codec_dai->dev, "Failed to set clock rate %d\n", freq); + return ret; + } + } + + da7219->mclk_rate = freq; + +- mutex_unlock(&da7219->pll_lock); +- + return 0; + } + +@@ -1296,13 +1275,10 @@ static int da7219_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + { + struct snd_soc_component *component = codec_dai->component; + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->pll_lock); +- ret = da7219_set_pll(component, source, fout); +- mutex_unlock(&da7219->pll_lock); ++ guard(mutex)(&da7219->pll_lock); + +- return ret; ++ return da7219_set_pll(component, source, fout); + } + + static int da7219_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c +--- a/sound/soc/codecs/es8316.c ++++ b/sound/soc/codecs/es8316.c +@@ -9,11 +9,13 @@ + + #include + #include ++#include + #include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -30,6 +32,13 @@ static const unsigned int supported_mclk_lrck_ratios[] = { + 256, 384, 400, 500, 512, 768, 1024 + }; + ++static const char * const es8316_supply_names[] = { ++ "avdd", ++ "cpvdd", ++ "dvdd", ++ "pvdd", ++}; ++ + struct es8316_priv { + struct mutex lock; + struct clk *mclk; +@@ -620,15 +629,15 @@ static irqreturn_t es8316_irq(int irq, void *data) + struct snd_soc_component *comp = es8316->component; + unsigned int flags; + +- mutex_lock(&es8316->lock); ++ guard(mutex)(&es8316->lock); + + regmap_read(es8316->regmap, ES8316_GPIO_FLAG, &flags); + if (flags == 0x00) +- goto out; /* Powered-down / reset */ ++ return IRQ_HANDLED; /* Powered-down / reset */ + + /* Catch spurious IRQ before set_jack is called */ + if (!es8316->jack) +- goto out; ++ return IRQ_HANDLED; + + if (es8316->jd_inverted) + flags ^= ES8316_GPIO_FLAG_HP_NOT_INSERTED; +@@ -681,8 +690,6 @@ static irqreturn_t es8316_irq(int irq, void *data) + } + } + +-out: +- mutex_unlock(&es8316->lock); + return IRQ_HANDLED; + } + +@@ -699,18 +706,16 @@ static void es8316_enable_jack_detect(struct snd_soc_component *component, + es8316->jd_inverted = device_property_read_bool(component->dev, + "everest,jack-detect-inverted"); + +- mutex_lock(&es8316->lock); ++ scoped_guard(mutex, &es8316->lock) { ++ es8316->jack = jack; + +- es8316->jack = jack; ++ if (es8316->jack->status & SND_JACK_MICROPHONE) ++ es8316_enable_micbias_for_mic_gnd_short_detect(component); + +- if (es8316->jack->status & SND_JACK_MICROPHONE) +- es8316_enable_micbias_for_mic_gnd_short_detect(component); +- +- snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE, +- ES8316_GPIO_ENABLE_INTERRUPT, +- ES8316_GPIO_ENABLE_INTERRUPT); +- +- mutex_unlock(&es8316->lock); ++ snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE, ++ ES8316_GPIO_ENABLE_INTERRUPT, ++ ES8316_GPIO_ENABLE_INTERRUPT); ++ } + + /* Enable irq and sync initial jack state */ + enable_irq(es8316->irq); +@@ -726,7 +731,7 @@ static void es8316_disable_jack_detect(struct snd_soc_component *component) + + disable_irq(es8316->irq); + +- mutex_lock(&es8316->lock); ++ guard(mutex)(&es8316->lock); + + snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE, + ES8316_GPIO_ENABLE_INTERRUPT, 0); +@@ -737,8 +742,6 @@ static void es8316_disable_jack_detect(struct snd_soc_component *component) + } + + es8316->jack = NULL; +- +- mutex_unlock(&es8316->lock); + } + + static int es8316_set_jack(struct snd_soc_component *component, +@@ -871,6 +874,11 @@ static int es8316_i2c_probe(struct i2c_client *i2c_client) + + i2c_set_clientdata(i2c_client, es8316); + ++ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(es8316_supply_names), ++ es8316_supply_names); ++ if (ret) ++ return dev_err_probe(dev, ret, "unable to enable supplies\n"); ++ + es8316->regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap); + if (IS_ERR(es8316->regmap)) + return PTR_ERR(es8316->regmap); +diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c +--- a/sound/soc/codecs/es8326.c ++++ b/sound/soc/codecs/es8326.c +@@ -6,6 +6,7 @@ + // Authors: David Yang + // + ++#include + #include + #include + #include +@@ -790,7 +791,7 @@ static void es8326_jack_button_handler(struct work_struct *work) + if (!(es8326->jack->status & SND_JACK_HEADSET)) /* Jack unplugged */ + return; + +- mutex_lock(&es8326->lock); ++ guard(mutex)(&es8326->lock); + iface = snd_soc_component_read(comp, ES8326_HPDET_STA); + switch (iface) { + case 0x93: +@@ -845,7 +846,6 @@ static void es8326_jack_button_handler(struct work_struct *work) + } + es8326_disable_micbias(es8326->component); + } +- mutex_unlock(&es8326->lock); + } + + static void es8326_jack_detect_handler(struct work_struct *work) +@@ -855,7 +855,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + struct snd_soc_component *comp = es8326->component; + unsigned int iface; + +- mutex_lock(&es8326->lock); ++ guard(mutex)(&es8326->lock); + iface = snd_soc_component_read(comp, ES8326_HPDET_STA); + dev_dbg(comp->dev, "gpio flag %#04x", iface); + +@@ -873,7 +873,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + regmap_update_bits(es8326->regmap, ES8326_HPDET_TYPE, + ES8326_HP_DET_JACK_POL, (es8326->jd_inverted ? + ~es8326->jack_pol : es8326->jack_pol)); +- goto exit; ++ return; + } + + if ((iface & ES8326_HPINSERT_FLAG) == 0) { +@@ -930,7 +930,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + queue_delayed_work(system_dfl_wq, &es8326->jack_detect_work, + msecs_to_jiffies(400)); + es8326->hp = 1; +- goto exit; ++ return; + } + if (es8326->jack->status & SND_JACK_HEADSET) { + /* detect button */ +@@ -939,7 +939,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + (ES8326_INT_SRC_PIN9 | ES8326_INT_SRC_BUTTON)); + es8326_enable_micbias(es8326->component); + queue_delayed_work(system_dfl_wq, &es8326->button_press_work, 10); +- goto exit; ++ return; + } + if ((iface & ES8326_HPBUTTON_FLAG) == 0x01) { + dev_dbg(comp->dev, "Headphone detected\n"); +@@ -958,8 +958,6 @@ static void es8326_jack_detect_handler(struct work_struct *work) + usleep_range(10000, 15000); + } + } +-exit: +- mutex_unlock(&es8326->lock); + } + + static irqreturn_t es8326_irq(int irq, void *dev_id) +@@ -1200,13 +1198,12 @@ static void es8326_enable_jack_detect(struct snd_soc_component *component, + { + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&es8326->lock); +- if (es8326->jd_inverted) +- snd_soc_component_update_bits(component, ES8326_HPDET_TYPE, +- ES8326_HP_DET_JACK_POL, ~es8326->jack_pol); +- es8326->jack = jack; +- +- mutex_unlock(&es8326->lock); ++ scoped_guard(mutex, &es8326->lock) { ++ if (es8326->jd_inverted) ++ snd_soc_component_update_bits(component, ES8326_HPDET_TYPE, ++ ES8326_HP_DET_JACK_POL, ~es8326->jack_pol); ++ es8326->jack = jack; ++ } + es8326_irq(es8326->irq, es8326); + } + +@@ -1219,13 +1216,12 @@ static void es8326_disable_jack_detect(struct snd_soc_component *component) + return; /* Already disabled (or never enabled) */ + cancel_delayed_work_sync(&es8326->jack_detect_work); + +- mutex_lock(&es8326->lock); ++ guard(mutex)(&es8326->lock); + if (es8326->jack->status & SND_JACK_MICROPHONE) { + es8326_disable_micbias(component); + snd_soc_jack_report(es8326->jack, 0, SND_JACK_HEADSET); + } + es8326->jack = NULL; +- mutex_unlock(&es8326->lock); + } + + static int es8326_set_jack(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c +--- a/sound/soc/codecs/es8328.c ++++ b/sound/soc/codecs/es8328.c +@@ -405,6 +405,11 @@ static const struct snd_soc_dapm_route es8328_dapm_routes[] = { + + { "Mic Bias", NULL, "Mic Bias Gen" }, + ++ { "LINPUT1", NULL, "Mic Bias" }, ++ { "RINPUT1", NULL, "Mic Bias" }, ++ { "LINPUT2", NULL, "Mic Bias" }, ++ { "RINPUT2", NULL, "Mic Bias" }, ++ + { "Left Mixer", NULL, "Left DAC" }, + { "Left Mixer", "Left Bypass Switch", "Left Line Mux" }, + { "Left Mixer", "Right Playback Switch", "Right DAC" }, +diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c +--- a/sound/soc/codecs/es8389.c ++++ b/sound/soc/codecs/es8389.c +@@ -36,6 +36,10 @@ struct es8389_private { + unsigned int sysclk; + int mastermode; + ++ u8 hpfl; ++ u8 hpfr; ++ u32 hpf_freq; ++ u32 capture_rate; + u8 mclk_src; + u8 vddd; + int version; +@@ -50,10 +54,29 @@ static const char * const es8389_core_supplies[] = { + static bool es8389_volatile_register(struct device *dev, + unsigned int reg) + { +- if ((reg <= 0xff)) +- return true; +- else ++ switch (reg) { ++ case ES8389_ADCL_VOL: ++ case ES8389_ADCR_VOL: ++ case ES8389_MIC1_GAIN: ++ case ES8389_MIC2_GAIN: ++ case ES8389_DACL_VOL: ++ case ES8389_DACR_VOL: ++ case ES8389_ALC_ON: ++ case ES8389_ALC_CTL: ++ case ES8389_ALC_TARGET: ++ case ES8389_ALC_GAIN: ++ case ES8389_ADC_MUTE: ++ case ES8389_OSR_VOL: ++ case ES8389_DAC_INV: ++ case ES8389_MIX_VOL: ++ case ES8389_DAC_MIX: ++ case ES8389_ADC_RESET: ++ case ES8389_ADC_MODE: ++ case ES8389_DMIC_EN: + return false; ++ default: ++ return true; ++ } + } + + static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -9550, 50, 0); +@@ -63,6 +86,92 @@ static const DECLARE_TLV_DB_SCALE(mix_vol_tlv, -9500, 100, 0); + static const DECLARE_TLV_DB_SCALE(alc_target_tlv, -3200, 200, 0); + static const DECLARE_TLV_DB_SCALE(alc_max_level, -3200, 200, 0); + ++static const u32 hpf_table[10][10] = { ++ {1020, 754, 624, 559, 527, 511, 502, 498, 497, 496}, ++ {754, 495, 368, 306, 274, 259, 251, 247, 246, 244}, ++ {624, 368, 243, 182, 151, 136, 128, 124, 123, 121}, ++ {559, 306, 182, 120, 90, 75, 68, 63, 62, 60}, ++ {527, 274, 151, 90, 60, 45, 38, 33, 32, 31}, ++ {511, 259, 136, 75, 45, 30, 23, 19, 18, 17}, ++ {502, 251, 128, 68, 38, 23, 16, 13, 11, 11}, ++ {498, 247, 124, 63, 33, 19, 13, 10, 8, 8}, ++ {497, 246, 123, 62, 32, 18, 11, 8, 8, 0}, ++ {496, 244, 121, 60, 31, 17, 11, 8, 0, 0} ++}; ++ ++static bool find_best_hpf_freq(u32 target_hz, u8 *hpf1, u8 *hpf2, u32 *out) ++{ ++ int best_row = -1, best_col = -1; ++ u32 min_diff = U32_MAX; ++ u32 f, diff; ++ int i, j; ++ ++ if (target_hz > 1020) ++ return false; ++ ++ for (i = 0; i < 10; i++) { ++ for (j = i; j < 10; j++) { ++ f = hpf_table[i][j]; ++ ++ diff = (target_hz > f) ? (target_hz - f) : (f - target_hz); ++ if (diff < min_diff) { ++ min_diff = diff; ++ best_row = i; ++ best_col = j; ++ *out = f; ++ } ++ } ++ } ++ ++ *hpf1 = best_col + ES8389_HPF_OFFSET; ++ *hpf2 = best_row + ES8389_HPF_OFFSET; ++ ++ return true; ++} ++ ++static int es8389_hpf_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = es8389->hpf_freq; ++ return 0; ++} ++ ++static int es8389_hpf_set(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ u32 freq; ++ bool hpf; ++ ++ if (es8389->hpf_freq == ucontrol->value.integer.value[0]) ++ return 0; ++ ++ if (es8389->capture_rate) { ++ freq = (ucontrol->value.integer.value[0] * 48000) / es8389->capture_rate; ++ ++ hpf = find_best_hpf_freq(freq, &es8389->hpfl, &es8389->hpfr, &es8389->hpf_freq); ++ if (!hpf) ++ return -EBUSY; ++ ++ if (es8389->hpf_freq != ucontrol->value.integer.value[0]) ++ dev_dbg(component->dev, "At the %u Hz sampling rate, %ld Hz could not be obtained." ++ "the frequency has been set to the closest value, %u Hz\n", ++ es8389->capture_rate, ucontrol->value.integer.value[0], es8389->hpf_freq); ++ ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, es8389->hpfl); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, es8389->hpfr); ++ } else { ++ es8389->hpf_freq = ucontrol->value.integer.value[0]; ++ dev_dbg(component->dev, "PCM_STREAM_CAPTURE is not active.retain the input frequency\n"); ++ } ++ ++ return 1; ++} ++ + static int es8389_dmic_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) + { +@@ -143,6 +252,16 @@ static const struct soc_enum alc_ramprate = + static const struct soc_enum alc_winsize = + SOC_ENUM_SINGLE(ES8389_ALC_CTL, 0, 16, winsize); + ++static const char *const es8389_adcl_mux_txt[] = { ++ "Normal", ++ "ADC2 channel to ADC1 channel", ++}; ++ ++static const char *const es8389_adcr_mux_txt[] = { ++ "Normal", ++ "ADC1 channel to ADC2 channel", ++}; ++ + static const char *const es8389_outl_mux_txt[] = { + "Normal", + "DAC2 channel to DAC1 channel", +@@ -170,6 +289,20 @@ static const unsigned int es8389_pga_values[] = { + 1, 5, 6 + }; + ++static const struct soc_enum es8389_adcl_mux_enum = ++ SOC_ENUM_SINGLE(ES8389_ADC_MODE, 5, ++ ARRAY_SIZE(es8389_adcl_mux_txt), es8389_adcl_mux_txt); ++ ++static const struct snd_kcontrol_new es8389_adcl_mux_controls = ++ SOC_DAPM_ENUM("INPUTL MUX", es8389_adcl_mux_enum); ++ ++static const struct soc_enum es8389_adcr_mux_enum = ++ SOC_ENUM_SINGLE(ES8389_ADC_MODE, 4, ++ ARRAY_SIZE(es8389_adcr_mux_txt), es8389_adcr_mux_txt); ++ ++static const struct snd_kcontrol_new es8389_adcr_mux_controls = ++ SOC_DAPM_ENUM("INPUTR MUX", es8389_adcr_mux_enum); ++ + static const struct soc_enum es8389_outl_mux_enum = + SOC_ENUM_SINGLE(ES8389_DAC_MIX, 5, + ARRAY_SIZE(es8389_outl_mux_txt), es8389_outl_mux_txt); +@@ -240,6 +373,8 @@ static const struct snd_kcontrol_new es8389_snd_controls[] = { + SOC_DOUBLE("ADC OSR Volume ON Switch", ES8389_ADC_MUTE, 6, 7, 1, 0), + SOC_SINGLE_TLV("ADC OSR Volume", ES8389_OSR_VOL, 0, 0xFF, 0, adc_vol_tlv), + SOC_DOUBLE("ADC OUTPUT Invert Switch", ES8389_ADC_HPF2, 5, 6, 1, 0), ++ SOC_SINGLE_EXT("ADC HPF Freq Select", SND_SOC_NOPM, 0, 1020, 0, ++ es8389_hpf_get, es8389_hpf_set), + + SOC_SINGLE_TLV("DACL Playback Volume", ES8389_DACL_VOL, 0, 0xFF, 0, dac_vol_tlv), + SOC_SINGLE_TLV("DACR Playback Volume", ES8389_DACR_VOL, 0, 0xFF, 0, dac_vol_tlv), +@@ -298,6 +433,8 @@ static const struct snd_soc_dapm_widget es8389_dapm_widgets[] = { + &es8389_adc_mixer_controls[0], + ARRAY_SIZE(es8389_adc_mixer_controls)), + SND_SOC_DAPM_MUX("ADC MUX", SND_SOC_NOPM, 0, 0, &es8389_dmic_mux_controls), ++ SND_SOC_DAPM_MUX("INPUTL MUX", SND_SOC_NOPM, 0, 0, &es8389_adcl_mux_controls), ++ SND_SOC_DAPM_MUX("INPUTR MUX", SND_SOC_NOPM, 0, 0, &es8389_adcr_mux_controls), + + SND_SOC_DAPM_MUX("OUTL MUX", SND_SOC_NOPM, 0, 0, &es8389_outl_mux_controls), + SND_SOC_DAPM_MUX("OUTR MUX", SND_SOC_NOPM, 0, 0, &es8389_outr_mux_controls), +@@ -311,10 +448,15 @@ static const struct snd_soc_dapm_route es8389_dapm_routes[] = { + {"ADCL", NULL, "PGAL"}, + {"ADCR", NULL, "PGAR"}, + ++ {"INPUTL MUX", "Normal", "ADCL"}, ++ {"INPUTL MUX", "ADC2 channel to ADC1 channel", "ADCR"}, ++ {"INPUTR MUX", "Normal", "ADCR"}, ++ {"INPUTR MUX", "ADC1 channel to ADC2 channel", "ADCL"}, ++ + {"ADC Mixer", "DACL ADCL Mixer", "DACL"}, + {"ADC Mixer", "DACR ADCR Mixer", "DACR"}, +- {"ADC Mixer", NULL, "ADCL"}, +- {"ADC Mixer", NULL, "ADCR"}, ++ {"ADC Mixer", NULL, "INPUTL MUX"}, ++ {"ADC Mixer", NULL, "INPUTR MUX"}, + + {"ADC MUX", "AMIC", "ADC Mixer"}, + {"ADC MUX", "DMIC", "DMIC"}, +@@ -415,52 +557,54 @@ static const struct _coeff_div coeff_div[] = { + {36, 576000, 16000, 0x00, 0x55, 0x84, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {48, 768000, 16000, 0x02, 0x57, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {50, 800000, 16000, 0x00, 0x7E, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {64, 1024000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {72, 1152000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {64, 1024000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {72, 1152000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {96, 1536000, 16000, 0x02, 0x55, 0x84, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {128, 2048000, 16000, 0x00, 0x51, 0x04, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {144, 2304000, 16000, 0x00, 0x51, 0x00, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {150, 2400000, 16000, 0x02, 0x7E, 0x01, 0xC9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {192, 3072000, 16000, 0x02, 0x65, 0x25, 0xE0, 0x00, 0xE1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {256, 4096000, 16000, 0x00, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {256, 4096000, 16000, 0x00, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {300, 4800000, 16000, 0x02, 0x66, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {384, 6144000, 16000, 0x02, 0x51, 0x04, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {512, 8192000, 16000, 0x01, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {512, 8192000, 16000, 0x01, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {750, 12000000, 16000, 0x0E, 0x7E, 0x01, 0xC9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {768, 12288000, 16000, 0x02, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {1024, 16384000, 16000, 0x03, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {768, 12288000, 16000, 0x02, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {1024, 16384000, 16000, 0x03, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1152, 18432000, 16000, 0x08, 0x51, 0x04, 0xD0, 0x01, 0xC1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1200, 19200000, 16000, 0x0B, 0x66, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1500, 24000000, 16000, 0x0E, 0x26, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {1536, 24576000, 16000, 0x05, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {1536, 24576000, 16000, 0x05, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1625, 26000000, 16000, 0x40, 0x6E, 0x05, 0xC8, 0x01, 0xC2, 0x90, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {800, 19200000, 24000, 0x07, 0x66, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x1A, 0x49, 0x14, 2, 2}, + {375, 12000000, 32000, 0x0E, 0x2E, 0x05, 0xC8, 0x00, 0xC2, 0x80, 0x40, 0x01, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x23, 0x61, 0x1B, 2, 0}, +- {600, 19200000, 32000, 0x05, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x23, 0x61, 0x1B, 2, 2}, +- {32, 1411200, 44100, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {600, 19200000, 32000, 0x05, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x23, 0x61, 0x1B, 2, 2}, ++ {32, 1411200, 44100, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {64, 2822400, 44100, 0x00, 0x51, 0x00, 0xC0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {128, 5644800, 44100, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {256, 11289600, 44100, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {512, 22579200, 44100, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {32, 1536000, 48000, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {128, 5644800, 44100, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {256, 11289600, 44100, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {512, 22579200, 44100, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {32, 1536000, 48000, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {48, 2304000, 48000, 0x02, 0x55, 0x04, 0xC0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {50, 2400000, 48000, 0x00, 0x76, 0x01, 0xC8, 0x10, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {64, 3072000, 48000, 0x00, 0x51, 0x04, 0xC0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {100, 4800000, 48000, 0x00, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {100, 4800000, 48000, 0x00, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {125, 6000000, 48000, 0x04, 0x6E, 0x05, 0xC8, 0x10, 0xC2, 0x80, 0x00, 0x01, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {128, 6144000, 48000, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {200, 9600000, 48000, 0x01, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {128, 6144000, 48000, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {200, 9600000, 48000, 0x01, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {250, 12000000, 48000, 0x04, 0x76, 0x01, 0xC8, 0x10, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {256, 12288000, 48000, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {384, 18432000, 48000, 0x02, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {400, 19200000, 48000, 0x03, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {500, 24000000, 48000, 0x04, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {512, 24576000, 48000, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {256, 12288000, 48000, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {384, 18432000, 48000, 0x02, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {400, 19200000, 48000, 0x03, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {500, 24000000, 48000, 0x04, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {512, 24576000, 48000, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {800, 38400000, 48000, 0x18, 0x45, 0x04, 0xC0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {128, 11289600, 88200, 0x00, 0x50, 0x00, 0xC0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x32, 0x89, 0x25, 2, 2}, +- {64, 6144000, 96000, 0x00, 0x41, 0x00, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, ++ {64, 6144000, 96000, 0x00, 0x41, 0x00, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, + {96, 9216000, 96000, 0x02, 0x43, 0x00, 0xC0, 0x10, 0xC0, 0x80, 0x00, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, + {256, 24576000, 96000, 0x00, 0x40, 0x00, 0xC0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, + {128, 24576000, 192000, 0x00, 0x50, 0x00, 0xC0, 0x18, 0xC1, 0x81, 0xC0, 0x00, 0x8F, 0x7F, 0xBF, 0xC0, 0x3F, 0x7F, 0x80, 0x12, 0xC0, 0x3F, 0xF9, 0x3F, 2, 2}, ++ {64, 12288000, 192000, 0x00, 0x41, 0x00, 0xC0, 0x18, 0xC1, 0x80, 0x00, 0x00, 0x8F, 0x7F, 0xEF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x3F, 0xF9, 0x3F, 1, 0}, + }; + + static inline int get_coeff(u8 vddd, u8 dmic, int mclk, int rate) +@@ -564,6 +708,8 @@ static int es8389_pcm_hw_params(struct snd_pcm_substream *substream, + int coeff, ret; + u8 dmic_enable, state = 0; + unsigned int regv; ++ u32 freq; ++ bool hpf; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: +@@ -590,7 +736,7 @@ static int es8389_pcm_hw_params(struct snd_pcm_substream *substream, + + if (es8389->mclk_src == ES8389_SCLK_PIN) { + regmap_update_bits(es8389->regmap, ES8389_MASTER_CLK, +- ES8389_MCLK_SOURCE, es8389->mclk_src); ++ ES8389_MCLK_MASK, ES8389_MCLK_FROM_SCLK); + es8389->sysclk = params_channels(params) * params_width(params) * params_rate(params); + } + +@@ -641,6 +787,28 @@ static int es8389_pcm_hw_params(struct snd_pcm_substream *substream, + dev_warn(component->dev, "Clock coefficients do not match"); + } + ++ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { ++ es8389->capture_rate = params_rate(params); ++ freq = (es8389->hpf_freq * 48000) / params_rate(params); ++ hpf = find_best_hpf_freq(freq, &es8389->hpfl, &es8389->hpfr, &es8389->hpf_freq); ++ if (!hpf) { ++ dev_err(component->dev, "The HPF frequency is invalid\n"); ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++} ++ ++static int es8389_pcm_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_component *component = dai->component; ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ ++ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ++ es8389->capture_rate = 0; ++ + return 0; + } + +@@ -721,8 +889,8 @@ static int es8389_mute(struct snd_soc_dai *dai, int mute, int direction) + regmap_update_bits(es8389->regmap, ES8389_DAC_FORMAT_MUTE, + 0x03, 0x00); + } else { +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x0a); +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x0a); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, es8389->hpfl); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, es8389->hpfr); + regmap_update_bits(es8389->regmap, ES8389_ADC_FORMAT_MUTE, + 0x03, 0x00); + } +@@ -738,6 +906,7 @@ static int es8389_mute(struct snd_soc_dai *dai, int mute, int direction) + + static const struct snd_soc_dai_ops es8389_ops = { + .hw_params = es8389_pcm_hw_params, ++ .hw_free = es8389_pcm_hw_free, + .set_fmt = es8389_set_dai_fmt, + .set_sysclk = es8389_set_dai_sysclk, + .set_tdm_slot = es8389_set_tdm_slot, +@@ -771,7 +940,7 @@ static void es8389_init(struct snd_soc_component *component) + + regmap_read(es8389->regmap, ES8389_MAX_REGISTER, ®); + es8389->version = reg; +- regmap_write(es8389->regmap, ES8389_ISO_CTL, 0x00); ++ regmap_write(es8389->regmap, ES8389_ISO_CTL, 0x56); + regmap_write(es8389->regmap, ES8389_RESET, 0x7E); + regmap_write(es8389->regmap, ES8389_ISO_CTL, 0x38); + regmap_write(es8389->regmap, ES8389_ADC_HPF1, 0x64); +@@ -823,7 +992,7 @@ static void es8389_init(struct snd_soc_component *component) + regmap_write(es8389->regmap, ES8389_SCLK_DIV, 0x04); + regmap_write(es8389->regmap, ES8389_LRCK_DIV1, 0x01); + regmap_write(es8389->regmap, ES8389_LRCK_DIV2, 0x00); +- regmap_write(es8389->regmap, ES8389_OSC_CLK, 0x00); ++ regmap_write(es8389->regmap, ES8389_OSC_CLK, 0x10); + regmap_write(es8389->regmap, ES8389_ADC_OSR, 0x1F); + regmap_write(es8389->regmap, ES8389_ADC_DSP, 0x7F); + regmap_write(es8389->regmap, ES8389_ADC_MUTE, 0xC0); +@@ -861,13 +1030,13 @@ static int es8389_resume(struct snd_soc_component *component) + regcache_cache_only(es8389->regmap, false); + regcache_cache_bypass(es8389->regmap, true); + regmap_read(es8389->regmap, ES8389_RESET, ®v); +- regcache_cache_bypass(es8389->regmap, false); + + if (regv == 0xff) + es8389_init(component); + else + es8389_set_bias_level(component, SND_SOC_BIAS_ON); + ++ regcache_cache_bypass(es8389->regmap, false); + regcache_sync(es8389->regmap); + + return 0; +@@ -913,6 +1082,7 @@ static int es8389_probe(struct snd_soc_component *component) + return ret; + } + ++ es8389->hpf_freq = ES8389_HPF_DEFAULT; + es8389_init(component); + es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); + +diff --git a/sound/soc/codecs/es8389.h b/sound/soc/codecs/es8389.h +--- a/sound/soc/codecs/es8389.h ++++ b/sound/soc/codecs/es8389.h +@@ -106,6 +106,9 @@ + #define ES8389_MIC_SEL_MASK (7 << 4) + #define ES8389_MIC_DEFAULT (1 << 4) + ++#define ES8389_HPF_DEFAULT 16 ++#define ES8389_HPF_OFFSET 4 ++ + #define ES8389_MASTER_MODE_EN (1 << 0) + + #define ES8389_TDM_OFF (0 << 0) +@@ -116,9 +119,11 @@ + #define ES8389_TDM_SLOT (0x70 << 0) + #define ES8389_TDM_SHIFT 4 + +-#define ES8389_MCLK_SOURCE (1 << 6) +-#define ES8389_MCLK_PIN (1 << 6) +-#define ES8389_SCLK_PIN (0 << 6) ++#define ES8389_MCLK_MASK (3 << 6) ++#define ES8389_MCLK_FROM_SCLK (1 << 6) ++#define ES8389_MCLK_SOURCE ES8389_MCLK_PIN ++#define ES8389_MCLK_PIN 0 ++#define ES8389_SCLK_PIN 1 + + /* ES8389_FMT */ + #define ES8389_S24_LE (0 << 5) +diff --git a/sound/soc/codecs/es9356.c b/sound/soc/codecs/es9356.c +--- a/sound/soc/codecs/es9356.c ++++ b/sound/soc/codecs/es9356.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -19,8 +20,6 @@ + #include + #include + #include +-#include +-#include + #include + #include + #include +@@ -500,16 +499,19 @@ static int es9356_power_state(struct snd_soc_dai *dai, unsigned char ps, unsigne + } + + /* power state changes are not independent across functions */ +- mutex_lock(&es9356->pde_lock); +- ret = es9356_pde_transition_delay(es9356, func, pde_entity, ps?ps0:ps3); +- if (ret) { +- regmap_write(es9356->regmap, +- SDW_SDCA_CTL(func, pde_entity, ES9356_SDCA_CTL_REQ_POWER_STATE, 0), ps?ps3:ps0); +- es9356_pde_transition_delay(es9356, func, pde_entity, ps?ps3:ps0); +- } else +- dev_dbg(component->dev, "%s PDE is already %d\n", __func__, ps?ps0:ps3); +- +- mutex_unlock(&es9356->pde_lock); ++ scoped_guard(mutex, &es9356->pde_lock) { ++ ret = es9356_pde_transition_delay(es9356, func, pde_entity, ps ? ps0 : ps3); ++ if (ret) { ++ regmap_write(es9356->regmap, ++ SDW_SDCA_CTL(func, pde_entity, ++ ES9356_SDCA_CTL_REQ_POWER_STATE, 0), ++ ps ? ps3 : ps0); ++ es9356_pde_transition_delay(es9356, func, pde_entity, ps ? ps3 : ps0); ++ } else { ++ dev_dbg(component->dev, "%s PDE is already %d\n", __func__, ++ ps ? ps0 : ps3); ++ } ++ } + + if (rate) + regmap_write(es9356->regmap, +@@ -671,7 +673,7 @@ static int es9356_sdca_mbq_size(struct device *dev, unsigned int reg) + } + } + +-static struct regmap_sdw_mbq_cfg es9356_mbq_config = { ++static const struct regmap_sdw_mbq_cfg es9356_mbq_config = { + .mbq_size = es9356_sdca_mbq_size, + }; + +@@ -1091,9 +1093,8 @@ static int es9356_sdca_dev_system_suspend(struct device *dev) + { + struct es9356_sdw_priv *es9356 = dev_get_drvdata(dev); + +- mutex_lock(&es9356->disable_irq_lock); +- es9356->disable_irq = true; +- mutex_unlock(&es9356->disable_irq_lock); ++ scoped_guard(mutex, &es9356->disable_irq_lock) ++ es9356->disable_irq = true; + + return es9356_sdca_dev_suspend(dev); + } +diff --git a/sound/soc/codecs/fs-amp-lib.c b/sound/soc/codecs/fs-amp-lib.c +--- a/sound/soc/codecs/fs-amp-lib.c ++++ b/sound/soc/codecs/fs-amp-lib.c +@@ -221,7 +221,7 @@ static void fs_print_firmware_info(struct fs_amp_lib *amp_lib) + + int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) + { +- const struct firmware *cont; ++ const struct firmware *cont __free(firmware) = NULL; + struct fs_fwm_header *hdr; + int ret; + +@@ -237,7 +237,6 @@ int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) + dev_info(amp_lib->dev, "Loading %s - size: %zu\n", name, cont->size); + + hdr = devm_kmemdup(amp_lib->dev, cont->data, cont->size, GFP_KERNEL); +- release_firmware(cont); + if (!hdr) + return -ENOMEM; + +diff --git a/sound/soc/codecs/fs210x.c b/sound/soc/codecs/fs210x.c +--- a/sound/soc/codecs/fs210x.c ++++ b/sound/soc/codecs/fs210x.c +@@ -4,6 +4,7 @@ + // + // Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + ++#include + #include + #include + #include +@@ -771,9 +772,8 @@ static int fs210x_dai_hw_params(struct snd_pcm_substream *substream, + if (fs210x->devid == FS2105S_DEVICE_ID && fs210x->srate == 16000) + return -EOPNOTSUPP; + +- mutex_lock(&fs210x->lock); +- ret = fs210x_set_hw_params(fs210x); +- mutex_unlock(&fs210x->lock); ++ scoped_guard(mutex, &fs210x->lock) ++ ret = fs210x_set_hw_params(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to set hw params: %d\n", ret); + +@@ -790,15 +790,11 @@ static int fs210x_dai_mute(struct snd_soc_dai *dai, int mute, int stream) + + fs210x = snd_soc_component_get_drvdata(dai->component); + +- mutex_lock(&fs210x->lock); +- +- if (!fs210x->is_inited || fs210x->is_suspended) { +- mutex_unlock(&fs210x->lock); +- return 0; ++ scoped_guard(mutex, &fs210x->lock) { ++ if (!fs210x->is_inited || fs210x->is_suspended) ++ return 0; + } + +- mutex_unlock(&fs210x->lock); +- + if (mute) { + cancel_delayed_work_sync(&fs210x->fault_check_work); + cancel_delayed_work_sync(&fs210x->start_work); +@@ -817,15 +813,11 @@ static int fs210x_dai_trigger(struct snd_pcm_substream *substream, + + fs210x = snd_soc_component_get_drvdata(dai->component); + +- mutex_lock(&fs210x->lock); +- +- if (!fs210x->is_inited || fs210x->is_suspended || fs210x->is_playing) { +- mutex_unlock(&fs210x->lock); +- return 0; ++ scoped_guard(mutex, &fs210x->lock) { ++ if (!fs210x->is_inited || fs210x->is_suspended || fs210x->is_playing) ++ return 0; + } + +- mutex_unlock(&fs210x->lock); +- + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: +@@ -853,13 +845,11 @@ static void fs210x_start_work(struct work_struct *work) + + fs210x = container_of(work, struct fs210x_priv, start_work.work); + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + + ret = fs210x_dev_play(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to start playing: %d\n", ret); +- +- mutex_unlock(&fs210x->lock); + } + + static void fs210x_fault_check_work(struct work_struct *work) +@@ -870,15 +860,13 @@ static void fs210x_fault_check_work(struct work_struct *work) + + fs210x = container_of(work, struct fs210x_priv, fault_check_work.work); + +- mutex_lock(&fs210x->lock); ++ scoped_guard(mutex, &fs210x->lock) { ++ if (!fs210x->is_inited || fs210x->is_suspended || !fs210x->is_playing) ++ return; + +- if (!fs210x->is_inited || fs210x->is_suspended || !fs210x->is_playing) { +- mutex_unlock(&fs210x->lock); +- return; ++ ret = fs210x_reg_read(fs210x, FS210X_05H_ANASTAT, &status); + } + +- ret = fs210x_reg_read(fs210x, FS210X_05H_ANASTAT, &status); +- mutex_unlock(&fs210x->lock); + if (ret) + return; + +@@ -991,7 +979,7 @@ static int fs210x_effect_scene_get(struct snd_kcontrol *kcontrol, + if (fs210x->scene_id < 1) + return -EINVAL; + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + /* + * FS210x has scene(s) as below: + * init scene: id = 0 +@@ -1000,7 +988,6 @@ static int fs210x_effect_scene_get(struct snd_kcontrol *kcontrol, + */ + index = fs210x->scene_id - 1; + ucontrol->value.integer.value[0] = index; +- mutex_unlock(&fs210x->lock); + + return 0; + } +@@ -1019,7 +1006,7 @@ static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + return -EINVAL; + } + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + + /* + * FS210x has scene(s) as below: +@@ -1029,17 +1016,14 @@ static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + */ + scene_id = ucontrol->value.integer.value[0] + 1; + scene_count = fs210x->amp_lib.scene_count - 1; /* Skip init scene */ +- if (scene_id < 1 || scene_id > scene_count) { +- mutex_unlock(&fs210x->lock); ++ if (scene_id < 1 || scene_id > scene_count) + return -ERANGE; +- } + + if (scene_id != fs210x->scene_id) + is_changed = true; + + if (fs210x->is_suspended) { + fs210x->scene_id = scene_id; +- mutex_unlock(&fs210x->lock); + return is_changed; + } + +@@ -1047,8 +1031,6 @@ static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + if (ret) + dev_err(fs210x->dev, "Failed to set scene: %d\n", ret); + +- mutex_unlock(&fs210x->lock); +- + if (!ret && is_changed) + return 1; + +@@ -1062,12 +1044,10 @@ static int fs210x_playback_event(struct snd_soc_dapm_widget *w, + struct fs210x_priv *fs210x = snd_soc_component_get_drvdata(cmpnt); + int ret = 0; + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + +- if (fs210x->is_suspended) { +- mutex_unlock(&fs210x->lock); ++ if (fs210x->is_suspended) + return 0; +- } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +@@ -1088,8 +1068,6 @@ static int fs210x_playback_event(struct snd_soc_dapm_widget *w, + break; + } + +- mutex_unlock(&fs210x->lock); +- + return ret; + } + +@@ -1220,11 +1198,9 @@ static int fs210x_probe(struct snd_soc_component *cmpnt) + if (ret) + return ret; + +- mutex_lock(&fs210x->lock); +- ret = fs210x_init_chip(fs210x); +- mutex_unlock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + +- return ret; ++ return fs210x_init_chip(fs210x); + } + + static void fs210x_remove(struct snd_soc_component *cmpnt) +@@ -1251,15 +1227,15 @@ static int fs210x_suspend(struct snd_soc_component *cmpnt) + + regcache_cache_only(fs210x->regmap, true); + +- mutex_lock(&fs210x->lock); +- fs210x->cur_scene = NULL; +- fs210x->is_inited = false; +- fs210x->is_playing = false; +- fs210x->is_suspended = true; ++ scoped_guard(mutex, &fs210x->lock) { ++ fs210x->cur_scene = NULL; ++ fs210x->is_inited = false; ++ fs210x->is_playing = false; ++ fs210x->is_suspended = true; + +- gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */ +- fsleep(30000); /* >= 30ms */ +- mutex_unlock(&fs210x->lock); ++ gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */ ++ fsleep(30000); /* >= 30ms */ ++ } + + cancel_delayed_work_sync(&fs210x->start_work); + cancel_delayed_work_sync(&fs210x->fault_check_work); +@@ -1288,14 +1264,11 @@ static int fs210x_resume(struct snd_soc_component *cmpnt) + return ret; + } + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + + fs210x->is_suspended = false; +- ret = fs210x_init_chip(fs210x); + +- mutex_unlock(&fs210x->lock); +- +- return ret; ++ return fs210x_init_chip(fs210x); + } + #else + #define fs210x_suspend NULL +diff --git a/sound/soc/codecs/hdac_hda.c b/sound/soc/codecs/hdac_hda.c +--- a/sound/soc/codecs/hdac_hda.c ++++ b/sound/soc/codecs/hdac_hda.c +@@ -437,7 +437,7 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) + + #ifdef CONFIG_SND_HDA_PATCH_LOADER + if (loadable_patch[hda_pvt->dev_index] && *loadable_patch[hda_pvt->dev_index]) { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + + dev_info(&hdev->dev, "Applying patch firmware '%s'\n", + loadable_patch[hda_pvt->dev_index]); +@@ -451,7 +451,6 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) + dev_err(&hdev->dev, "%s: failed to load hda patch %d\n", __func__, ret); + goto error_no_pm; + } +- release_firmware(fw); + } + } + #endif +diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c +--- a/sound/soc/codecs/hdac_hdmi.c ++++ b/sound/soc/codecs/hdac_hdmi.c +@@ -10,6 +10,7 @@ + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + ++#include + #include + #include + #include +@@ -537,10 +538,11 @@ static struct hdac_hdmi_port *hdac_hdmi_get_port_from_cvt( + continue; + + list_for_each_entry(port, &pcm->port_list, head) { +- mutex_lock(&pcm->lock); +- ret = hdac_hdmi_query_port_connlist(hdev, +- port->pin, port); +- mutex_unlock(&pcm->lock); ++ scoped_guard(mutex, &pcm->lock) { ++ ret = hdac_hdmi_query_port_connlist(hdev, ++ port->pin, ++ port); ++ } + if (ret < 0) + continue; + +@@ -640,11 +642,11 @@ static void hdac_hdmi_pcm_close(struct snd_pcm_substream *substream, + pcm = hdac_hdmi_get_pcm_from_cvt(hdmi, dai_map->cvt); + + if (pcm) { +- mutex_lock(&pcm->lock); +- pcm->chmap_set = false; +- memset(pcm->chmap, 0, sizeof(pcm->chmap)); +- pcm->channels = 0; +- mutex_unlock(&pcm->lock); ++ scoped_guard(mutex, &pcm->lock) { ++ pcm->chmap_set = false; ++ memset(pcm->chmap, 0, sizeof(pcm->chmap)); ++ pcm->channels = 0; ++ } + } + + if (dai_map->port) +@@ -922,7 +924,7 @@ static int hdac_hdmi_set_pin_port_mux(struct snd_kcontrol *kcontrol, + if (port == NULL) + return -EINVAL; + +- mutex_lock(&hdmi->pin_mutex); ++ guard(mutex)(&hdmi->pin_mutex); + list_for_each_entry(pcm, &hdmi->pcm_list, head) { + if (list_empty(&pcm->port_list)) + continue; +@@ -945,12 +947,10 @@ static int hdac_hdmi_set_pin_port_mux(struct snd_kcontrol *kcontrol, + list_add_tail(&port->head, &pcm->port_list); + if (port->eld.monitor_present && port->eld.eld_valid) { + hdac_hdmi_jack_report_sync(pcm, port, true); +- mutex_unlock(&hdmi->pin_mutex); + return ret; + } + } + } +- mutex_unlock(&hdmi->pin_mutex); + + return ret; + } +@@ -1274,67 +1274,65 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, + * In case of non MST pin, get_eld info API expectes port + * to be -1. + */ +- mutex_lock(&hdmi->pin_mutex); +- port->eld.monitor_present = false; ++ scoped_guard(mutex, &hdmi->pin_mutex) { ++ port->eld.monitor_present = false; + +- if (pin->mst_capable) +- port_id = port->id; ++ if (pin->mst_capable) ++ port_id = port->id; + +- size = snd_hdac_acomp_get_eld(hdev, pin->nid, port_id, +- &port->eld.monitor_present, +- port->eld.eld_buffer, +- ELD_MAX_SIZE); ++ size = snd_hdac_acomp_get_eld(hdev, pin->nid, port_id, ++ &port->eld.monitor_present, ++ port->eld.eld_buffer, ++ ELD_MAX_SIZE); + +- if (size > 0) { +- size = min(size, ELD_MAX_SIZE); +- if (hdac_hdmi_parse_eld(hdev, port) < 0) +- size = -EINVAL; +- } +- +- eld_valid = port->eld.eld_valid; +- +- if (size > 0) { +- port->eld.eld_valid = true; +- port->eld.eld_size = size; +- } else { +- port->eld.eld_valid = false; +- port->eld.eld_size = 0; +- } +- +- eld_changed = (eld_valid != port->eld.eld_valid); +- +- pcm = hdac_hdmi_get_pcm(hdev, port); +- +- if (!port->eld.monitor_present || !port->eld.eld_valid) { +- +- dev_dbg(&hdev->dev, "%s: disconnect for pin:port %d:%d\n", +- __func__, pin->nid, port->id); +- +- /* +- * PCMs are not registered during device probe, so don't +- * report jack here. It will be done in usermode mux +- * control select. +- */ +- if (pcm) { +- hdac_hdmi_jack_report(pcm, port, false); +- schedule_work(&port->dapm_work); ++ if (size > 0) { ++ size = min(size, ELD_MAX_SIZE); ++ if (hdac_hdmi_parse_eld(hdev, port) < 0) ++ size = -EINVAL; + } + +- mutex_unlock(&hdmi->pin_mutex); +- return; +- } ++ eld_valid = port->eld.eld_valid; + +- if (port->eld.monitor_present && port->eld.eld_valid) { +- if (pcm) { +- hdac_hdmi_jack_report(pcm, port, true); +- schedule_work(&port->dapm_work); ++ if (size > 0) { ++ port->eld.eld_valid = true; ++ port->eld.eld_size = size; ++ } else { ++ port->eld.eld_valid = false; ++ port->eld.eld_size = 0; + } + +- print_hex_dump_debug("ELD: ", DUMP_PREFIX_OFFSET, 16, 1, +- port->eld.eld_buffer, port->eld.eld_size, false); ++ eld_changed = (eld_valid != port->eld.eld_valid); + ++ pcm = hdac_hdmi_get_pcm(hdev, port); ++ ++ if (!port->eld.monitor_present || !port->eld.eld_valid) { ++ ++ dev_dbg(&hdev->dev, "%s: disconnect for pin:port %d:%d\n", ++ __func__, pin->nid, port->id); ++ ++ /* ++ * PCMs are not registered during device probe, so don't ++ * report jack here. It will be done in usermode mux ++ * control select. ++ */ ++ if (pcm) { ++ hdac_hdmi_jack_report(pcm, port, false); ++ schedule_work(&port->dapm_work); ++ } ++ ++ return; ++ } ++ ++ if (port->eld.monitor_present && port->eld.eld_valid) { ++ if (pcm) { ++ hdac_hdmi_jack_report(pcm, port, true); ++ schedule_work(&port->dapm_work); ++ } ++ ++ print_hex_dump_debug("ELD: ", DUMP_PREFIX_OFFSET, 16, 1, ++ port->eld.eld_buffer, port->eld.eld_size, false); ++ } + } +- mutex_unlock(&hdmi->pin_mutex); + + if (eld_changed && pcm) + snd_ctl_notify(hdmi->card, +@@ -1795,13 +1793,12 @@ static void hdac_hdmi_set_chmap(struct hdac_device *hdev, int pcm_idx, + if (list_empty(&pcm->port_list)) + return; + +- mutex_lock(&pcm->lock); ++ guard(mutex)(&pcm->lock); + pcm->chmap_set = true; + memcpy(pcm->chmap, chmap, ARRAY_SIZE(pcm->chmap)); + list_for_each_entry(port, &pcm->port_list, head) + if (prepared) + hdac_hdmi_setup_audio_infoframe(hdev, pcm, port); +- mutex_unlock(&pcm->lock); + } + + static bool is_hdac_hdmi_pcm_attached(struct hdac_device *hdev, int pcm_idx) +@@ -1866,8 +1863,10 @@ static int hdac_hdmi_dev_probe(struct hdac_device *hdev) + snd_hdac_ext_bus_link_get(hdev->bus, hlink); + + hdmi_priv = devm_kzalloc(&hdev->dev, sizeof(*hdmi_priv), GFP_KERNEL); +- if (hdmi_priv == NULL) ++ if (hdmi_priv == NULL) { ++ snd_hdac_ext_bus_link_put(hdev->bus, hlink); + return -ENOMEM; ++ } + + snd_hdac_register_chmap_ops(hdev, &hdmi_priv->chmap); + hdmi_priv->chmap.ops.get_chmap = hdac_hdmi_get_chmap; +@@ -1876,8 +1875,10 @@ static int hdac_hdmi_dev_probe(struct hdac_device *hdev) + hdmi_priv->chmap.ops.get_spk_alloc = hdac_hdmi_get_spk_alloc; + hdmi_priv->hdev = hdev; + +- if (!hdac_id) ++ if (!hdac_id) { ++ snd_hdac_ext_bus_link_put(hdev->bus, hlink); + return -ENODEV; ++ } + + if (hdac_id->driver_data) + hdmi_priv->drv_data = +@@ -1902,6 +1903,8 @@ static int hdac_hdmi_dev_probe(struct hdac_device *hdev) + if (ret < 0) { + dev_err(&hdev->dev, + "Failed in parse and map nid with err: %d\n", ret); ++ snd_hdac_ext_bus_link_put(hdev->bus, hlink); ++ snd_hdac_display_power(hdev->bus, hdev->addr, false); + return ret; + } + snd_hdac_refresh_widgets(hdev); +diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c +--- a/sound/soc/codecs/hdmi-codec.c ++++ b/sound/soc/codecs/hdmi-codec.c +@@ -4,6 +4,7 @@ + * Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com/ + * Author: Jyri Sarha + */ ++#include + #include + #include + #include +@@ -452,31 +453,30 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, + if (!((has_playback && tx) || (has_capture && !tx))) + return 0; + +- mutex_lock(&hcp->lock); ++ guard(mutex)(&hcp->lock); + if (hcp->busy) { + dev_err(dai->dev, "Only one simultaneous stream supported!\n"); +- mutex_unlock(&hcp->lock); + return -EINVAL; + } + + if (hcp->hcd.ops->audio_startup) { + ret = hcp->hcd.ops->audio_startup(dai->dev->parent, hcp->hcd.data); + if (ret) +- goto err; ++ return ret; + } + + if (tx && hcp->hcd.ops->get_eld) { + ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data, + hcp->eld, sizeof(hcp->eld)); + if (ret) +- goto err; ++ return ret; + + snd_parse_eld(dai->dev, &hcp->eld_parsed, + hcp->eld, sizeof(hcp->eld)); + + ret = snd_pcm_hw_constraint_eld(substream->runtime, hcp->eld); + if (ret) +- goto err; ++ return ret; + + /* Select chmap supported */ + hdmi_codec_eld_chmap(hcp); +@@ -484,8 +484,6 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, + + hcp->busy = true; + +-err: +- mutex_unlock(&hcp->lock); + return ret; + } + +@@ -503,9 +501,8 @@ static void hdmi_codec_shutdown(struct snd_pcm_substream *substream, + hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; + hcp->hcd.ops->audio_shutdown(dai->dev->parent, hcp->hcd.data); + +- mutex_lock(&hcp->lock); ++ guard(mutex)(&hcp->lock); + hcp->busy = false; +- mutex_unlock(&hcp->lock); + } + + static int hdmi_codec_fill_codec_params(struct snd_soc_dai *dai, +diff --git a/sound/soc/codecs/idt821034.c b/sound/soc/codecs/idt821034.c +--- a/sound/soc/codecs/idt821034.c ++++ b/sound/soc/codecs/idt821034.c +@@ -7,6 +7,7 @@ + // Author: Herve Codina + + #include ++#include + #include + #include + #include +@@ -413,12 +414,12 @@ static int idt821034_kctrl_gain_get(struct snd_kcontrol *kcontrol, + + ch = IDT821034_ID_GET_CHAN(mc->reg); + +- mutex_lock(&idt821034->mutex); +- if (IDT821034_ID_IS_OUT(mc->reg)) +- val = idt821034->amps.ch[ch].amp_out.gain; +- else +- val = idt821034->amps.ch[ch].amp_in.gain; +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) { ++ if (IDT821034_ID_IS_OUT(mc->reg)) ++ val = idt821034->amps.ch[ch].amp_out.gain; ++ else ++ val = idt821034->amps.ch[ch].amp_in.gain; ++ } + + ucontrol->value.integer.value[0] = val & mask; + if (invert) +@@ -456,7 +457,7 @@ static int idt821034_kctrl_gain_put(struct snd_kcontrol *kcontrol, + + ch = IDT821034_ID_GET_CHAN(mc->reg); + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + if (IDT821034_ID_IS_OUT(mc->reg)) { + amp = &idt821034->amps.ch[ch].amp_out; +@@ -466,22 +467,18 @@ static int idt821034_kctrl_gain_put(struct snd_kcontrol *kcontrol, + gain_type = IDT821034_GAIN_TX; + } + +- if (amp->gain == val) { +- ret = 0; +- goto end; +- } ++ if (amp->gain == val) ++ return 0; + + if (!amp->is_muted) { + ret = idt821034_set_gain_channel(idt821034, ch, gain_type, val); + if (ret) +- goto end; ++ return ret; + } + + amp->gain = val; +- ret = 1; /* The value changed */ +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return 1; + } + + static int idt821034_kctrl_mute_get(struct snd_kcontrol *kcontrol, +@@ -495,11 +492,11 @@ static int idt821034_kctrl_mute_get(struct snd_kcontrol *kcontrol, + + ch = IDT821034_ID_GET_CHAN(id); + +- mutex_lock(&idt821034->mutex); +- is_muted = IDT821034_ID_IS_OUT(id) ? +- idt821034->amps.ch[ch].amp_out.is_muted : +- idt821034->amps.ch[ch].amp_in.is_muted; +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) { ++ is_muted = IDT821034_ID_IS_OUT(id) ? ++ idt821034->amps.ch[ch].amp_out.is_muted : ++ idt821034->amps.ch[ch].amp_in.is_muted; ++ } + + ucontrol->value.integer.value[0] = !is_muted; + +@@ -521,7 +518,7 @@ static int idt821034_kctrl_mute_put(struct snd_kcontrol *kcontrol, + ch = IDT821034_ID_GET_CHAN(id); + is_mute = !ucontrol->value.integer.value[0]; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + if (IDT821034_ID_IS_OUT(id)) { + amp = &idt821034->amps.ch[ch].amp_out; +@@ -531,21 +528,17 @@ static int idt821034_kctrl_mute_put(struct snd_kcontrol *kcontrol, + gain_type = IDT821034_GAIN_TX; + } + +- if (amp->is_muted == is_mute) { +- ret = 0; +- goto end; +- } ++ if (amp->is_muted == is_mute) ++ return 0; + + ret = idt821034_set_gain_channel(idt821034, ch, gain_type, + is_mute ? 0 : amp->gain); + if (ret) +- goto end; ++ return ret; + + amp->is_muted = is_mute; +- ret = 1; /* The value changed */ +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return 1; + } + + static const DECLARE_TLV_DB_LINEAR(idt821034_gain_in, -300, 1300); +@@ -623,24 +616,20 @@ static int idt821034_power_event(struct snd_soc_dapm_widget *w, + struct idt821034 *idt821034 = snd_soc_component_get_drvdata(component); + unsigned int id = w->shift; + u8 power, mask; +- int ret; + u8 ch; + + ch = IDT821034_ID_GET_CHAN(id); + mask = IDT821034_ID_IS_OUT(id) ? IDT821034_CONF_PWRUP_RX : IDT821034_CONF_PWRUP_TX; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + power = idt821034_get_channel_power(idt821034, ch); + if (SND_SOC_DAPM_EVENT_ON(event)) + power |= mask; + else + power &= ~mask; +- ret = idt821034_set_channel_power(idt821034, ch, power); + +- mutex_unlock(&idt821034->mutex); +- +- return ret; ++ return idt821034_set_channel_power(idt821034, ch, power); + } + + static const struct snd_soc_dapm_widget idt821034_dapm_widgets[] = { +@@ -717,9 +706,9 @@ static int idt821034_dai_set_tdm_slot(struct snd_soc_dai *dai, + ch = 0; + while (mask && ch < IDT821034_NB_CHANNEL) { + if (mask & 0x1) { +- mutex_lock(&idt821034->mutex); +- ret = idt821034_set_channel_ts(idt821034, ch, IDT821034_CH_RX, slot); +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) ++ ret = idt821034_set_channel_ts(idt821034, ch, ++ IDT821034_CH_RX, slot); + if (ret) { + dev_err(dai->dev, "ch%u set tx tdm slot failed (%d)\n", + ch, ret); +@@ -742,9 +731,9 @@ static int idt821034_dai_set_tdm_slot(struct snd_soc_dai *dai, + ch = 0; + while (mask && ch < IDT821034_NB_CHANNEL) { + if (mask & 0x1) { +- mutex_lock(&idt821034->mutex); +- ret = idt821034_set_channel_ts(idt821034, ch, IDT821034_CH_TX, slot); +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) ++ ret = idt821034_set_channel_ts(idt821034, ch, ++ IDT821034_CH_TX, slot); + if (ret) { + dev_err(dai->dev, "ch%u set rx tdm slot failed (%d)\n", + ch, ret); +@@ -769,9 +758,8 @@ static int idt821034_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + { + struct idt821034 *idt821034 = snd_soc_component_get_drvdata(dai->component); + u8 conf; +- int ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + conf = idt821034_get_codec_conf(idt821034); + +@@ -785,13 +773,10 @@ static int idt821034_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + default: + dev_err(dai->dev, "Unsupported DAI format 0x%x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); +- ret = -EINVAL; +- goto end; ++ return -EINVAL; + } +- ret = idt821034_set_codec_conf(idt821034, conf); +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return idt821034_set_codec_conf(idt821034, conf); + } + + static int idt821034_dai_hw_params(struct snd_pcm_substream *substream, +@@ -800,9 +785,8 @@ static int idt821034_dai_hw_params(struct snd_pcm_substream *substream, + { + struct idt821034 *idt821034 = snd_soc_component_get_drvdata(dai->component); + u8 conf; +- int ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + conf = idt821034_get_codec_conf(idt821034); + +@@ -816,13 +800,10 @@ static int idt821034_dai_hw_params(struct snd_pcm_substream *substream, + default: + dev_err(dai->dev, "Unsupported PCM format 0x%x\n", + params_format(params)); +- ret = -EINVAL; +- goto end; ++ return -EINVAL; + } +- ret = idt821034_set_codec_conf(idt821034, conf); +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return idt821034_set_codec_conf(idt821034, conf); + } + + static const unsigned int idt821034_sample_bits[] = {8}; +@@ -897,11 +878,11 @@ static int idt821034_reset_audio(struct idt821034 *idt821034) + int ret; + u8 i; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + ret = idt821034_set_codec_conf(idt821034, 0); + if (ret) +- goto end; ++ return ret; + + for (i = 0; i < IDT821034_NB_CHANNEL; i++) { + idt821034->amps.ch[i].amp_out.gain = IDT821034_GAIN_OUT_INIT_RAW; +@@ -909,24 +890,21 @@ static int idt821034_reset_audio(struct idt821034 *idt821034) + ret = idt821034_set_gain_channel(idt821034, i, IDT821034_GAIN_RX, + idt821034->amps.ch[i].amp_out.gain); + if (ret) +- goto end; ++ return ret; + + idt821034->amps.ch[i].amp_in.gain = IDT821034_GAIN_IN_INIT_RAW; + idt821034->amps.ch[i].amp_in.is_muted = false; + ret = idt821034_set_gain_channel(idt821034, i, IDT821034_GAIN_TX, + idt821034->amps.ch[i].amp_in.gain); + if (ret) +- goto end; ++ return ret; + + ret = idt821034_set_channel_power(idt821034, i, 0); + if (ret) +- goto end; ++ return ret; + } + +- ret = 0; +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ return 0; + } + + static int idt821034_component_probe(struct snd_soc_component *component) +@@ -965,7 +943,7 @@ static int idt821034_chip_gpio_set(struct gpio_chip *c, unsigned int offset, + u8 slic_raw; + int ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + slic_raw = idt821034_get_written_slic_raw(idt821034, ch); + if (val) +@@ -974,8 +952,6 @@ static int idt821034_chip_gpio_set(struct gpio_chip *c, unsigned int offset, + slic_raw &= ~mask; + ret = idt821034_write_slic_raw(idt821034, ch, slic_raw); + +- mutex_unlock(&idt821034->mutex); +- + if (ret) + dev_err(&idt821034->spi->dev, "set gpio %d (%u, 0x%x) failed (%d)\n", + offset, ch, mask, ret); +@@ -991,9 +967,8 @@ static int idt821034_chip_gpio_get(struct gpio_chip *c, unsigned int offset) + u8 slic_raw; + int ret; + +- mutex_lock(&idt821034->mutex); +- ret = idt821034_read_slic_raw(idt821034, ch, &slic_raw); +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) ++ ret = idt821034_read_slic_raw(idt821034, ch, &slic_raw); + if (ret) { + dev_err(&idt821034->spi->dev, "get gpio %d (%u, 0x%x) failed (%d)\n", + offset, ch, mask, ret); +@@ -1015,9 +990,8 @@ static int idt821034_chip_get_direction(struct gpio_chip *c, unsigned int offset + struct idt821034 *idt821034 = gpiochip_get_data(c); + u8 slic_dir; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + slic_dir = idt821034_get_slic_conf(idt821034, ch); +- mutex_unlock(&idt821034->mutex); + + return slic_dir & mask ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT; + } +@@ -1034,7 +1008,7 @@ static int idt821034_chip_direction_input(struct gpio_chip *c, unsigned int offs + if (mask & ~(IDT821034_SLIC_IO1_IN | IDT821034_SLIC_IO0_IN)) + return -EPERM; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + slic_conf = idt821034_get_slic_conf(idt821034, ch) | mask; + +@@ -1044,7 +1018,6 @@ static int idt821034_chip_direction_input(struct gpio_chip *c, unsigned int offs + offset, ch, mask, ret); + } + +- mutex_unlock(&idt821034->mutex); + return ret; + } + +@@ -1060,7 +1033,7 @@ static int idt821034_chip_direction_output(struct gpio_chip *c, unsigned int off + if (ret) + return ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + slic_conf = idt821034_get_slic_conf(idt821034, ch) & ~mask; + +@@ -1070,7 +1043,6 @@ static int idt821034_chip_direction_output(struct gpio_chip *c, unsigned int off + offset, ch, mask, ret); + } + +- mutex_unlock(&idt821034->mutex); + return ret; + } + +@@ -1079,24 +1051,22 @@ static int idt821034_reset_gpio(struct idt821034 *idt821034) + int ret; + u8 i; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + /* IO0 and IO1 as input for all channels and output IO set to 0 */ + for (i = 0; i < IDT821034_NB_CHANNEL; i++) { + ret = idt821034_set_slic_conf(idt821034, i, + IDT821034_SLIC_IO1_IN | IDT821034_SLIC_IO0_IN); + if (ret) +- goto end; ++ return ret; + + ret = idt821034_write_slic_raw(idt821034, i, 0); + if (ret) +- goto end; ++ return ret; + + } +- ret = 0; +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return 0; + } + + static int idt821034_gpio_init(struct idt821034 *idt821034) +diff --git a/sound/soc/codecs/lochnagar-sc.c b/sound/soc/codecs/lochnagar-sc.c +--- a/sound/soc/codecs/lochnagar-sc.c ++++ b/sound/soc/codecs/lochnagar-sc.c +@@ -137,15 +137,23 @@ static int lochnagar_sc_set_usb_fmt(struct snd_soc_dai *dai, unsigned int fmt) + return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBP_CFP); + } + ++static const u64 lochnagar_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops lochnagar_sc_line_ops = { + .startup = lochnagar_sc_line_startup, + .shutdown = lochnagar_sc_line_shutdown, + .set_fmt = lochnagar_sc_set_line_fmt, ++ .auto_selectable_formats = &lochnagar_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_ops lochnagar_sc_usb_ops = { + .startup = lochnagar_sc_startup, + .set_fmt = lochnagar_sc_set_usb_fmt, ++ .auto_selectable_formats = &lochnagar_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver lochnagar_sc_dai[] = { +diff --git a/sound/soc/codecs/lpass-macro-common.c b/sound/soc/codecs/lpass-macro-common.c +--- a/sound/soc/codecs/lpass-macro-common.c ++++ b/sound/soc/codecs/lpass-macro-common.c +@@ -1,6 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-only + // Copyright (c) 2022, The Linux Foundation. All rights reserved. + ++#include + #include + #include + #include +@@ -71,21 +72,16 @@ EXPORT_SYMBOL_GPL(lpass_macro_pds_exit); + + void lpass_macro_set_codec_version(enum lpass_codec_version version) + { +- mutex_lock(&lpass_codec_mutex); ++ guard(mutex)(&lpass_codec_mutex); + lpass_codec_version = version; +- mutex_unlock(&lpass_codec_mutex); + } + EXPORT_SYMBOL_GPL(lpass_macro_set_codec_version); + + enum lpass_codec_version lpass_macro_get_codec_version(void) + { +- enum lpass_codec_version ver; ++ guard(mutex)(&lpass_codec_mutex); + +- mutex_lock(&lpass_codec_mutex); +- ver = lpass_codec_version; +- mutex_unlock(&lpass_codec_mutex); +- +- return ver; ++ return lpass_codec_version; + } + EXPORT_SYMBOL_GPL(lpass_macro_get_codec_version); + +diff --git a/sound/soc/codecs/lpass-rx-macro.c b/sound/soc/codecs/lpass-rx-macro.c +--- a/sound/soc/codecs/lpass-rx-macro.c ++++ b/sound/soc/codecs/lpass-rx-macro.c +@@ -6,6 +6,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2030,9 +2031,10 @@ static struct snd_soc_dai_driver rx_macro_dai[] = { + }, + }; + +-static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) ++static int rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) + { + struct regmap *regmap = rx->regmap; ++ int ret; + + if (mclk_enable) { + if (rx->rx_mclk_users == 0) { +@@ -2047,14 +2049,16 @@ static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) + CDC_RX_FS_MCLK_CNT_EN_MASK, + CDC_RX_FS_MCLK_CNT_ENABLE); + regcache_mark_dirty(regmap); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) ++ return ret; + } + rx->rx_mclk_users++; + } else { + if (rx->rx_mclk_users <= 0) { + dev_err(rx->dev, "%s: clock already disabled\n", __func__); + rx->rx_mclk_users = 0; +- return; ++ return 0; + } + rx->rx_mclk_users--; + if (rx->rx_mclk_users == 0) { +@@ -2068,6 +2072,8 @@ static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) + CDC_RX_CLK_MCLK2_EN_MASK, 0x0); + } + } ++ ++ return 0; + } + + static int rx_macro_mclk_event(struct snd_soc_dapm_widget *w, +@@ -2079,11 +2085,9 @@ static int rx_macro_mclk_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +- rx_macro_mclk_enable(rx, true); +- break; ++ return rx_macro_mclk_enable(rx, true); + case SND_SOC_DAPM_POST_PMD: +- rx_macro_mclk_enable(rx, false); +- break; ++ return rx_macro_mclk_enable(rx, false); + default: + dev_err(component->dev, "%s: invalid DAPM event %d\n", __func__, event); + ret = -EINVAL; +@@ -3671,14 +3675,16 @@ static int swclk_gate_enable(struct clk_hw *hw) + struct rx_macro *rx = to_rx_macro(hw); + int ret; + +- ret = clk_prepare_enable(rx->mclk); ++ ret = pm_runtime_resume_and_get(rx->dev); ++ if (ret < 0) ++ return ret; ++ ++ ret = rx_macro_mclk_enable(rx, true); + if (ret) { +- dev_err(rx->dev, "unable to prepare mclk\n"); ++ clk_disable_unprepare(rx->mclk); + return ret; + } + +- rx_macro_mclk_enable(rx, true); +- + regmap_update_bits(rx->regmap, CDC_RX_CLK_RST_CTRL_SWR_CONTROL, + CDC_RX_SWR_CLK_EN_MASK, 1); + +@@ -3693,7 +3699,7 @@ static void swclk_gate_disable(struct clk_hw *hw) + CDC_RX_SWR_CLK_EN_MASK, 0); + + rx_macro_mclk_enable(rx, false); +- clk_disable_unprepare(rx->mclk); ++ pm_runtime_put_autosuspend(rx->dev); + } + + static int swclk_gate_is_enabled(struct clk_hw *hw) +@@ -3864,28 +3870,31 @@ static int rx_macro_probe(struct platform_device *pdev) + rx->dev = dev; + + /* set MCLK and NPL rates */ +- clk_set_rate(rx->mclk, MCLK_FREQ); +- clk_set_rate(rx->npl, MCLK_FREQ); +- +- ret = clk_prepare_enable(rx->macro); ++ ret = clk_set_rate(rx->mclk, MCLK_FREQ); + if (ret) + return ret; + +- ret = clk_prepare_enable(rx->dcodec); ++ ret = clk_set_rate(rx->npl, MCLK_FREQ); + if (ret) +- goto err_dcodec; ++ return ret; + +- ret = clk_prepare_enable(rx->mclk); ++ ret = devm_pm_clk_create(dev); + if (ret) +- goto err_mclk; ++ return ret; + +- ret = clk_prepare_enable(rx->npl); +- if (ret) +- goto err_npl; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ return ret; + +- ret = clk_prepare_enable(rx->fsgen); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_fsgen; ++ return ret; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret) ++ return ret; + + /* reset swr block */ + regmap_update_bits(rx->regmap, CDC_RX_CLK_RST_CTRL_SWR_CONTROL, +@@ -3902,46 +3911,25 @@ static int rx_macro_probe(struct platform_device *pdev) + rx_macro_dai, + ARRAY_SIZE(rx_macro_dai)); + if (ret) +- goto err_clkout; +- +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = rx_macro_register_mclk_output(rx); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; ++ ++ ret = pm_runtime_put_autosuspend(dev); ++ if (ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", ret); + + return 0; + +-err_clkout: +- clk_disable_unprepare(rx->fsgen); +-err_fsgen: +- clk_disable_unprepare(rx->npl); +-err_npl: +- clk_disable_unprepare(rx->mclk); +-err_mclk: +- clk_disable_unprepare(rx->dcodec); +-err_dcodec: +- clk_disable_unprepare(rx->macro); ++err_rpm_put: ++ if (pm_runtime_put_sync_suspend(dev) < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind\n"); + + return ret; + } + +-static void rx_macro_remove(struct platform_device *pdev) +-{ +- struct rx_macro *rx = dev_get_drvdata(&pdev->dev); +- +- clk_disable_unprepare(rx->mclk); +- clk_disable_unprepare(rx->npl); +- clk_disable_unprepare(rx->fsgen); +- clk_disable_unprepare(rx->macro); +- clk_disable_unprepare(rx->dcodec); +-} +- + static const struct of_device_id rx_macro_dt_match[] = { + { + .compatible = "qcom,sc7280-lpass-rx-macro", +@@ -3969,13 +3957,17 @@ MODULE_DEVICE_TABLE(of, rx_macro_dt_match); + static int rx_macro_runtime_suspend(struct device *dev) + { + struct rx_macro *rx = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(rx->regmap, true); +- regcache_mark_dirty(rx->regmap); + +- clk_disable_unprepare(rx->fsgen); +- clk_disable_unprepare(rx->npl); +- clk_disable_unprepare(rx->mclk); ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(rx->regmap, false); ++ return ret; ++ } ++ ++ regcache_mark_dirty(rx->regmap); + + return 0; + } +@@ -3985,33 +3977,23 @@ static int rx_macro_runtime_resume(struct device *dev) + struct rx_macro *rx = dev_get_drvdata(dev); + int ret; + +- ret = clk_prepare_enable(rx->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(dev, "unable to prepare mclk\n"); ++ regcache_cache_only(rx->regmap, true); ++ regcache_mark_dirty(rx->regmap); + return ret; + } + +- ret = clk_prepare_enable(rx->npl); +- if (ret) { +- dev_err(dev, "unable to prepare mclkx2\n"); +- goto err_npl; +- } +- +- ret = clk_prepare_enable(rx->fsgen); +- if (ret) { +- dev_err(dev, "unable to prepare fsgen\n"); +- goto err_fsgen; +- } + regcache_cache_only(rx->regmap, false); +- regcache_sync(rx->regmap); ++ ret = regcache_sync(rx->regmap); ++ if (ret) { ++ regcache_cache_only(rx->regmap, true); ++ regcache_mark_dirty(rx->regmap); ++ pm_clk_suspend(dev); ++ return ret; ++ } + + return 0; +-err_fsgen: +- clk_disable_unprepare(rx->npl); +-err_npl: +- clk_disable_unprepare(rx->mclk); +- +- return ret; + } + + static const struct dev_pm_ops rx_macro_pm_ops = { +@@ -4026,7 +4008,6 @@ static struct platform_driver rx_macro_driver = { + .pm = pm_ptr(&rx_macro_pm_ops), + }, + .probe = rx_macro_probe, +- .remove = rx_macro_remove, + }; + + module_platform_driver(rx_macro_driver); +diff --git a/sound/soc/codecs/lpass-tx-macro.c b/sound/soc/codecs/lpass-tx-macro.c +--- a/sound/soc/codecs/lpass-tx-macro.c ++++ b/sound/soc/codecs/lpass-tx-macro.c +@@ -6,6 +6,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -614,6 +615,7 @@ static int tx_macro_mclk_enable(struct tx_macro *tx, + bool mclk_enable) + { + struct regmap *regmap = tx->regmap; ++ int ret; + + if (mclk_enable) { + if (tx->tx_mclk_users == 0) { +@@ -626,7 +628,9 @@ static int tx_macro_mclk_enable(struct tx_macro *tx, + CDC_TX_FS_CNT_EN_MASK, + CDC_TX_FS_CNT_ENABLE); + regcache_mark_dirty(regmap); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) ++ return ret; + } + tx->tx_mclk_users++; + } else { +@@ -739,11 +743,9 @@ static int tx_macro_mclk_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +- tx_macro_mclk_enable(tx, true); +- break; ++ return tx_macro_mclk_enable(tx, true); + case SND_SOC_DAPM_POST_PMD: +- tx_macro_mclk_enable(tx, false); +- break; ++ return tx_macro_mclk_enable(tx, false); + default: + break; + } +@@ -2149,17 +2151,20 @@ static int swclk_gate_enable(struct clk_hw *hw) + struct regmap *regmap = tx->regmap; + int ret; + +- ret = clk_prepare_enable(tx->mclk); ++ ret = pm_runtime_resume_and_get(tx->dev); ++ if (ret < 0) ++ return ret; ++ ++ ret = tx_macro_mclk_enable(tx, true); + if (ret) { +- dev_err(tx->dev, "failed to enable mclk\n"); ++ pm_runtime_put_autosuspend(tx->dev); + return ret; + } + +- tx_macro_mclk_enable(tx, true); +- + regmap_update_bits(regmap, CDC_TX_CLK_RST_CTRL_SWR_CONTROL, + CDC_TX_SWR_CLK_EN_MASK, + CDC_TX_SWR_CLK_ENABLE); ++ + return 0; + } + +@@ -2172,7 +2177,7 @@ static void swclk_gate_disable(struct clk_hw *hw) + CDC_TX_SWR_CLK_EN_MASK, 0x0); + + tx_macro_mclk_enable(tx, false); +- clk_disable_unprepare(tx->mclk); ++ pm_runtime_put_autosuspend(tx->dev); + } + + static int swclk_gate_is_enabled(struct clk_hw *hw) +@@ -2315,28 +2320,31 @@ static int tx_macro_probe(struct platform_device *pdev) + tx->active_decimator[TX_MACRO_AIF3_CAP] = -1; + + /* set MCLK and NPL rates */ +- clk_set_rate(tx->mclk, MCLK_FREQ); +- clk_set_rate(tx->npl, MCLK_FREQ); +- +- ret = clk_prepare_enable(tx->macro); ++ ret = clk_set_rate(tx->mclk, MCLK_FREQ); + if (ret) + goto err; + +- ret = clk_prepare_enable(tx->dcodec); ++ ret = clk_set_rate(tx->npl, MCLK_FREQ); + if (ret) +- goto err_dcodec; ++ goto err; + +- ret = clk_prepare_enable(tx->mclk); ++ ret = devm_pm_clk_create(dev); + if (ret) +- goto err_mclk; ++ goto err; + +- ret = clk_prepare_enable(tx->npl); +- if (ret) +- goto err_npl; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ goto err; + +- ret = clk_prepare_enable(tx->fsgen); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_fsgen; ++ goto err; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) ++ goto err; + + + /* reset soundwire block */ +@@ -2356,30 +2364,21 @@ static int tx_macro_probe(struct platform_device *pdev) + tx_macro_dai, + ARRAY_SIZE(tx_macro_dai)); + if (ret) +- goto err_clkout; +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = tx_macro_register_mclk_output(tx); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; ++ ++ ret = pm_runtime_put_autosuspend(dev); ++ if (ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", ret); + + return 0; + +-err_clkout: +- clk_disable_unprepare(tx->fsgen); +-err_fsgen: +- clk_disable_unprepare(tx->npl); +-err_npl: +- clk_disable_unprepare(tx->mclk); +-err_mclk: +- clk_disable_unprepare(tx->dcodec); +-err_dcodec: +- clk_disable_unprepare(tx->macro); ++err_rpm_put: ++ if (pm_runtime_put_sync_suspend(dev) < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind\n"); + err: + lpass_macro_pds_exit(tx->pds); + +@@ -2390,25 +2389,23 @@ static void tx_macro_remove(struct platform_device *pdev) + { + struct tx_macro *tx = dev_get_drvdata(&pdev->dev); + +- clk_disable_unprepare(tx->macro); +- clk_disable_unprepare(tx->dcodec); +- clk_disable_unprepare(tx->mclk); +- clk_disable_unprepare(tx->npl); +- clk_disable_unprepare(tx->fsgen); +- + lpass_macro_pds_exit(tx->pds); + } + + static int tx_macro_runtime_suspend(struct device *dev) + { + struct tx_macro *tx = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(tx->regmap, true); +- regcache_mark_dirty(tx->regmap); + +- clk_disable_unprepare(tx->fsgen); +- clk_disable_unprepare(tx->npl); +- clk_disable_unprepare(tx->mclk); ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(tx->regmap, false); ++ return ret; ++ } ++ ++ regcache_mark_dirty(tx->regmap); + + return 0; + } +@@ -2418,34 +2415,23 @@ static int tx_macro_runtime_resume(struct device *dev) + struct tx_macro *tx = dev_get_drvdata(dev); + int ret; + +- ret = clk_prepare_enable(tx->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(dev, "unable to prepare mclk\n"); ++ regcache_cache_only(tx->regmap, true); ++ regcache_mark_dirty(tx->regmap); + return ret; + } + +- ret = clk_prepare_enable(tx->npl); +- if (ret) { +- dev_err(dev, "unable to prepare npl\n"); +- goto err_npl; +- } +- +- ret = clk_prepare_enable(tx->fsgen); +- if (ret) { +- dev_err(dev, "unable to prepare fsgen\n"); +- goto err_fsgen; +- } +- + regcache_cache_only(tx->regmap, false); +- regcache_sync(tx->regmap); ++ ret = regcache_sync(tx->regmap); ++ if (ret) { ++ regcache_cache_only(tx->regmap, true); ++ regcache_mark_dirty(tx->regmap); ++ pm_clk_suspend(dev); ++ return ret; ++ } + + return 0; +-err_fsgen: +- clk_disable_unprepare(tx->npl); +-err_npl: +- clk_disable_unprepare(tx->mclk); +- +- return ret; + } + + static const struct dev_pm_ops tx_macro_pm_ops = { +diff --git a/sound/soc/codecs/lpass-va-macro.c b/sound/soc/codecs/lpass-va-macro.c +--- a/sound/soc/codecs/lpass-va-macro.c ++++ b/sound/soc/codecs/lpass-va-macro.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1351,34 +1352,43 @@ static int fsgen_gate_enable(struct clk_hw *hw) + { + struct va_macro *va = to_va_macro(hw); + struct regmap *regmap = va->regmap; +- int ret; ++ int ret, rpm_ret; + +- if (va->has_swr_master) { +- ret = clk_prepare_enable(va->mclk); +- if (ret) +- return ret; +- } ++ ret = pm_runtime_resume_and_get(va->dev); ++ if (ret < 0) ++ return ret; + + ret = va_macro_mclk_enable(va, true); ++ if (ret) { ++ rpm_ret = pm_runtime_put_autosuspend(va->dev); ++ if (rpm_ret < 0) ++ dev_warn(va->dev, ++ "runtime PM put failed in fsgen enable unwind: %d\n", ++ rpm_ret); ++ return ret; ++ } + if (va->has_swr_master) + regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_SWR_CONTROL, + CDC_VA_SWR_CLK_EN_MASK, CDC_VA_SWR_CLK_ENABLE); + +- return ret; ++ return 0; + } + + static void fsgen_gate_disable(struct clk_hw *hw) + { + struct va_macro *va = to_va_macro(hw); + struct regmap *regmap = va->regmap; ++ int ret; + + if (va->has_swr_master) + regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_SWR_CONTROL, + CDC_VA_SWR_CLK_EN_MASK, 0x0); + + va_macro_mclk_enable(va, false); +- if (va->has_swr_master) +- clk_disable_unprepare(va->mclk); ++ ++ ret = pm_runtime_put_autosuspend(va->dev); ++ if (ret < 0) ++ dev_warn(va->dev, "runtime PM put failed in fsgen disable: %d\n", ret); + } + + static int fsgen_gate_is_enabled(struct clk_hw *hw) +@@ -1539,6 +1549,7 @@ static int va_macro_probe(struct platform_device *pdev) + void __iomem *base; + u32 sample_rate = 0; + int ret; ++ int rpm_ret; + + va = devm_kzalloc(dev, sizeof(*va), GFP_KERNEL); + if (!va) +@@ -1594,7 +1605,9 @@ static int va_macro_probe(struct platform_device *pdev) + va->has_npl_clk = data->has_npl_clk; + + /* mclk rate */ +- clk_set_rate(va->mclk, 2 * VA_MACRO_MCLK_FREQ); ++ ret = clk_set_rate(va->mclk, 2 * VA_MACRO_MCLK_FREQ); ++ if (ret) ++ goto err; + + if (va->has_npl_clk) { + va->npl = devm_clk_get(dev, "npl"); +@@ -1603,25 +1616,29 @@ static int va_macro_probe(struct platform_device *pdev) + goto err; + } + +- clk_set_rate(va->npl, 2 * VA_MACRO_MCLK_FREQ); ++ ret = clk_set_rate(va->npl, 2 * VA_MACRO_MCLK_FREQ); ++ if (ret) ++ goto err; + } + +- ret = clk_prepare_enable(va->macro); ++ ret = devm_pm_clk_create(dev); + if (ret) + goto err; + +- ret = clk_prepare_enable(va->dcodec); +- if (ret) +- goto err_dcodec; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ goto err; + +- ret = clk_prepare_enable(va->mclk); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_mclk; ++ goto err; + +- if (va->has_npl_clk) { +- ret = clk_prepare_enable(va->npl); +- if (ret) +- goto err_npl; ++ rpm_ret = pm_runtime_resume_and_get(dev); ++ if (rpm_ret < 0) { ++ ret = rpm_ret; ++ goto err; + } + + /** +@@ -1634,7 +1651,7 @@ static int va_macro_probe(struct platform_device *pdev) + /* read version from register */ + ret = va_macro_set_lpass_codec_version(va); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; + } + + if (va->has_swr_master) { +@@ -1664,35 +1681,28 @@ static int va_macro_probe(struct platform_device *pdev) + va_macro_dais, + ARRAY_SIZE(va_macro_dais)); + if (ret) +- goto err_clkout; +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = va_macro_register_fsgen_output(va); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; + + va->fsgen = devm_clk_hw_get_clk(dev, &va->hw, "fsgen"); + if (IS_ERR(va->fsgen)) { + ret = PTR_ERR(va->fsgen); +- goto err_clkout; ++ goto err_rpm_put; + } + ++ rpm_ret = pm_runtime_put_autosuspend(dev); ++ if (rpm_ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", rpm_ret); ++ + return 0; + +-err_clkout: +- if (va->has_npl_clk) +- clk_disable_unprepare(va->npl); +-err_npl: +- clk_disable_unprepare(va->mclk); +-err_mclk: +- clk_disable_unprepare(va->dcodec); +-err_dcodec: +- clk_disable_unprepare(va->macro); ++err_rpm_put: ++ rpm_ret = pm_runtime_put_sync_suspend(dev); ++ if (rpm_ret < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind: %d\n", rpm_ret); + err: + lpass_macro_pds_exit(va->pds); + +@@ -1703,53 +1713,52 @@ static void va_macro_remove(struct platform_device *pdev) + { + struct va_macro *va = dev_get_drvdata(&pdev->dev); + +- if (va->has_npl_clk) +- clk_disable_unprepare(va->npl); +- +- clk_disable_unprepare(va->mclk); +- clk_disable_unprepare(va->dcodec); +- clk_disable_unprepare(va->macro); +- + lpass_macro_pds_exit(va->pds); + } + + static int va_macro_runtime_suspend(struct device *dev) + { + struct va_macro *va = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(va->regmap, true); ++ ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(va->regmap, false); ++ return ret; ++ } ++ + regcache_mark_dirty(va->regmap); + +- if (va->has_npl_clk) +- clk_disable_unprepare(va->npl); +- +- clk_disable_unprepare(va->mclk); +- + return 0; + } + + static int va_macro_runtime_resume(struct device *dev) + { + struct va_macro *va = dev_get_drvdata(dev); +- int ret; ++ int ret, sret; + +- ret = clk_prepare_enable(va->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(va->dev, "unable to prepare mclk\n"); ++ regcache_cache_only(va->regmap, true); ++ regcache_mark_dirty(va->regmap); + return ret; + } + +- if (va->has_npl_clk) { +- ret = clk_prepare_enable(va->npl); +- if (ret) { +- clk_disable_unprepare(va->mclk); +- dev_err(va->dev, "unable to prepare npl\n"); +- return ret; +- } +- } +- + regcache_cache_only(va->regmap, false); +- regcache_sync(va->regmap); ++ ++ ret = regcache_sync(va->regmap); ++ if (ret) { ++ regcache_cache_only(va->regmap, true); ++ regcache_mark_dirty(va->regmap); ++ sret = pm_clk_suspend(dev); ++ if (sret) ++ dev_err(va->dev, ++ "failed to suspend clocks after regcache sync failure: %d\n", ++ sret); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c +--- a/sound/soc/codecs/lpass-wsa-macro.c ++++ b/sound/soc/codecs/lpass-wsa-macro.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2529,15 +2530,13 @@ static const struct snd_soc_dapm_route wsa_audio_map[] = { + static int wsa_swrm_clock(struct wsa_macro *wsa, bool enable) + { + struct regmap *regmap = wsa->regmap; ++ int ret; + + if (enable) { +- int ret; +- +- ret = clk_prepare_enable(wsa->mclk); +- if (ret) { +- dev_err(wsa->dev, "failed to enable mclk\n"); ++ ret = pm_runtime_resume_and_get(wsa->dev); ++ if (ret < 0) + return ret; +- } ++ + wsa_macro_mclk_enable(wsa, true); + + regmap_update_bits(regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, +@@ -2548,7 +2547,10 @@ static int wsa_swrm_clock(struct wsa_macro *wsa, bool enable) + regmap_update_bits(regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, + CDC_WSA_SWR_CLK_EN_MASK, 0); + wsa_macro_mclk_enable(wsa, false); +- clk_disable_unprepare(wsa->mclk); ++ ++ ret = pm_runtime_put_autosuspend(wsa->dev); ++ if (ret < 0) ++ dev_warn(wsa->dev, "runtime PM put failed: %d\n", ret); + } + + return 0; +@@ -2656,7 +2658,7 @@ static int wsa_macro_register_mclk_output(struct wsa_macro *wsa) + init.num_parents = 1; + wsa->hw.init = &init; + hw = &wsa->hw; +- ret = clk_hw_register(wsa->dev, hw); ++ ret = devm_clk_hw_register(wsa->dev, hw); + if (ret) + return ret; + +@@ -2771,28 +2773,31 @@ static int wsa_macro_probe(struct platform_device *pdev) + wsa->dev = dev; + + /* set MCLK and NPL rates */ +- clk_set_rate(wsa->mclk, WSA_MACRO_MCLK_FREQ); +- clk_set_rate(wsa->npl, WSA_MACRO_MCLK_FREQ); +- +- ret = clk_prepare_enable(wsa->macro); ++ ret = clk_set_rate(wsa->mclk, WSA_MACRO_MCLK_FREQ); + if (ret) +- goto err; ++ return ret; + +- ret = clk_prepare_enable(wsa->dcodec); ++ ret = clk_set_rate(wsa->npl, WSA_MACRO_MCLK_FREQ); + if (ret) +- goto err_dcodec; ++ return ret; + +- ret = clk_prepare_enable(wsa->mclk); ++ ret = devm_pm_clk_create(dev); + if (ret) +- goto err_mclk; ++ return ret; + +- ret = clk_prepare_enable(wsa->npl); +- if (ret) +- goto err_npl; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ return ret; + +- ret = clk_prepare_enable(wsa->fsgen); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_fsgen; ++ return ret; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) ++ return ret; + + /* reset swr ip */ + regmap_update_bits(wsa->regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, +@@ -2809,56 +2814,37 @@ static int wsa_macro_probe(struct platform_device *pdev) + wsa_macro_dai, + ARRAY_SIZE(wsa_macro_dai)); + if (ret) +- goto err_clkout; +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = wsa_macro_register_mclk_output(wsa); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; ++ ++ ret = pm_runtime_put_autosuspend(dev); ++ if (ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", ret); + + return 0; +- +-err_clkout: +- clk_disable_unprepare(wsa->fsgen); +-err_fsgen: +- clk_disable_unprepare(wsa->npl); +-err_npl: +- clk_disable_unprepare(wsa->mclk); +-err_mclk: +- clk_disable_unprepare(wsa->dcodec); +-err_dcodec: +- clk_disable_unprepare(wsa->macro); +-err: ++err_rpm_put: ++ if (pm_runtime_put_sync_suspend(dev) < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind\n"); + return ret; +- +-} +- +-static void wsa_macro_remove(struct platform_device *pdev) +-{ +- struct wsa_macro *wsa = dev_get_drvdata(&pdev->dev); +- +- clk_disable_unprepare(wsa->macro); +- clk_disable_unprepare(wsa->dcodec); +- clk_disable_unprepare(wsa->mclk); +- clk_disable_unprepare(wsa->npl); +- clk_disable_unprepare(wsa->fsgen); + } + + static int wsa_macro_runtime_suspend(struct device *dev) + { + struct wsa_macro *wsa = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(wsa->regmap, true); +- regcache_mark_dirty(wsa->regmap); + +- clk_disable_unprepare(wsa->fsgen); +- clk_disable_unprepare(wsa->npl); +- clk_disable_unprepare(wsa->mclk); ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(wsa->regmap, false); ++ return ret; ++ } ++ ++ regcache_mark_dirty(wsa->regmap); + + return 0; + } +@@ -2866,36 +2852,29 @@ static int wsa_macro_runtime_suspend(struct device *dev) + static int wsa_macro_runtime_resume(struct device *dev) + { + struct wsa_macro *wsa = dev_get_drvdata(dev); +- int ret; ++ int ret, sret; + +- ret = clk_prepare_enable(wsa->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(dev, "unable to prepare mclk\n"); ++ regcache_cache_only(wsa->regmap, true); ++ regcache_mark_dirty(wsa->regmap); ++ return ret; ++ } ++ regcache_cache_only(wsa->regmap, false); ++ ++ ret = regcache_sync(wsa->regmap); ++ if (ret) { ++ regcache_cache_only(wsa->regmap, true); ++ regcache_mark_dirty(wsa->regmap); ++ sret = pm_clk_suspend(dev); ++ if (sret) ++ dev_err(dev, ++ "failed to suspend clocks after regcache sync failure: %d\n", ++ sret); + return ret; + } + +- ret = clk_prepare_enable(wsa->npl); +- if (ret) { +- dev_err(dev, "unable to prepare mclkx2\n"); +- goto err_npl; +- } +- +- ret = clk_prepare_enable(wsa->fsgen); +- if (ret) { +- dev_err(dev, "unable to prepare fsgen\n"); +- goto err_fsgen; +- } +- +- regcache_cache_only(wsa->regmap, false); +- regcache_sync(wsa->regmap); +- + return 0; +-err_fsgen: +- clk_disable_unprepare(wsa->npl); +-err_npl: +- clk_disable_unprepare(wsa->mclk); +- +- return ret; + } + + static const struct dev_pm_ops wsa_macro_pm_ops = { +@@ -2929,7 +2908,6 @@ static struct platform_driver wsa_macro_driver = { + .pm = pm_ptr(&wsa_macro_pm_ops), + }, + .probe = wsa_macro_probe, +- .remove = wsa_macro_remove, + }; + + module_platform_driver(wsa_macro_driver); +diff --git a/sound/soc/codecs/madera.c b/sound/soc/codecs/madera.c +--- a/sound/soc/codecs/madera.c ++++ b/sound/soc/codecs/madera.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -513,7 +514,7 @@ int madera_domain_clk_ev(struct snd_soc_dapm_widget *w, + * We can't rely on the DAPM mutex for locking because we need a lock + * that can safely be called in hw_params + */ +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +@@ -532,8 +533,6 @@ int madera_domain_clk_ev(struct snd_soc_dapm_widget *w, + + madera_debug_dump_domain_groups(priv); + +- mutex_unlock(&priv->rate_lock); +- + return 0; + } + EXPORT_SYMBOL_GPL(madera_domain_clk_ev); +@@ -875,9 +874,8 @@ static int madera_adsp_rate_get(struct snd_kcontrol *kcontrol, + const int adsp_num = e->shift_l; + int item; + +- mutex_lock(&priv->rate_lock); +- cached_rate = priv->adsp_rate_cache[adsp_num]; +- mutex_unlock(&priv->rate_lock); ++ scoped_guard(mutex, &priv->rate_lock) ++ cached_rate = priv->adsp_rate_cache[adsp_num]; + + item = snd_soc_enum_val_to_item(e, cached_rate); + ucontrol->value.enumerated.item[0] = item; +@@ -893,7 +891,6 @@ static int madera_adsp_rate_put(struct snd_kcontrol *kcontrol, + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + const int adsp_num = e->shift_l; + const unsigned int item = ucontrol->value.enumerated.item[0]; +- int ret = 0; + + if (item >= e->items) + return -EINVAL; +@@ -903,22 +900,20 @@ static int madera_adsp_rate_put(struct snd_kcontrol *kcontrol, + * maintain consistent behaviour that rate domains cannot be changed + * while in use since this is a hardware requirement + */ +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + if (!madera_can_change_grp_rate(priv, priv->adsp[adsp_num].cs_dsp.base)) { + dev_warn(priv->madera->dev, + "Cannot change '%s' while in use by active audio paths\n", + kcontrol->id.name); +- ret = -EBUSY; ++ return -EBUSY; + } else if (priv->adsp_rate_cache[adsp_num] != e->values[item]) { + /* Volatile register so defer until the codec is powered up */ + priv->adsp_rate_cache[adsp_num] = e->values[item]; +- ret = 1; ++ return 1; + } + +- mutex_unlock(&priv->rate_lock); +- +- return ret; ++ return 0; + } + + static const struct soc_enum madera_adsp_rate_enum[] = { +@@ -1061,15 +1056,13 @@ int madera_rate_put(struct snd_kcontrol *kcontrol, + * Prevent the domain powering up while we're checking whether it's + * safe to change rate domain + */ +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + val = snd_soc_component_read(component, e->reg); + val >>= e->shift_l; + val &= e->mask; +- if (snd_soc_enum_item_to_val(e, item) == val) { +- ret = 0; +- goto out; +- } ++ if (snd_soc_enum_item_to_val(e, item) == val) ++ return 0; + + if (!madera_can_change_grp_rate(priv, e->reg)) { + dev_warn(priv->madera->dev, +@@ -1082,8 +1075,6 @@ int madera_rate_put(struct snd_kcontrol *kcontrol, + ret = snd_soc_put_enum_double(kcontrol, ucontrol); + madera_spin_sysclk(priv); + } +-out: +- mutex_unlock(&priv->rate_lock); + + return ret; + } +@@ -3041,12 +3032,11 @@ static int madera_hw_params_rate(struct snd_pcm_substream *substream, + if ((cur & MADERA_AIF1_RATE_MASK) == (tar & MADERA_AIF1_RATE_MASK)) + return 0; + +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + if (!madera_can_change_grp_rate(priv, base + MADERA_AIF_RATE_CTRL)) { + madera_aif_warn(dai, "Cannot change rate while active\n"); +- ret = -EBUSY; +- goto out; ++ return -EBUSY; + } + + /* Guard the rate change with SYSCLK cycles */ +@@ -3055,9 +3045,6 @@ static int madera_hw_params_rate(struct snd_pcm_substream *substream, + MADERA_AIF1_RATE_MASK, tar); + madera_spin_sysclk(priv); + +-out: +- mutex_unlock(&priv->rate_lock); +- + return ret; + } + +@@ -3345,6 +3332,16 @@ static int madera_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + return 0; + } + ++static const u64 madera_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + const struct snd_soc_dai_ops madera_dai_ops = { + .startup = &madera_startup, + .set_fmt = &madera_set_fmt, +@@ -3352,6 +3349,8 @@ const struct snd_soc_dai_ops madera_dai_ops = { + .hw_params = &madera_hw_params, + .set_sysclk = &madera_dai_set_sysclk, + .set_tristate = &madera_set_tristate, ++ .auto_selectable_formats = &madera_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + EXPORT_SYMBOL_GPL(madera_dai_ops); + +diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c +--- a/sound/soc/codecs/max98090.c ++++ b/sound/soc/codecs/max98090.c +@@ -2341,6 +2341,7 @@ static irqreturn_t max98090_interrupt(int irq, void *data) + * + * @component: MAX98090 component + * @jack: jack to report detection events on ++ * @data: can be used if codec driver need extra data for configuring jack + * + * Enable microphone detection via IRQ on the MAX98090. If GPIOs are + * being used to bring out signals to the processor then only platform +@@ -2646,12 +2647,18 @@ static void max98090_i2c_remove(struct i2c_client *client) + static int max98090_runtime_resume(struct device *dev) + { + struct max98090_priv *max98090 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(max98090->regmap, false); + + max98090_reset(max98090); + +- regcache_sync(max98090->regmap); ++ ret = regcache_sync(max98090->regmap); ++ if (ret < 0) { ++ regcache_cache_only(max98090->regmap, true); ++ regcache_mark_dirty(max98090->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c +--- a/sound/soc/codecs/max98095.c ++++ b/sound/soc/codecs/max98095.c +@@ -5,6 +5,7 @@ + * Copyright 2011 Maxim Integrated Products + */ + ++#include + #include + #include + #include +@@ -1532,15 +1533,17 @@ static int max98095_put_eq_enum(struct snd_kcontrol *kcontrol, + regsave = snd_soc_component_read(component, M98095_088_CFG_LEVEL); + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, 0); + +- mutex_lock(&max98095->lock); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG); +- m98095_eq_band(component, channel, 0, coef_set->band1); +- m98095_eq_band(component, channel, 1, coef_set->band2); +- m98095_eq_band(component, channel, 2, coef_set->band3); +- m98095_eq_band(component, channel, 3, coef_set->band4); +- m98095_eq_band(component, channel, 4, coef_set->band5); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, 0); +- mutex_unlock(&max98095->lock); ++ scoped_guard(mutex, &max98095->lock) { ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, M98095_SEG); ++ m98095_eq_band(component, channel, 0, coef_set->band1); ++ m98095_eq_band(component, channel, 1, coef_set->band2); ++ m98095_eq_band(component, channel, 2, coef_set->band3); ++ m98095_eq_band(component, channel, 3, coef_set->band4); ++ m98095_eq_band(component, channel, 4, coef_set->band5); ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, 0); ++ } + + /* Restore the original on/off state */ + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, regsave); +@@ -1683,12 +1686,14 @@ static int max98095_put_bq_enum(struct snd_kcontrol *kcontrol, + regsave = snd_soc_component_read(component, M98095_088_CFG_LEVEL); + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, 0); + +- mutex_lock(&max98095->lock); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG); +- m98095_biquad_band(component, channel, 0, coef_set->band1); +- m98095_biquad_band(component, channel, 1, coef_set->band2); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, 0); +- mutex_unlock(&max98095->lock); ++ scoped_guard(mutex, &max98095->lock) { ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, M98095_SEG); ++ m98095_biquad_band(component, channel, 0, coef_set->band1); ++ m98095_biquad_band(component, channel, 1, coef_set->band2); ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, 0); ++ } + + /* Restore the original on/off state */ + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, regsave); +diff --git a/sound/soc/codecs/max98363.c b/sound/soc/codecs/max98363.c +--- a/sound/soc/codecs/max98363.c ++++ b/sound/soc/codecs/max98363.c +@@ -100,7 +100,12 @@ static int max98363_resume(struct device *dev) + return ret; + + regcache_cache_only(max98363->regmap, false); +- regcache_sync(max98363->regmap); ++ ret = regcache_sync(max98363->regmap); ++ if (ret) { ++ regcache_cache_only(max98363->regmap, true); ++ regcache_mark_dirty(max98363->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98373-i2c.c b/sound/soc/codecs/max98373-i2c.c +--- a/sound/soc/codecs/max98373-i2c.c ++++ b/sound/soc/codecs/max98373-i2c.c +@@ -488,10 +488,17 @@ static int max98373_suspend(struct device *dev) + static int max98373_resume(struct device *dev) + { + struct max98373_priv *max98373 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(max98373->regmap, false); + max98373_reset(max98373, dev); +- regcache_sync(max98373->regmap); ++ ret = regcache_sync(max98373->regmap); ++ if (ret) { ++ regcache_cache_only(max98373->regmap, true); ++ regcache_mark_dirty(max98373->regmap); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/max98373-sdw.c b/sound/soc/codecs/max98373-sdw.c +--- a/sound/soc/codecs/max98373-sdw.c ++++ b/sound/soc/codecs/max98373-sdw.c +@@ -277,7 +277,12 @@ static int max98373_resume(struct device *dev) + } + + regcache_cache_only(max98373->regmap, false); +- regcache_sync(max98373->regmap); ++ ret = regcache_sync(max98373->regmap); ++ if (ret) { ++ regcache_cache_only(max98373->regmap, true); ++ regcache_mark_dirty(max98373->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98388.c b/sound/soc/codecs/max98388.c +--- a/sound/soc/codecs/max98388.c ++++ b/sound/soc/codecs/max98388.c +@@ -863,10 +863,16 @@ static int max98388_suspend(struct device *dev) + static int max98388_resume(struct device *dev) + { + struct max98388_priv *max98388 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(max98388->regmap, false); + max98388_reset(max98388, dev); +- regcache_sync(max98388->regmap); ++ ret = regcache_sync(max98388->regmap); ++ if (ret) { ++ regcache_cache_only(max98388->regmap, true); ++ regcache_mark_dirty(max98388->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98390.c b/sound/soc/codecs/max98390.c +--- a/sound/soc/codecs/max98390.c ++++ b/sound/soc/codecs/max98390.c +@@ -788,7 +788,6 @@ static int max98390_dsm_init(struct snd_soc_component *component) + const char *vendor, *product; + struct max98390_priv *max98390 = + snd_soc_component_get_drvdata(component); +- const struct firmware *fw; + char *dsm_param; + + vendor = dmi_get_system_info(DMI_SYS_VENDOR); +@@ -805,6 +804,8 @@ static int max98390_dsm_init(struct snd_soc_component *component) + snprintf(filename, sizeof(filename), "%s", + max98390->dsm_param_name); + } ++ ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, filename, component->dev); + if (ret) { + ret = request_firmware(&fw, "dsm_param.bin", component->dev); +@@ -812,7 +813,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + ret = request_firmware(&fw, "dsmparam.bin", + component->dev); + if (ret) +- goto err; ++ return ret; + } + } + +@@ -822,8 +823,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + if (fw->size < MAX98390_DSM_PARAM_MIN_SIZE) { + dev_err(component->dev, + "param fw is invalid.\n"); +- ret = -EINVAL; +- goto err_alloc; ++ return -EINVAL; + } + dsm_param = (char *)fw->data; + param_start_addr = (dsm_param[0] & 0xff) | (dsm_param[1] & 0xff) << 8; +@@ -833,8 +833,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + fw->size < param_size + MAX98390_DSM_PAYLOAD_OFFSET) { + dev_err(component->dev, + "param fw is invalid.\n"); +- ret = -EINVAL; +- goto err_alloc; ++ return -EINVAL; + } + regmap_write(max98390->regmap, MAX98390_R203A_AMP_EN, 0x80); + dsm_param += MAX98390_DSM_PAYLOAD_OFFSET; +@@ -842,10 +841,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + dsm_param, param_size); + regmap_write(max98390->regmap, MAX98390_R23E1_DSP_GLOBAL_EN, 0x01); + +-err_alloc: +- release_firmware(fw); +-err: +- return ret; ++ return 0; + } + + static void max98390_init_regs(struct snd_soc_component *component) +@@ -952,11 +948,17 @@ static int max98390_suspend(struct device *dev) + static int max98390_resume(struct device *dev) + { + struct max98390_priv *max98390 = dev_get_drvdata(dev); ++ int ret; + + dev_dbg(dev, "%s:Enter\n", __func__); + + regcache_cache_only(max98390->regmap, false); +- regcache_sync(max98390->regmap); ++ ret = regcache_sync(max98390->regmap); ++ if (ret) { ++ regcache_cache_only(max98390->regmap, true); ++ regcache_mark_dirty(max98390->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98396.c b/sound/soc/codecs/max98396.c +--- a/sound/soc/codecs/max98396.c ++++ b/sound/soc/codecs/max98396.c +@@ -1600,19 +1600,37 @@ static int max98396_resume(struct device *dev) + if (max98396->pvdd) { + ret = regulator_enable(max98396->pvdd); + if (ret < 0) +- return ret; ++ goto err_core_supplies; + } + + if (max98396->vbat) { + ret = regulator_enable(max98396->vbat); + if (ret < 0) +- return ret; ++ goto err_pvdd; + } + + regcache_cache_only(max98396->regmap, false); + max98396_reset(max98396, dev); +- regcache_sync(max98396->regmap); ++ ret = regcache_sync(max98396->regmap); ++ if (ret < 0) { ++ regcache_cache_only(max98396->regmap, true); ++ regcache_mark_dirty(max98396->regmap); ++ goto err_vbat; ++ } ++ + return 0; ++ ++err_vbat: ++ if (max98396->vbat) ++ regulator_disable(max98396->vbat); ++err_pvdd: ++ if (max98396->pvdd) ++ regulator_disable(max98396->pvdd); ++err_core_supplies: ++ regulator_bulk_disable(MAX98396_NUM_CORE_SUPPLIES, ++ max98396->core_supplies); ++ ++ return ret; + } + + static const struct dev_pm_ops max98396_pm = { +diff --git a/sound/soc/codecs/max98927.c b/sound/soc/codecs/max98927.c +--- a/sound/soc/codecs/max98927.c ++++ b/sound/soc/codecs/max98927.c +@@ -742,11 +742,17 @@ static int max98927_suspend(struct device *dev) + static int max98927_resume(struct device *dev) + { + struct max98927_priv *max98927 = dev_get_drvdata(dev); ++ int ret; + + regmap_write(max98927->regmap, MAX98927_R0100_SOFT_RESET, + MAX98927_SOFT_RESET); + regcache_cache_only(max98927->regmap, false); +- regcache_sync(max98927->regmap); ++ ret = regcache_sync(max98927->regmap); ++ if (ret) { ++ regcache_cache_only(max98927->regmap, true); ++ regcache_mark_dirty(max98927->regmap); ++ return ret; ++ } + return 0; + } + +diff --git a/sound/soc/codecs/msm8916-wcd-analog.c b/sound/soc/codecs/msm8916-wcd-analog.c +--- a/sound/soc/codecs/msm8916-wcd-analog.c ++++ b/sound/soc/codecs/msm8916-wcd-analog.c +@@ -217,9 +217,11 @@ + #define CDC_A_RX_HPH_BIAS_LDO_OCP (0xf195) + #define CDC_A_RX_HPH_BIAS_CNP (0xf196) + #define CDC_A_RX_HPH_CNP_EN (0xf197) ++#define CDC_A_RX_HPH_L_TEST (0xf19A) + #define CDC_A_RX_HPH_L_PA_DAC_CTL (0xf19B) + #define RX_HPA_L_PA_DAC_CTL_DATA_RESET_MASK BIT(1) + #define RX_HPA_L_PA_DAC_CTL_DATA_RESET_RESET BIT(1) ++#define CDC_A_RX_HPH_R_TEST (0xf19C) + #define CDC_A_RX_HPH_R_PA_DAC_CTL (0xf19D) + #define RX_HPH_R_PA_DAC_CTL_DATA_RESET BIT(1) + #define RX_HPH_R_PA_DAC_CTL_DATA_RESET_MASK BIT(1) +@@ -232,6 +234,8 @@ + #define RX_EAR_CTL_PA_SEL_MASK BIT(7) + #define RX_EAR_CTL_PA_SEL BIT(7) + ++#define CDC_A_RX_EAR_STATUS (0xf1A1) ++ + #define CDC_A_SPKR_DAC_CTL (0xf1B0) + #define SPKR_DAC_CTL_DAC_RESET_MASK BIT(4) + #define SPKR_DAC_CTL_DAC_RESET_NORMAL 0 +@@ -250,6 +254,7 @@ + SPKR_DRV_CAL_EN | SPKR_DRV_SETTLE_EN | \ + SPKR_DRV_FW_EN | SPKR_DRV_BOOST_SET | \ + SPKR_DRV_CMFB_SET | SPKR_DRV_GAIN_SET) ++#define CDC_A_SPKR_ANA_BIAS_SET (0xf1B3) + #define CDC_A_SPKR_OCP_CTL (0xf1B4) + #define CDC_A_SPKR_PWRSTG_CTL (0xf1B5) + #define SPKR_PWRSTG_CTL_DAC_EN_MASK BIT(0) +@@ -264,6 +269,7 @@ + + #define CDC_A_SPKR_DRV_DBG (0xf1B7) + #define CDC_A_CURRENT_LIMIT (0xf1C0) ++#define CDC_A_BYPASS_MODE (0xf1C2) + #define CDC_A_BOOST_EN_CTL (0xf1C3) + #define CDC_A_SLOPE_COMP_IP_ZERO (0xf1C4) + #define CDC_A_SEC_ACCESS (0xf1D0) +@@ -286,6 +292,11 @@ static const char * const supply_names[] = { + + #define MBHC_MAX_BUTTONS (5) + ++struct wcd_reg_seq { ++ const struct reg_default *seq; ++ int seq_size; ++}; ++ + struct pm8916_wcd_analog_priv { + u16 pmic_rev; + u16 codec_version; +@@ -696,6 +707,59 @@ static int pm8916_wcd_analog_enable_ear_pa(struct snd_soc_dapm_widget *w, + return 0; + } + ++static int pm8916_wcd_analog_enable_hphl_pa(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, ++ int event) ++{ ++ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); ++ ++ /* This quirk is not required for revisions prior to CAJON_2_0 */ ++ if (priv->codec_version < 4) ++ return 0; ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ usleep_range(7000, 7100); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_L_TEST, ++ 0x04, 0x04); ++ break; ++ case SND_SOC_DAPM_POST_PMD: ++ /* wait 20 ms after the digital codec has powered down */ ++ msleep(20); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_L_TEST, ++ 0x04, 0x00); ++ break; ++ } ++ return 0; ++} ++ ++static int pm8916_wcd_analog_enable_hphr_pa(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, ++ int event) ++{ ++ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); ++ ++ /* This quirk is not required for revisions prior to CAJON_2_0 */ ++ if (priv->codec_version < 4) ++ return 0; ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ usleep_range(7000, 7100); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_R_TEST, ++ 0x04, 0x04); ++ break; ++ case SND_SOC_DAPM_POST_PMD: ++ msleep(20); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_R_TEST, ++ 0x04, 0x00); ++ break; ++ } ++ return 0; ++} ++ + static const struct reg_default wcd_reg_defaults_2_0[] = { + {CDC_A_RX_COM_OCP_CTL, 0xD1}, + {CDC_A_RX_COM_OCP_COUNT, 0xFF}, +@@ -715,9 +779,69 @@ static const struct reg_default wcd_reg_defaults_2_0[] = { + {CDC_A_MASTER_BIAS_CTL, 0x30}, + }; + ++static const struct wcd_reg_seq pm8916_data = { ++ .seq = wcd_reg_defaults_2_0, ++ .seq_size = ARRAY_SIZE(wcd_reg_defaults_2_0), ++}; ++ ++static const struct reg_default wcd_reg_defaults_pm8950[] = { ++ {CDC_A_RX_COM_OCP_CTL, 0xd1}, ++ {CDC_A_RX_COM_OCP_COUNT, 0xff}, ++ {CDC_D_SEC_ACCESS, 0xa5}, ++ {CDC_D_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_TX_1_2_OPAMP_BIAS, 0x4c}, ++ {CDC_A_NCP_FBCTRL, 0xa8}, ++ {CDC_A_NCP_VCTRL, 0xa4}, ++ {CDC_A_SPKR_DRV_CTL, 0x69}, ++ {CDC_A_SPKR_DRV_DBG, 0x01}, ++ {CDC_A_SEC_ACCESS, 0xa5}, ++ {CDC_A_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_CURRENT_LIMIT, 0x82}, ++ {CDC_A_SPKR_ANA_BIAS_SET, 0x41}, ++ {CDC_A_SPKR_DAC_CTL, 0x03}, ++ {CDC_A_SPKR_OCP_CTL, 0xe1}, ++ {CDC_A_RX_HPH_BIAS_PA, 0xfa}, ++ {CDC_A_MASTER_BIAS_CTL, 0x30}, ++ {CDC_A_MICB_1_INT_RBIAS, 0x00}, ++}; ++ ++static const struct wcd_reg_seq pm8950_data = { ++ .seq = wcd_reg_defaults_pm8950, ++ .seq_size = ARRAY_SIZE(wcd_reg_defaults_pm8950), ++}; ++ ++static const struct reg_default wcd_reg_defaults_pm8953[] = { ++ {CDC_A_RX_COM_OCP_CTL, 0xd1}, ++ {CDC_A_RX_COM_OCP_COUNT, 0xff}, ++ {CDC_D_SEC_ACCESS, 0xa5}, ++ {CDC_D_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_TX_1_2_OPAMP_BIAS, 0x4c}, ++ {CDC_A_NCP_FBCTRL, 0xa8}, ++ {CDC_A_NCP_VCTRL, 0xa4}, ++ {CDC_A_SPKR_DRV_CTL, 0x69}, ++ {CDC_A_SPKR_DRV_DBG, 0x01}, ++ {CDC_A_SEC_ACCESS, 0xa5}, ++ {CDC_A_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_CURRENT_LIMIT, 0xa2}, ++ {CDC_A_BYPASS_MODE, 0x18}, ++ {CDC_A_SPKR_ANA_BIAS_SET, 0x41}, ++ {CDC_A_SPKR_DAC_CTL, 0x03}, ++ {CDC_A_SPKR_OCP_CTL, 0xe1}, ++ {CDC_A_RX_HPH_BIAS_PA, 0xfa}, ++ {CDC_A_RX_EAR_STATUS, 0x10}, ++ {CDC_A_MASTER_BIAS_CTL, 0x30}, ++ {CDC_A_MICB_1_INT_RBIAS, 0x00}, ++}; ++ ++static const struct wcd_reg_seq pm8953_data = { ++ .seq = wcd_reg_defaults_pm8953, ++ .seq_size = ARRAY_SIZE(wcd_reg_defaults_pm8953), ++}; ++ + static int pm8916_wcd_analog_probe(struct snd_soc_component *component) + { + struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); ++ const struct wcd_reg_seq *wcd_reg_init_data; + int err, reg; + + err = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); +@@ -738,9 +862,11 @@ static int pm8916_wcd_analog_probe(struct snd_soc_component *component) + snd_soc_component_write(component, CDC_D_PERPH_RESET_CTL4, 0x01); + snd_soc_component_write(component, CDC_A_PERPH_RESET_CTL4, 0x01); + +- for (reg = 0; reg < ARRAY_SIZE(wcd_reg_defaults_2_0); reg++) +- snd_soc_component_write(component, wcd_reg_defaults_2_0[reg].reg, +- wcd_reg_defaults_2_0[reg].def); ++ wcd_reg_init_data = of_device_get_match_data(component->dev); ++ ++ for (reg = 0; reg < wcd_reg_init_data->seq_size; reg++) ++ snd_soc_component_write(component, wcd_reg_init_data->seq[reg].reg, ++ wcd_reg_init_data->seq[reg].def); + + priv->component = component; + +@@ -883,11 +1009,15 @@ static const struct snd_soc_dapm_widget pm8916_wcd_analog_dapm_widgets[] = { + SND_SOC_DAPM_MUX("EAR_S", SND_SOC_NOPM, 0, 0, &ear_mux), + SND_SOC_DAPM_SUPPLY("EAR CP", CDC_A_NCP_EN, 4, 0, NULL, 0), + +- SND_SOC_DAPM_PGA("HPHL PA", CDC_A_RX_HPH_CNP_EN, 5, 0, NULL, 0), ++ SND_SOC_DAPM_PGA_E("HPHL PA", CDC_A_RX_HPH_CNP_EN, 5, 0, NULL, 0, ++ pm8916_wcd_analog_enable_hphl_pa, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("HPHL", SND_SOC_NOPM, 0, 0, &hphl_mux), + SND_SOC_DAPM_MIXER("HPHL DAC", CDC_A_RX_HPH_L_PA_DAC_CTL, 3, 0, NULL, + 0), +- SND_SOC_DAPM_PGA("HPHR PA", CDC_A_RX_HPH_CNP_EN, 4, 0, NULL, 0), ++ SND_SOC_DAPM_PGA_E("HPHR PA", CDC_A_RX_HPH_CNP_EN, 4, 0, NULL, 0, ++ pm8916_wcd_analog_enable_hphr_pa, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("HPHR", SND_SOC_NOPM, 0, 0, &hphr_mux), + SND_SOC_DAPM_MIXER("HPHR DAC", CDC_A_RX_HPH_R_PA_DAC_CTL, 3, 0, NULL, + 0), +@@ -1259,7 +1389,9 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev) + } + + static const struct of_device_id pm8916_wcd_analog_spmi_match_table[] = { +- { .compatible = "qcom,pm8916-wcd-analog-codec", }, ++ { .compatible = "qcom,pm8916-wcd-analog-codec", .data = &pm8916_data }, ++ { .compatible = "qcom,pm8950-wcd-analog-codec", .data = &pm8950_data }, ++ { .compatible = "qcom,pm8953-wcd-analog-codec", .data = &pm8953_data }, + { } + }; + +diff --git a/sound/soc/codecs/mt6357.c b/sound/soc/codecs/mt6357.c +--- a/sound/soc/codecs/mt6357.c ++++ b/sound/soc/codecs/mt6357.c +@@ -1834,8 +1834,8 @@ static int mt6357_platform_driver_probe(struct platform_device *pdev) + } + + static const struct platform_device_id mt6357_platform_ids[] = { +- {"mt6357-sound", 0}, +- { /* sentinel */ }, ++ { .name = "mt6357-sound" }, ++ { /* sentinel */ } + }; + MODULE_DEVICE_TABLE(platform, mt6357_platform_ids); + +diff --git a/sound/soc/codecs/mt6359-accdet.c b/sound/soc/codecs/mt6359-accdet.c +--- a/sound/soc/codecs/mt6359-accdet.c ++++ b/sound/soc/codecs/mt6359-accdet.c +@@ -6,6 +6,7 @@ + // Author: Argus Lin + // + ++#include + #include + #include + #include +@@ -398,14 +399,13 @@ static void mt6359_accdet_work(struct work_struct *work) + struct mt6359_accdet *priv = + container_of(work, struct mt6359_accdet, accdet_work); + +- mutex_lock(&priv->res_lock); ++ guard(mutex)(&priv->res_lock); + priv->pre_accdet_status = priv->accdet_status; + check_jack_btn_type(priv); + + if (priv->jack_plugged && + priv->pre_accdet_status != priv->accdet_status) + mt6359_accdet_jack_report(priv); +- mutex_unlock(&priv->res_lock); + } + + static void mt6359_accdet_jd_work(struct work_struct *work) +@@ -416,7 +416,7 @@ static void mt6359_accdet_jd_work(struct work_struct *work) + struct mt6359_accdet *priv = + container_of(work, struct mt6359_accdet, jd_work); + +- mutex_lock(&priv->res_lock); ++ guard(mutex)(&priv->res_lock); + if (priv->jd_sts == M_PLUG_IN) { + priv->jack_plugged = true; + +@@ -450,7 +450,6 @@ static void mt6359_accdet_jd_work(struct work_struct *work) + + if (priv->caps & ACCDET_PMIC_EINT_IRQ) + recover_eint_setting(priv); +- mutex_unlock(&priv->res_lock); + } + + static irqreturn_t mt6359_accdet_irq(int irq, void *data) +@@ -459,7 +458,7 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + unsigned int irq_val = 0, val = 0, value = 0; + int ret; + +- mutex_lock(&priv->res_lock); ++ guard(mutex)(&priv->res_lock); + regmap_read(priv->regmap, ACCDET_IRQ_ADDR, &irq_val); + + if (irq_val & ACCDET_IRQ_MASK_SFT) { +@@ -474,7 +473,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + 1000); + if (ret) { + dev_err(priv->dev, "%s(), ret %d\n", __func__, ret); +- mutex_unlock(&priv->res_lock); + return IRQ_NONE; + } + regmap_update_bits(priv->regmap, ACCDET_IRQ_ADDR, +@@ -498,7 +496,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + if (ret) { + dev_err(priv->dev, "%s(), ret %d\n", __func__, + ret); +- mutex_unlock(&priv->res_lock); + return IRQ_NONE; + } + regmap_update_bits(priv->regmap, ACCDET_IRQ_ADDR, +@@ -521,7 +518,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + if (ret) { + dev_err(priv->dev, "%s(), ret %d\n", __func__, + ret); +- mutex_unlock(&priv->res_lock); + return IRQ_NONE; + } + regmap_update_bits(priv->regmap, ACCDET_IRQ_ADDR, +@@ -540,7 +536,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + + queue_work(priv->jd_workqueue, &priv->jd_work); + } +- mutex_unlock(&priv->res_lock); + + return IRQ_HANDLED; + } +diff --git a/sound/soc/codecs/ntpfw.c b/sound/soc/codecs/ntpfw.c +--- a/sound/soc/codecs/ntpfw.c ++++ b/sound/soc/codecs/ntpfw.c +@@ -89,7 +89,7 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) + { + struct device *dev = &i2c->dev; + const struct ntpfw_chunk *chunk; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + const u8 *data; + size_t leftover; + int ret; +@@ -101,10 +101,8 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) + return ret; + } + +- if (!ntpfw_verify(dev, fw->data, fw->size, magic)) { +- ret = -EINVAL; +- goto done; +- } ++ if (!ntpfw_verify(dev, fw->data, fw->size, magic)) ++ return -EINVAL; + + data = fw->data + sizeof(struct ntpfw_header); + leftover = fw->size - sizeof(struct ntpfw_header); +@@ -112,23 +110,18 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) + while (leftover) { + chunk = (struct ntpfw_chunk *)data; + +- if (!ntpfw_verify_chunk(dev, chunk, leftover)) { +- ret = -EINVAL; +- goto done; +- } ++ if (!ntpfw_verify_chunk(dev, chunk, leftover)) ++ return -EINVAL; + + ret = ntpfw_send_chunk(i2c, chunk); + if (ret) +- goto done; ++ return ret; + + data += be16_to_cpu(chunk->length) + sizeof(*chunk); + leftover -= be16_to_cpu(chunk->length) + sizeof(*chunk); + } + +-done: +- release_firmware(fw); +- +- return ret; ++ return 0; + } + EXPORT_SYMBOL_GPL(ntpfw_load); + +diff --git a/sound/soc/codecs/ntpfw.h b/sound/soc/codecs/ntpfw.h +--- a/sound/soc/codecs/ntpfw.h ++++ b/sound/soc/codecs/ntpfw.h +@@ -1,5 +1,5 @@ + /* SPDX-License-Identifier: GPL-2.0-only */ +-/** ++/* + * ntpfw.h - Firmware helper functions for Neofidelity codecs + * + * Copyright (c) 2024, SaluteDevices. All Rights Reserved. +@@ -13,10 +13,11 @@ + /** + * ntpfw_load - load firmware to amplifier over i2c interface. + * +- * @i2c Pointer to amplifier's I2C client. +- * @name Firmware file name. +- * @magic Magic number to validate firmware. +- * @return 0 or error code upon error. ++ * @i2c: Pointer to amplifier's I2C client. ++ * @name: Firmware file name. ++ * @magic: Magic number to validate firmware. ++ * ++ * Returns: 0 or error code upon error. + */ + int ntpfw_load(struct i2c_client *i2c, const char *name, const u32 magic); + +diff --git a/sound/soc/codecs/pcm1681.c b/sound/soc/codecs/pcm1681.c +--- a/sound/soc/codecs/pcm1681.c ++++ b/sound/soc/codecs/pcm1681.c +@@ -199,10 +199,17 @@ static int pcm1681_hw_params(struct snd_pcm_substream *substream, + return pcm1681_set_deemph(component); + } + ++static const u64 pcm1681_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J; ++ + static const struct snd_soc_dai_ops pcm1681_dai_ops = { + .set_fmt = pcm1681_set_dai_fmt, + .hw_params = pcm1681_hw_params, + .mute_stream = pcm1681_mute, ++ .auto_selectable_formats = &pcm1681_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm1754.c b/sound/soc/codecs/pcm1754.c +--- a/sound/soc/codecs/pcm1754.c ++++ b/sound/soc/codecs/pcm1754.c +@@ -78,10 +78,16 @@ static int pcm1754_mute_stream(struct snd_soc_dai *dai, int mute, int stream) + return 0; + } + ++static const u64 pcm1754_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J; ++ + static const struct snd_soc_dai_ops pcm1754_dai_ops = { + .set_fmt = pcm1754_set_dai_fmt, + .hw_params = pcm1754_hw_params, + .mute_stream = pcm1754_mute_stream, ++ .auto_selectable_formats = &pcm1754_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_driver pcm1754_dai = { +diff --git a/sound/soc/codecs/pcm1789.c b/sound/soc/codecs/pcm1789.c +--- a/sound/soc/codecs/pcm1789.c ++++ b/sound/soc/codecs/pcm1789.c +@@ -164,11 +164,18 @@ static int pcm1789_trigger(struct snd_pcm_substream *substream, int cmd, + return ret; + } + ++static const u64 pcm1789_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J; ++ + static const struct snd_soc_dai_ops pcm1789_dai_ops = { + .set_fmt = pcm1789_set_dai_fmt, + .hw_params = pcm1789_hw_params, + .mute_stream = pcm1789_mute, + .trigger = pcm1789_trigger, ++ .auto_selectable_formats = &pcm1789_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm179x.c b/sound/soc/codecs/pcm179x.c +--- a/sound/soc/codecs/pcm179x.c ++++ b/sound/soc/codecs/pcm179x.c +@@ -142,10 +142,16 @@ static int pcm179x_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 pcm179x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J; ++ + static const struct snd_soc_dai_ops pcm179x_dai_ops = { + .set_fmt = pcm179x_set_dai_fmt, + .hw_params = pcm179x_hw_params, + .mute_stream = pcm179x_mute, ++ .auto_selectable_formats = &pcm179x_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm186x.c b/sound/soc/codecs/pcm186x.c +--- a/sound/soc/codecs/pcm186x.c ++++ b/sound/soc/codecs/pcm186x.c +@@ -473,11 +473,20 @@ static int pcm186x_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + return 0; + } + ++static const u64 pcm186x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops pcm186x_dai_ops = { + .set_sysclk = pcm186x_set_dai_sysclk, + .set_tdm_slot = pcm186x_set_tdm_slot, + .set_fmt = pcm186x_set_fmt, + .hw_params = pcm186x_hw_params, ++ .auto_selectable_formats = &pcm186x_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver pcm1863_dai = { +diff --git a/sound/soc/codecs/pcm3060.c b/sound/soc/codecs/pcm3060.c +--- a/sound/soc/codecs/pcm3060.c ++++ b/sound/soc/codecs/pcm3060.c +@@ -164,10 +164,18 @@ static int pcm3060_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 pcm3060_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops pcm3060_dai_ops = { + .set_sysclk = pcm3060_set_sysclk, + .set_fmt = pcm3060_set_fmt, + .hw_params = pcm3060_hw_params, ++ .auto_selectable_formats = &pcm3060_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define PCM3060_DAI_RATES_ADC (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | \ +diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c +--- a/sound/soc/codecs/pcm512x.c ++++ b/sound/soc/codecs/pcm512x.c +@@ -6,7 +6,7 @@ + * Copyright 2014 Linaro Ltd + */ + +- ++#include + #include + #include + #include +@@ -399,10 +399,9 @@ static int pcm512x_digital_playback_switch_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + +- mutex_lock(&pcm512x->mutex); ++ guard(mutex)(&pcm512x->mutex); + ucontrol->value.integer.value[0] = !(pcm512x->mute & 0x4); + ucontrol->value.integer.value[1] = !(pcm512x->mute & 0x2); +- mutex_unlock(&pcm512x->mutex); + + return 0; + } +@@ -414,7 +413,7 @@ static int pcm512x_digital_playback_switch_put(struct snd_kcontrol *kcontrol, + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + int ret, changed = 0; + +- mutex_lock(&pcm512x->mutex); ++ guard(mutex)(&pcm512x->mutex); + + if ((pcm512x->mute & 0x4) == (ucontrol->value.integer.value[0] << 2)) { + pcm512x->mute ^= 0x4; +@@ -430,13 +429,10 @@ static int pcm512x_digital_playback_switch_put(struct snd_kcontrol *kcontrol, + if (ret != 0) { + dev_err(component->dev, + "Failed to update digital mute: %d\n", ret); +- mutex_unlock(&pcm512x->mutex); + return ret; + } + } + +- mutex_unlock(&pcm512x->mutex); +- + return changed; + } + +@@ -1465,7 +1461,7 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + int ret; + unsigned int mute_det; + +- mutex_lock(&pcm512x->mutex); ++ guard(mutex)(&pcm512x->mutex); + + if (mute) { + pcm512x->mute |= 0x1; +@@ -1475,7 +1471,7 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + if (ret != 0) { + dev_err(component->dev, + "Failed to set digital mute: %d\n", ret); +- goto unlock; ++ return ret; + } + + regmap_read_poll_timeout(pcm512x->regmap, +@@ -1488,7 +1484,7 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + if (ret != 0) { + dev_err(component->dev, + "Failed to update digital mute: %d\n", ret); +- goto unlock; ++ return ret; + } + + regmap_read_poll_timeout(pcm512x->regmap, +@@ -1499,18 +1495,24 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + 200, 10000); + } + +-unlock: +- mutex_unlock(&pcm512x->mutex); +- + return ret; + } + ++static const u64 pcm512x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B; ++ + static const struct snd_soc_dai_ops pcm512x_dai_ops = { + .startup = pcm512x_dai_startup, + .hw_params = pcm512x_hw_params, + .set_fmt = pcm512x_set_fmt, + .mute_stream = pcm512x_mute, + .set_bclk_ratio = pcm512x_set_bclk_ratio, ++ .auto_selectable_formats = &pcm512x_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm6240.c b/sound/soc/codecs/pcm6240.c +--- a/sound/soc/codecs/pcm6240.c ++++ b/sound/soc/codecs/pcm6240.c +@@ -12,6 +12,7 @@ + // Author: Shenghao Ding + // + ++#include + #include + #include + #include +@@ -605,7 +606,7 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, + unsigned int reg = mc->reg; + unsigned int val; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + + if (pcm_dev->chip_id == PCM1690) { + ret = pcmdev_dev_read(pcm_dev, dev_no, PCM1690_REG_MODE_CTRL, +@@ -613,18 +614,18 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, + if (ret) { + dev_err(pcm_dev->dev, "%s: read mode err=%d\n", + __func__, ret); +- goto out; ++ return ret; + } + val &= PCM1690_REG_MODE_CTRL_DAMS_MSK; + /* Set to wide-range mode, before using vol ctrl. */ + if (!val && vol_ctrl_type == PCMDEV_PCM1690_VOL_CTRL) { + ucontrol->value.integer.value[0] = -25500; +- goto out; ++ return ret; + } + /* Set to fine mode, before using fine vol ctrl. */ + if (val && vol_ctrl_type == PCMDEV_PCM1690_FINE_VOL_CTRL) { + ucontrol->value.integer.value[0] = -12750; +- goto out; ++ return ret; + } + } + +@@ -632,15 +633,14 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, + if (ret) { + dev_err(pcm_dev->dev, "%s: read err=%d\n", + __func__, ret); +- goto out; ++ return ret; + } + + val = (val >> shift) & mask; + val = (val > max) ? max : val; + val = mc->invert ? max - val : val; + ucontrol->value.integer.value[0] = val; +-out: +- mutex_unlock(&pcm_dev->codec_lock); ++ + return ret; + } + +@@ -678,7 +678,7 @@ static int pcmdev_put_volsw(struct snd_kcontrol *kcontrol, + unsigned int val, val_mask; + unsigned int reg = mc->reg; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + val = ucontrol->value.integer.value[0] & mask; + val = (val > max) ? max : val; + val = mc->invert ? max - val : val; +@@ -702,7 +702,7 @@ static int pcmdev_put_volsw(struct snd_kcontrol *kcontrol, + __func__, rc); + else + rc = 1; +- mutex_unlock(&pcm_dev->codec_lock); ++ + return rc; + } + +@@ -1577,10 +1577,10 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) + { + struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(comp); + struct i2c_adapter *adap = pcm_dev->client->adapter; +- const struct firmware *fw_entry = NULL; ++ const struct firmware *fw_entry __free(firmware) = NULL; + int ret, i, j; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + + pcm_dev->component = comp; + +@@ -1588,7 +1588,7 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) + for (j = 0; j < 2; j++) { + ret = pcmdev_gain_ctrl_add(pcm_dev, i, j); + if (ret < 0) +- goto out; ++ return ret; + } + } + +@@ -1621,21 +1621,17 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) + if (ret) { + dev_err(pcm_dev->dev, "%s: request %s err = %d\n", __func__, + pcm_dev->bin_name, ret); +- goto out; ++ return ret; + } + + ret = pcmdev_regbin_ready(fw_entry, pcm_dev); + if (ret) { + dev_err(pcm_dev->dev, "%s: %s parse err = %d\n", __func__, + pcm_dev->bin_name, ret); +- goto out; ++ return ret; + } +- ret = pcmdev_profile_ctrl_add(pcm_dev); +-out: +- release_firmware(fw_entry); + +- mutex_unlock(&pcm_dev->codec_lock); +- return ret; ++ return pcmdev_profile_ctrl_add(pcm_dev); + } + + +@@ -1645,9 +1641,8 @@ static void pcmdevice_comp_remove(struct snd_soc_component *codec) + + if (!pcm_dev) + return; +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + pcmdevice_config_info_remove(pcm_dev); +- mutex_unlock(&pcm_dev->codec_lock); + } + + static const struct snd_soc_dapm_widget pcmdevice_dapm_widgets[] = { +@@ -1890,9 +1885,9 @@ static int pcmdevice_mute(struct snd_soc_dai *dai, int mute, int stream) + else + block_type = PCMDEVICE_BIN_BLK_PRE_POWER_UP; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + pcmdevice_select_cfg_blk(pcm_dev, pcm_dev->cur_conf, block_type); +- mutex_unlock(&pcm_dev->codec_lock); ++ + return 0; + } + +diff --git a/sound/soc/codecs/peb2466.c b/sound/soc/codecs/peb2466.c +--- a/sound/soc/codecs/peb2466.c ++++ b/sound/soc/codecs/peb2466.c +@@ -6,6 +6,7 @@ + // + // Author: Herve Codina + ++#include + #include + #include + #include +@@ -1537,17 +1538,14 @@ static int peb2466_fw_parse(struct snd_soc_component *component, + + static int peb2466_load_coeffs(struct snd_soc_component *component, const char *fw_name) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw, fw_name, component->dev); + if (ret) + return ret; + +- ret = peb2466_fw_parse(component, fw->data, fw->size); +- release_firmware(fw); +- +- return ret; ++ return peb2466_fw_parse(component, fw->data, fw->size); + } + + static int peb2466_component_probe(struct snd_soc_component *component) +@@ -1704,13 +1702,11 @@ static int peb2466_chip_gpio_update_bits(struct peb2466 *peb2466, unsigned int x + * So, a specific cache value is used. + */ + +- mutex_lock(&peb2466->gpio.lock); ++ guard(mutex)(&peb2466->gpio.lock); + + cache = peb2466_chip_gpio_get_cache(peb2466, xr_reg); +- if (!cache) { +- ret = -EINVAL; +- goto end; +- } ++ if (!cache) ++ return -EINVAL; + + tmp = *cache; + tmp &= ~mask; +@@ -1718,14 +1714,11 @@ static int peb2466_chip_gpio_update_bits(struct peb2466 *peb2466, unsigned int x + + ret = regmap_write(peb2466->regmap, xr_reg, tmp); + if (ret) +- goto end; ++ return ret; + + *cache = tmp; +- ret = 0; + +-end: +- mutex_unlock(&peb2466->gpio.lock); +- return ret; ++ return 0; + } + + static int peb2466_chip_gpio_set(struct gpio_chip *c, unsigned int offset, +diff --git a/sound/soc/codecs/pm4125-sdw.c b/sound/soc/codecs/pm4125-sdw.c +--- a/sound/soc/codecs/pm4125-sdw.c ++++ b/sound/soc/codecs/pm4125-sdw.c +@@ -464,10 +464,16 @@ static int __maybe_unused pm4125_sdw_runtime_suspend(struct device *dev) + static int __maybe_unused pm4125_sdw_runtime_resume(struct device *dev) + { + struct pm4125_sdw_priv *priv = dev_get_drvdata(dev); ++ int ret; + + if (priv->regmap) { + regcache_cache_only(priv->regmap, false); +- regcache_sync(priv->regmap); ++ ret = regcache_sync(priv->regmap); ++ if (ret) { ++ regcache_cache_only(priv->regmap, true); ++ regcache_mark_dirty(priv->regmap); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/pm4125.c b/sound/soc/codecs/pm4125.c +--- a/sound/soc/codecs/pm4125.c ++++ b/sound/soc/codecs/pm4125.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1295,7 +1296,7 @@ static const struct irq_domain_ops pm4125_domain_ops = { + + static int pm4125_irq_init(struct pm4125_priv *pm4125, struct device *dev) + { +- pm4125->virq = irq_domain_add_linear(NULL, 1, &pm4125_domain_ops, NULL); ++ pm4125->virq = irq_domain_create_linear(NULL, 1, &pm4125_domain_ops, NULL); + if (!(pm4125->virq)) { + dev_err(dev, "%s: Failed to add IRQ domain\n", __func__); + return -EINVAL; +diff --git a/sound/soc/codecs/rt1017-sdca-sdw.c b/sound/soc/codecs/rt1017-sdca-sdw.c +--- a/sound/soc/codecs/rt1017-sdca-sdw.c ++++ b/sound/soc/codecs/rt1017-sdca-sdw.c +@@ -784,7 +784,12 @@ static int rt1017_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt1017->regmap, false); +- regcache_sync(rt1017->regmap); ++ ret = regcache_sync(rt1017->regmap); ++ if (ret) { ++ regcache_cache_only(rt1017->regmap, true); ++ regcache_mark_dirty(rt1017->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt1316-sdw.c b/sound/soc/codecs/rt1316-sdw.c +--- a/sound/soc/codecs/rt1316-sdw.c ++++ b/sound/soc/codecs/rt1316-sdw.c +@@ -756,7 +756,12 @@ static int rt1316_dev_resume(struct device *dev) + } + + regcache_cache_only(rt1316->regmap, false); +- regcache_sync(rt1316->regmap); ++ ret = regcache_sync(rt1316->regmap); ++ if (ret) { ++ regcache_cache_only(rt1316->regmap, true); ++ regcache_mark_dirty(rt1316->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt1318-sdw.c b/sound/soc/codecs/rt1318-sdw.c +--- a/sound/soc/codecs/rt1318-sdw.c ++++ b/sound/soc/codecs/rt1318-sdw.c +@@ -830,7 +830,12 @@ static int rt1318_dev_resume(struct device *dev) + return ret; + + regcache_cache_only(rt1318->regmap, false); +- regcache_sync(rt1318->regmap); ++ ret = regcache_sync(rt1318->regmap); ++ if (ret) { ++ regcache_cache_only(rt1318->regmap, true); ++ regcache_mark_dirty(rt1318->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c +--- a/sound/soc/codecs/rt1320-sdw.c ++++ b/sound/soc/codecs/rt1320-sdw.c +@@ -410,6 +410,402 @@ static const struct reg_sequence rt1321_blind_write[] = { + { 0x41001988, 0x03 }, + }; + ++static const struct reg_sequence rt1321_va1_blind_write[] = { ++ { 0x0000c003, 0xf0 }, ++ { 0x0000c01b, 0xfc }, ++ { 0x0000c5c3, 0xf2 }, ++ { 0x0000c5c2, 0x00 }, ++ { 0x0000c5c1, 0x10 }, ++ { 0x0000c5c0, 0x04 }, ++ { 0x0000c5c7, 0x03 }, ++ { 0x0000c5c6, 0x10 }, ++ { 0x0000c526, 0x47 }, ++ { 0x0000c5c4, 0x12 }, ++ { 0x0000c5c5, 0x60 }, ++ { 0x0000c520, 0x10 }, ++ { 0x0000c521, 0x32 }, ++ { 0x0000c5c7, 0x00 }, ++ { 0x0000c5c8, 0x03 }, ++ { 0x0000c5d3, 0x08 }, ++ { 0x0000c5d2, 0x0a }, ++ { 0x0000c5d1, 0x49 }, ++ { 0x0000c5d0, 0x0f }, ++ { 0x0000c580, 0x10 }, ++ { 0x0000c581, 0x32 }, ++ { 0x0000c582, 0x01 }, ++ { 0x0000c682, 0x60 }, ++ { 0x0000c019, 0x10 }, ++ { 0x0000c5f0, 0x01 }, ++ { 0x0000c5f7, 0x22 }, ++ { 0x0000c5f6, 0x22 }, ++ { 0x0000c057, 0x51 }, ++ { 0x0000c054, 0x55 }, ++ { 0x0000c053, 0x55 }, ++ { 0x0000c052, 0x55 }, ++ { 0x0000c051, 0x01 }, ++ { 0x0000c050, 0x15 }, ++ { 0x0000c060, 0x99 }, ++ { 0x0000c030, 0x55 }, ++ { 0x0000c061, 0x55 }, ++ { 0x0000c063, 0x55 }, ++ { 0x0000c065, 0xa5 }, ++ { 0x0000c06b, 0x0a }, ++ { 0x0000ca05, 0xd6 }, ++ { 0x0000ca06, 0x11 }, ++ { 0x0000ca07, 0x1e }, ++ { 0x0000ca25, 0xd6 }, ++ { 0x0000ca26, 0x11 }, ++ { 0x0000ca27, 0x1e }, ++ { 0x0000cd00, 0x05 }, ++ { 0x0000cd81, 0x49 }, ++ { 0x0000cd82, 0x49 }, ++ { 0x0000c604, 0x40 }, ++ { 0x0000c609, 0x40 }, ++ { 0x0000c046, 0xf7 }, ++ { 0x0000c045, 0xff }, ++ { 0x0000c044, 0xff }, ++ { 0x0000c043, 0xff }, ++ { 0x0000c042, 0xff }, ++ { 0x0000c041, 0xff }, ++ { 0x0000c040, 0xff }, ++ { 0x0000c049, 0xff }, ++ { 0x0000c028, 0x3f }, ++ { 0x0000c020, 0x3f }, ++ { 0x0000c032, 0x13 }, ++ { 0x0000c033, 0x01 }, ++ { 0x0000cc10, 0x01 }, ++ { 0x0000dc20, 0x03 }, ++ { 0x0000de03, 0x05 }, ++ { 0x0000dc00, 0x00 }, ++ { 0x0000c700, 0xf0 }, ++ { 0x0000c701, 0x13 }, ++ { 0x0000c900, 0xc3 }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000c086, 0x02 }, ++ { 0x0000c085, 0x7f }, ++ { 0x0000c084, 0x00 }, ++ { 0x0000c081, 0xff }, ++ { 0x0000f084, 0x0f }, ++ { 0x0000f083, 0xff }, ++ { 0x0000f082, 0xff }, ++ { 0x0000f081, 0xff }, ++ { 0x0000f080, 0xff }, ++ { 0x20003003, 0x3f }, ++ { 0x20005818, 0x81 }, ++ { 0x20009018, 0x81 }, ++ { 0x2000301c, 0x81 }, ++ { 0x0000c003, 0xc0 }, ++ { 0x0000c047, 0x80 }, ++ { 0x0000d541, 0x80 }, ++ { 0x0000d487, 0x0b }, ++ { 0x0000d487, 0x3b }, ++ { 0x0000d486, 0xc3 }, ++ { 0x0000d470, 0x89 }, ++ { 0x0000d471, 0x3a }, ++ { 0x0000d472, 0x3d }, ++ { 0x0000d474, 0x11 }, ++ { 0x0000d475, 0x32 }, ++ { 0x0000d476, 0x64 }, ++ { 0x0000d477, 0x10 }, ++ { 0x0000d478, 0xff }, ++ { 0x0000d479, 0x20 }, ++ { 0x0000d47a, 0x10 }, ++ { 0x0000d73c, 0xb7 }, ++ { 0x0000d73d, 0xd7 }, ++ { 0x0000d73e, 0x00 }, ++ { 0x0000d73f, 0x10 }, ++ { 0x1000cd56, 0x00 }, ++ { 0x3fc2dfc0, 0x03 }, ++ { 0x3fc2dfc1, 0x00 }, ++ { 0x3fc2dfc2, 0x00 }, ++ { 0x3fc2dfc3, 0x00 }, ++ { 0x3fc2dfc4, 0x01 }, ++ { 0x3fc2dfc5, 0x00 }, ++ { 0x3fc2dfc6, 0x00 }, ++ { 0x3fc2dfc7, 0x00 }, ++ { 0x3fc2df80, 0x00 }, ++ { 0x3fc2df81, 0x00 }, ++ { 0x3fc2df82, 0x00 }, ++ { 0x3fc2df83, 0x00 }, ++ { 0x0000d541, 0x40 }, ++ { 0x0000d486, 0x43 }, ++ { 0x1000db00, 0x04 }, ++ { 0x1000db01, 0x00 }, ++ { 0x1000db02, 0x10 }, ++ { 0x1000db03, 0x00 }, ++ { 0x1000db04, 0x00 }, ++ { 0x1000db05, 0x45 }, ++ { 0x1000db06, 0x0d }, ++ { 0x1000db07, 0x01 }, ++ { 0x1000db08, 0x00 }, ++ { 0x1000db09, 0x00 }, ++ { 0x1000db0a, 0xbf }, ++ { 0x1000db0b, 0x0b }, ++ { 0x1000db0c, 0x11 }, ++ { 0x1000db0d, 0x00 }, ++ { 0x1000db0e, 0x00 }, ++ { 0x1000db0f, 0x00 }, ++ { 0x1000db10, 0x2c }, ++ { 0x1000db11, 0xfa }, ++ { 0x1000db12, 0x00 }, ++ { 0x1000db13, 0x00 }, ++ { 0x1000db14, 0x09 }, ++ { 0x0000d540, 0x21 }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000d714, 0x17 }, ++ { 0x0000c5c3, 0xf2 }, ++ { 0x0000c5c8, 0x03 }, ++ { 0x20009012, 0x00 }, ++ { 0x0000dd08, 0x17 }, ++ { 0x0000dd09, 0x0e }, ++ { 0x0000dd0a, 0x17 }, ++ { 0x0000dd0b, 0x0e }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000d471, 0x3a }, ++ { 0x0000db00, 0x00 }, ++ { 0x0000db01, 0x00 }, ++ { 0x0000db02, 0x73 }, ++ { 0x0000db03, 0x00 }, ++ { 0x0000db04, 0x00 }, ++ { 0x0000db05, 0x00 }, ++ { 0x0000db06, 0x00 }, ++ { 0x0000db07, 0x00 }, ++ { 0x0000db08, 0x7f }, ++ { 0x0000db09, 0x00 }, ++ { 0x0000db1a, 0x00 }, ++ { 0x0000db1b, 0x00 }, ++ { 0x0000db19, 0x00 }, ++}; ++ ++static const struct reg_sequence rt1321_va2_blind_write[] = { ++ { 0x0000c003, 0xf0 }, ++ { 0x0000c01b, 0xfc }, ++ { 0x0000c5c3, 0xf2 }, ++ { 0x0000c5c2, 0x00 }, ++ { 0x0000c5c1, 0x10 }, ++ { 0x0000c5c0, 0x04 }, ++ { 0x0000c5c7, 0x03 }, ++ { 0x0000c5c6, 0x10 }, ++ { 0x0000c526, 0x47 }, ++ { 0x0000c5c4, 0x12 }, ++ { 0x0000c5c5, 0x60 }, ++ { 0x0000c520, 0x10 }, ++ { 0x0000c521, 0x32 }, ++ { 0x0000c5c7, 0x00 }, ++ { 0x0000c5c8, 0x03 }, ++ { 0x0000c5d3, 0x08 }, ++ { 0x0000c5d2, 0x0a }, ++ { 0x0000c5d1, 0x49 }, ++ { 0x0000c5d0, 0x0f }, ++ { 0x0000c580, 0x10 }, ++ { 0x0000c581, 0x32 }, ++ { 0x0000c582, 0x01 }, ++ { 0x0000c682, 0x60 }, ++ { 0x0000c019, 0x10 }, ++ { 0x0000c5f0, 0x01 }, ++ { 0x0000c5f7, 0x22 }, ++ { 0x0000c5f6, 0x22 }, ++ { 0x0000c057, 0x51 }, ++ { 0x0000c054, 0x55 }, ++ { 0x0000c053, 0x55 }, ++ { 0x0000c052, 0x55 }, ++ { 0x0000c051, 0x01 }, ++ { 0x0000c050, 0x15 }, ++ { 0x0000c060, 0x99 }, ++ { 0x0000c030, 0x55 }, ++ { 0x0000c061, 0x55 }, ++ { 0x0000c063, 0x55 }, ++ { 0x0000c065, 0xa5 }, ++ { 0x0000c06b, 0x0a }, ++ { 0x0000ca05, 0xd6 }, ++ { 0x0000ca06, 0x11 }, ++ { 0x0000ca07, 0x1e }, ++ { 0x0000ca25, 0xd6 }, ++ { 0x0000ca26, 0x11 }, ++ { 0x0000ca27, 0x1e }, ++ { 0x0000cd00, 0x05 }, ++ { 0x0000cd81, 0x49 }, ++ { 0x0000cd82, 0x49 }, ++ { 0x0000c604, 0x40 }, ++ { 0x0000c609, 0x40 }, ++ { 0x0000c046, 0xf7 }, ++ { 0x0000c045, 0xff }, ++ { 0x0000c044, 0xff }, ++ { 0x0000c043, 0xff }, ++ { 0x0000c042, 0xff }, ++ { 0x0000c041, 0xff }, ++ { 0x0000c040, 0xff }, ++ { 0x0000c049, 0xff }, ++ { 0x0000c028, 0x3f }, ++ { 0x0000c020, 0x3f }, ++ { 0x0000c032, 0x13 }, ++ { 0x0000c033, 0x01 }, ++ { 0x0000cc10, 0x01 }, ++ { 0x0000dc20, 0x03 }, ++ { 0x0000de03, 0x05 }, ++ { 0x0000dc00, 0x00 }, ++ { 0x0000c700, 0xf0 }, ++ { 0x0000c701, 0x13 }, ++ { 0x0000c900, 0xc3 }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000c086, 0x02 }, ++ { 0x0000c085, 0x7f }, ++ { 0x0000c084, 0x00 }, ++ { 0x0000c081, 0xff }, ++ { 0x0000f084, 0x0f }, ++ { 0x0000f083, 0xff }, ++ { 0x0000f082, 0xff }, ++ { 0x0000f081, 0xff }, ++ { 0x0000f080, 0xff }, ++ { 0x20003003, 0x3f }, ++ { 0x20005818, 0x81 }, ++ { 0x20009018, 0x81 }, ++ { 0x2000301c, 0x81 }, ++ { 0x0000c003, 0xc0 }, ++ { 0x0000c047, 0x80 }, ++ { 0x0000d541, 0x80 }, ++ { 0x0000d487, 0x0b }, ++ { 0x0000d487, 0x3b }, ++ { 0x0000d486, 0xc3 }, ++ { 0x0000d470, 0x89 }, ++ { 0x0000d471, 0x3a }, ++ { 0x0000d472, 0x3d }, ++ { 0x0000d474, 0x11 }, ++ { 0x0000d475, 0x32 }, ++ { 0x0000d476, 0x64 }, ++ { 0x0000d477, 0x10 }, ++ { 0x0000d478, 0xff }, ++ { 0x0000d479, 0x20 }, ++ { 0x0000d47a, 0x10 }, ++ { 0x10008000, 0x67 }, ++ { 0x10008001, 0x80 }, ++ { 0x10008002, 0x00 }, ++ { 0x10008003, 0x00 }, ++ { 0x1000cd56, 0x00 }, ++ { 0x0000d486, 0x43 }, ++ { 0x3fc2dfc3, 0x00 }, ++ { 0x3fc2dfc2, 0x00 }, ++ { 0x3fc2dfc1, 0x00 }, ++ { 0x3fc2dfc0, 0x03 }, ++ { 0x3fc2dfc7, 0x00 }, ++ { 0x3fc2dfc6, 0x00 }, ++ { 0x3fc2dfc5, 0x00 }, ++ { 0x3fc2dfc4, 0x01 }, ++ { 0x3fc2dfa3, 0x00 }, ++ { 0x3fc2dfa2, 0x00 }, ++ { 0x3fc2dfa1, 0x00 }, ++ { 0x3fc2dfa0, 0x00 }, ++ { 0x3fc2df80, 0x10 }, ++ { 0x3fc2df81, 0x20 }, ++ { 0x3fc2df82, 0x00 }, ++ { 0x3fc2df83, 0x00 }, ++ { 0x3fc2df84, 0x50 }, ++ { 0x3fc2df85, 0x19 }, ++ { 0x3fc2df86, 0x00 }, ++ { 0x3fc2df87, 0x00 }, ++ { 0x3fc2df88, 0x52 }, ++ { 0x3fc2df89, 0x23 }, ++ { 0x3fc2df8a, 0x00 }, ++ { 0x3fc2df8b, 0x00 }, ++ { 0x3fc2df8c, 0xe0 }, ++ { 0x3fc2df8d, 0x2e }, ++ { 0x3fc2df8e, 0x00 }, ++ { 0x3fc2df8f, 0x00 }, ++ { 0x3fc2df90, 0xe0 }, ++ { 0x3fc2df91, 0x2e }, ++ { 0x3fc2df92, 0x00 }, ++ { 0x3fc2df93, 0x00 }, ++ { 0x3fc2df94, 0x01 }, ++ { 0x3fc2df95, 0x08 }, ++ { 0x3fc2df96, 0x00 }, ++ { 0x3fc2df97, 0x00 }, ++ { 0x3fc2df40, 0x80 }, ++ { 0x3fc2df41, 0xbb }, ++ { 0x3fc2df42, 0x00 }, ++ { 0x3fc2df43, 0x00 }, ++ { 0x3fc2df44, 0xc0 }, ++ { 0x3fc2df45, 0x99 }, ++ { 0x3fc2df46, 0x01 }, ++ { 0x3fc2df47, 0x00 }, ++ { 0x3fc2df48, 0x00 }, ++ { 0x3fc2df49, 0x00 }, ++ { 0x3fc2df4a, 0x00 }, ++ { 0x3fc2df4b, 0x00 }, ++ { 0x3fc2df4c, 0x00 }, ++ { 0x3fc2df4d, 0x00 }, ++ { 0x3fc2df4e, 0x00 }, ++ { 0x3fc2df4f, 0x00 }, ++ { 0x3fc2df50, 0x01 }, ++ { 0x3fc2df51, 0x00 }, ++ { 0x3fc2df52, 0x00 }, ++ { 0x3fc2df53, 0x00 }, ++ { 0x3fc2df54, 0x01 }, ++ { 0x3fc2df55, 0x00 }, ++ { 0x3fc2df56, 0x00 }, ++ { 0x3fc2df57, 0x00 }, ++ { 0x3fc2df58, 0x00 }, ++ { 0x3fc2df59, 0x00 }, ++ { 0x3fc2df5a, 0x00 }, ++ { 0x3fc2df5b, 0x00 }, ++ { 0x3fc2df5c, 0x01 }, ++ { 0x3fc2df5d, 0x00 }, ++ { 0x3fc2df5e, 0x00 }, ++ { 0x3fc2df5f, 0x00 }, ++ { 0x3fc2df60, 0x00 }, ++ { 0x3fc2df61, 0x00 }, ++ { 0x3fc2df62, 0x00 }, ++ { 0x3fc2df63, 0x00 }, ++ { 0x3fc2df64, 0x00 }, ++ { 0x3fc2df65, 0x00 }, ++ { 0x3fc2df66, 0x00 }, ++ { 0x3fc2df67, 0x10 }, ++ { 0x3fc2df68, 0x01 }, ++ { 0x3fc2df69, 0x00 }, ++ { 0x3fc2df6a, 0x00 }, ++ { 0x3fc2df6b, 0x00 }, ++ { 0x3fc2df6c, 0x01 }, ++ { 0x3fc2df6d, 0x00 }, ++ { 0x3fc2df6e, 0x00 }, ++ { 0x3fc2df6f, 0x00 }, ++ { 0x3fc2df70, 0x04 }, ++ { 0x3fc2df71, 0x00 }, ++ { 0x3fc2df72, 0x00 }, ++ { 0x3fc2df73, 0x00 }, ++ { 0x3fc2df74, 0x01 }, ++ { 0x3fc2df75, 0x00 }, ++ { 0x3fc2df76, 0x00 }, ++ { 0x3fc2df77, 0x00 }, ++ { 0x1000db00, 0x04 }, ++ { 0x1000db01, 0x00 }, ++ { 0x1000db02, 0x10 }, ++ { 0x1000db03, 0x00 }, ++ { 0x1000db04, 0x00 }, ++ { 0x1000db05, 0x45 }, ++ { 0x1000db06, 0x0d }, ++ { 0x1000db07, 0x01 }, ++ { 0x1000db08, 0x00 }, ++ { 0x1000db09, 0x00 }, ++ { 0x1000db0a, 0xbf }, ++ { 0x1000db0b, 0x0b }, ++ { 0x1000db0c, 0x11 }, ++ { 0x1000db0d, 0x00 }, ++ { 0x1000db0e, 0x00 }, ++ { 0x1000db0f, 0x00 }, ++ { 0x1000db10, 0x2c }, ++ { 0x1000db11, 0xfa }, ++ { 0x1000db12, 0x00 }, ++ { 0x1000db13, 0x00 }, ++ { 0x1000db14, 0x09 }, ++ { 0x0000d540, 0x21 }, ++ { 0x0000d714, 0x17 }, ++ { 0x0000dd0b, 0x0d }, ++ { 0x0000dd0a, 0xff }, ++ { 0x0000dd09, 0x0d }, ++ { 0x0000dd08, 0xff }, ++ { 0x0000c5fb, 0x12 }, ++ { 0x0000c570, 0x08 }, ++}; ++ + static const struct reg_default rt1320_reg_defaults[] = { + { SDW_SDCA_CTL(FUNC_NUM_MIC, RT1320_SDCA_ENT_PDE11, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x03 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC, RT1320_SDCA_ENT_FU113, RT1320_SDCA_CTL_FU_MUTE, CH_01), 0x01 }, +@@ -440,6 +836,7 @@ static const struct reg_default rt1320_mbq_defaults[] = { + static bool rt1320_readable_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0xc000 ... 0xc086: + case 0xc400 ... 0xc409: + case 0xc480 ... 0xc48f: +@@ -497,10 +894,15 @@ static bool rt1320_readable_register(struct device *dev, unsigned int reg) + case RT1321_PATCH_MAIN_VER ... RT1321_PATCH_BETA_VER: + case 0x1000f008: + case 0x1000f021: +- case 0x2000300f: ++ case 0x20003000 ... 0x2000300f: + case 0x2000301c: +- case 0x2000900f: ++ case 0x20003040 ... 0x20003059: ++ case 0x20005800 ... 0x2000580f: ++ case 0x20005818: ++ case 0x20005840 ... 0x20005859: ++ case 0x20009000 ... 0x2000900f: + case 0x20009018: ++ case 0x20009040 ... 0x20009059: + case 0x3fc000c0 ... 0x3fc2dfc8: + case 0x3fe00000 ... 0x3fe36fff: + /* 0x40801508/0x40801809/0x4080180a/0x40801909/0x4080190a */ +@@ -545,6 +947,7 @@ static bool rt1320_readable_register(struct device *dev, unsigned int reg) + static bool rt1320_volatile_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0xc000: + case 0xc003: + case 0xc081: +@@ -596,15 +999,20 @@ static bool rt1320_volatile_register(struct device *dev, unsigned int reg) + case 0x1000c000 ... 0x1000dfff: + case 0x1000f008: + case 0x1000f021: +- case 0x2000300f: ++ case 0x2000300e ... 0x2000300f: + case 0x2000301c: +- case 0x2000900f: ++ case 0x20003040 ... 0x20003059: ++ case 0x2000580e ... 0x2000580f: ++ case 0x20005818: ++ case 0x20005840 ... 0x20005859: ++ case 0x2000900e ... 0x2000900f: + case 0x20009018: ++ case 0x20009040 ... 0x20009059: + case 0x3fc2ab80 ... 0x3fc2ac4c: + case 0x3fc2b780: + case 0x3fc2bf80 ... 0x3fc2bf83: + case 0x3fc2bfc0 ... 0x3fc2bfc8: +- case 0x3fc2d300 ... 0x3fc2d354: ++ case 0x3fc2d300 ... 0x3fc2d3cc: + case 0x3fc2dfc0 ... 0x3fc2dfc8: + case 0x3fe2e000 ... 0x3fe2e003: + case SDW_SDCA_CTL(FUNC_NUM_MIC, RT1320_SDCA_ENT_PDE11, RT1320_SDCA_CTL_ACTUAL_POWER_STATE, 0): +@@ -892,6 +1300,42 @@ static int rt1320_check_fw_ready(struct rt1320_sdw_priv *rt1320) + return 0; + } + ++static int rt1320_dspfw_status(struct rt1320_sdw_priv *rt1320) ++{ ++ struct device *dev = &rt1320->sdw_slave->dev; ++ unsigned int fw_status_addr, fw_ready; ++ unsigned int dspfw_run; ++ ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ fw_status_addr = RT1320_DSPFW_STATUS_ADDR; ++ break; ++ case RT1321_DEV_ID: ++ fw_status_addr = RT1321_DSPFW_STATUS_ADDR; ++ break; ++ default: ++ dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); ++ return -EINVAL; ++ } ++ ++ regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); ++ fw_ready &= 0x1; ++ ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ regmap_read(rt1320->regmap, 0xf01e, &dspfw_run); ++ dspfw_run &= 0x1; ++ fw_ready = (!dspfw_run && fw_ready); ++ } ++ ++ if (fw_ready) { ++ dev_dbg(dev, "%s, DSP FW was already\n", __func__); ++ return 1; ++ } ++ ++ dev_dbg(dev, "%s, DSP FW is NOT ready. Please load DSP FW first\n", __func__); ++ return 0; ++} ++ + static int rt1320_check_power_state_ready(struct rt1320_sdw_priv *rt1320, enum rt1320_power_state ps) + { + struct device *dev = &rt1320->sdw_slave->dev; +@@ -982,6 +1426,9 @@ static int rt1320_fw_param_protocol(struct rt1320_sdw_priv *rt1320, enum rt1320_ + if (!tempbuf) + return -ENOMEM; + ++ if (rt1320->dev_id == RT1321_DEV_ID && rt1320->version_id == RT1321_VA2) ++ paramid += 0x0bff0000; ++ + paramhr.moudleid = 1; + paramhr.commandtype = cmdid; + /* 8 is "sizeof(paramid) + sizeof(paramlength)" */ +@@ -1120,45 +1567,73 @@ static void rt1320_calc_r0(struct rt1320_sdw_priv *rt1320) + l_calir0, l_calir0_lo, r_calir0, r_calir0_lo); + } + ++static int rt1320_pilot_tone_output(struct rt1320_sdw_priv *rt1320) ++{ ++ struct device *dev = &rt1320->sdw_slave->dev; ++ int l_targetpostgain, r_targetpostgain; ++ unsigned long long factor = (1 << 12); ++ int l_pilotgain[9], r_pilotgain[9]; ++ const int postgain_step = 234; ++ int targetGain; ++ ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ targetGain = -320000; ++ break; ++ case RT1321_DEV_ID: ++ targetGain = -420000; ++ if (rt1320->version_id == RT1321_VA0) ++ targetGain = -320000; ++ break; ++ default: ++ dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); ++ return -EINVAL; ++ } ++ ++ rt1320_fw_param_protocol(rt1320, RT1320_GET_PARAM, 70, &l_pilotgain[0], sizeof(l_pilotgain)); ++ rt1320_fw_param_protocol(rt1320, RT1320_GET_PARAM, 71, &r_pilotgain[0], sizeof(r_pilotgain)); ++ dev_dbg(dev, "%s, LR pilotgain %d, %d\n", __func__, l_pilotgain[2], r_pilotgain[2]); ++ ++ /* calculate pilot tone gain */ ++ l_pilotgain[2] = (l_pilotgain[2] * 10000) / factor; ++ r_pilotgain[2] = (r_pilotgain[2] * 10000) / factor; ++ ++ /* calculate post gain to meet target gain */ ++ l_targetpostgain = abs(targetGain - l_pilotgain[2]) / postgain_step; ++ r_targetpostgain = abs(targetGain - r_pilotgain[2]) / postgain_step; ++ l_targetpostgain = 0xfff - l_targetpostgain; ++ r_targetpostgain = 0xfff - r_targetpostgain; ++ dev_dbg(dev, "%s, LR targetpostgain=0x%x, 0x%x\n", __func__, l_targetpostgain, r_targetpostgain); ++ ++ regmap_write(rt1320->regmap, 0xdd0b, (l_targetpostgain & 0xf00) >> 8); ++ regmap_write(rt1320->regmap, 0xdd0a, l_targetpostgain & 0xff); ++ regmap_write(rt1320->regmap, 0xdd09, (r_targetpostgain & 0xf00) >> 8); ++ regmap_write(rt1320->regmap, 0xdd08, r_targetpostgain & 0xff); ++ ++ return 0; ++} ++ + static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + { + struct device *dev = &rt1320->sdw_slave->dev; + struct rt1320_datafixpoint audfixpoint[2]; + unsigned int reg_c5fb, reg_c570, reg_cd00; +- unsigned int vol_reg[4], fw_ready; ++ unsigned int vol_reg[4]; + unsigned long long l_meanr0, r_meanr0; +- unsigned int fw_status_addr; + int l_re[5], r_re[5]; + int ret, tmp; + unsigned long long factor = (1 << 27); + unsigned short l_advancegain, r_advancegain; + unsigned int delay_s = 7; /* delay seconds for the calibration */ ++ int dspfw_status; + + if (!rt1320->component) + return; + +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return; +- } +- +- /* set volume 0dB */ + regmap_read(rt1320->regmap, 0xdd0b, &vol_reg[3]); + regmap_read(rt1320->regmap, 0xdd0a, &vol_reg[2]); + regmap_read(rt1320->regmap, 0xdd09, &vol_reg[1]); + regmap_read(rt1320->regmap, 0xdd08, &vol_reg[0]); +- regmap_write(rt1320->regmap, 0xdd0b, 0x0f); +- regmap_write(rt1320->regmap, 0xdd0a, 0xff); +- regmap_write(rt1320->regmap, 0xdd09, 0x0f); +- regmap_write(rt1320->regmap, 0xdd08, 0xff); +- + regmap_read(rt1320->regmap, 0xc5fb, ®_c5fb); + regmap_read(rt1320->regmap, 0xc570, ®_c570); + regmap_read(rt1320->regmap, 0xcd00, ®_cd00); +@@ -1171,9 +1646,8 @@ static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + goto _finish_; + } + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (!fw_ready) { ++ dspfw_status = rt1320_dspfw_status(rt1320); ++ if (dspfw_status <= 0) { + dev_dbg(dev, "%s, DSP FW is NOT ready. Please load DSP FW first\n", __func__); + goto _finish_; + } +@@ -1184,8 +1658,19 @@ static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + goto _finish_; + } + +- if (rt1320->dev_id == RT1320_DEV_ID) +- regmap_write(rt1320->regmap, 0xc5fb, 0x00); ++ /* fine tune pilot tone output */ ++ ret = rt1320_pilot_tone_output(rt1320); ++ if (ret < 0) { ++ dev_dbg(dev, "%s, Failed to tune pilot tone output\n", __func__); ++ goto _finish_; ++ } ++ ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x00); ++ regmap_write(rt1320->regmap, 0xc5c4, 0x12); ++ } ++ ++ regmap_write(rt1320->regmap, 0xc5fb, 0x00); + regmap_write(rt1320->regmap, 0xc570, 0x0b); + regmap_write(rt1320->regmap, 0xcd00, 0xc5); + +@@ -1246,6 +1731,11 @@ static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x03); + rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x03); + ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x80); ++ regmap_write(rt1320->regmap, 0xc5c4, 0x10); ++ } ++ + /* advance gain will be set when R0 load, not here */ + regmap_write(rt1320->regmap, 0xdd0b, vol_reg[3]); + regmap_write(rt1320->regmap, 0xdd0a, vol_reg[2]); +@@ -1296,7 +1786,7 @@ static int rt1320_r0_cali_put(struct snd_kcontrol *kcontrol, + static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) + { + struct sdw_slave *slave = rt1320->sdw_slave; +- const struct firmware *patch; ++ const struct firmware *patch __free(firmware) = NULL; + const char *filename; + unsigned int addr, val, min_addr, max_addr; + const unsigned char *ptr; +@@ -1312,6 +1802,9 @@ static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) + max_addr = 0x10007fff; + break; + case RT1321_DEV_ID: ++ if (rt1320->version_id == RT1321_VA2) ++ return; ++ + filename = RT1321_VA_MCU_PATCH; + min_addr = 0x10008000; + max_addr = 0x10008fff; +@@ -1347,17 +1840,15 @@ static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) + + if (addr > max_addr || addr < min_addr) { + dev_err(&slave->dev, "%s: the address 0x%x is wrong", __func__, addr); +- goto _exit_; ++ return; + } + if (val > 0xff) { + dev_err(&slave->dev, "%s: the value 0x%x is wrong", __func__, val); +- goto _exit_; ++ return; + } + regmap_write(rt1320->regmap, addr, val); + } + } +-_exit_: +- release_firmware(patch); + } + } + +@@ -1382,30 +1873,17 @@ static void rt1320_vab_preset(struct rt1320_sdw_priv *rt1320) + static void rt1320_t0_load(struct rt1320_sdw_priv *rt1320, unsigned int l_t0, unsigned int r_t0) + { + struct device *dev = &rt1320->sdw_slave->dev; +- unsigned int factor = (1 << 22), fw_ready; ++ unsigned int factor = (1 << 22); + int l_t0_data[38], r_t0_data[38]; +- unsigned int fw_status_addr; +- +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return; +- } ++ int dspfw_status; + + regmap_write(rt1320->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, + RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x00); + rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x00); + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (!fw_ready) { ++ dspfw_status = rt1320_dspfw_status(rt1320); ++ if (dspfw_status <= 0) { + dev_warn(dev, "%s, DSP FW is NOT ready\n", __func__); + goto _exit_; + } +@@ -1444,7 +1922,7 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + struct device *dev = &rt1320->sdw_slave->dev; + static const char func_tag[] = "FUNC"; + static const char xu_tag[] = "XU"; +- const struct firmware *rae_fw = NULL; ++ const struct firmware *rae_fw __free(firmware) = NULL; + unsigned int fw_offset; + unsigned char *fw_data; + unsigned char *param_data; +@@ -1483,22 +1961,46 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + request_firmware(&rae_fw, rae_filename, dev); + if (rae_fw) { + +- /* RAE CRC clear */ +- regmap_write(rt1320->regmap, 0xe80b, 0x0f); +- +- /* RAE stop & CRC disable */ +- regmap_update_bits(rt1320->regmap, 0xe803, 0xbc, 0x00); +- +- while (--retry) { +- regmap_read(rt1320->regmap, 0xe83f, &value); +- if (value & 0x40) +- break; +- usleep_range(1000, 1100); +- } +- if (!retry && !(value & 0x40)) { +- dev_err(dev, "%s: RAE is not ready to load\n", __func__); +- release_firmware(rae_fw); +- return -ETIMEDOUT; ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ /* RAE CRC clear */ ++ regmap_write(rt1320->regmap, 0xe80b, 0x0f); ++ /* RAE stop & CRC disable */ ++ regmap_update_bits(rt1320->regmap, 0xe803, 0xbc, 0x00); ++ while (--retry) { ++ regmap_read(rt1320->regmap, 0xe83f, &value); ++ if (value & 0x40) ++ break; ++ usleep_range(1000, 1100); ++ } ++ if (!retry && !(value & 0x40)) { ++ dev_err(dev, "%s: RAE is not ready to load\n", __func__); ++ return -ETIMEDOUT; ++ } ++ break; ++ case RT1321_DEV_ID: ++ /* RAE CRC clear */ ++ regmap_write(rt1320->regmap, 0x2000300e, 0xc0); ++ regmap_write(rt1320->regmap, 0x2000300f, 0x0f); ++ /* RAE stop & Phase sync & CRC disable */ ++ regmap_update_bits(rt1320->regmap, 0x20003003, 0xfe, 0x00); ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x01, 0x00); ++ /* check whether write state is ready */ ++ while (--retry) { ++ regmap_read(rt1320->regmap, 0x20003043, &value); ++ if (value & 0x40) ++ break; ++ usleep_range(1000, 1100); ++ } ++ if (!retry && !(value & 0x40)) { ++ dev_err(dev, "%s: RAE is not ready to load\n", __func__); ++ return -ETIMEDOUT; ++ } ++ break; ++ default: ++ dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); ++ return -EINVAL; + } + + dev_dbg(dev, "%s, rae_fw size=0x%zx\n", __func__, rae_fw->size); +@@ -1551,7 +2053,6 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + } + + regcache_cache_bypass(rt1320->regmap, false); +- release_firmware(rae_fw); + + } else { + dev_err(dev, "%s: Failed to load %s firmware\n", __func__, rae_filename); +@@ -1559,18 +2060,34 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + goto _exit_; + } + +- /* RAE CRC enable */ +- regmap_update_bits(rt1320->regmap, 0xe803, 0x0c, 0x0c); +- +- /* RAE update */ +- regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x00); +- regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x80); +- +- /* RAE run */ +- regmap_update_bits(rt1320->regmap, 0xe803, 0x80, 0x80); +- +- regmap_read(rt1320->regmap, 0xe80b, &value); +- dev_dbg(dev, "%s: CAE run => 0xe80b reg = 0x%x\n", __func__, value); ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ /* RAE CRC enable */ ++ regmap_update_bits(rt1320->regmap, 0xe803, 0x0c, 0x0c); ++ /* RAE update */ ++ regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x80); ++ /* RAE run */ ++ regmap_update_bits(rt1320->regmap, 0xe803, 0x80, 0x80); ++ regmap_read(rt1320->regmap, 0xe80b, &value); ++ dev_dbg(dev, "%s: CAE run => 0xe80b reg = 0x%x\n", __func__, value); ++ break; ++ case RT1321_DEV_ID: ++ /* RAE CRC enable */ ++ regmap_update_bits(rt1320->regmap, 0x20003003, 0x30, 0x30); ++ /* RAE update */ ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x80, 0x80); ++ regmap_update_bits(rt1320->regmap, 0x20009018, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x20009018, 0x80, 0x80); ++ regmap_update_bits(rt1320->regmap, 0x20005818, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x20005818, 0x80, 0x80); ++ /* RAE run */ ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x01, 0x01); ++ /* Phase sync eanble */ ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x80); ++ break; ++ } + + rt1320->rae_update_done = true; + +@@ -1598,32 +2115,21 @@ struct rt1320_dspfwheader { + + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(rt1320->component); + struct device *dev = &rt1320->sdw_slave->dev; +- unsigned int val, i, fw_offset, fw_ready; +- unsigned int fw_status_addr; ++ unsigned int val, i, fw_offset; + struct rt1320_dspfwheader *fwheader; + struct rt1320_imageinfo *ptr_img; + struct sdw_bpt_section sec[10]; +- const struct firmware *fw = NULL; ++ const struct firmware *fw __free(firmware) = NULL; + unsigned char *fw_data; + bool dev_fw_match = false; + static const char hdr_sig[] = "AFX"; + unsigned int hdr_size = 0; + const char *dmi_vendor, *dmi_product, *dmi_sku; + int len_vendor, len_product, len_sku; ++ unsigned char boot_mode = 0; /* 0: from RAM; 1: from ROM */ ++ unsigned char has_0x3fc00000 = 0; + char filename[512]; + +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return; +- } +- + dmi_vendor = dmi_get_system_info(DMI_SYS_VENDOR); + dmi_product = dmi_get_system_info(DMI_PRODUCT_NAME); + dmi_sku = dmi_get_system_info(DMI_PRODUCT_SKU); +@@ -1653,17 +2159,12 @@ struct rt1320_dspfwheader { + RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x00); + rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x00); + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (fw_ready) { ++ if (rt1320_dspfw_status(rt1320)) { + dev_dbg(dev, "%s, DSP FW was already\n", __func__); + rt1320->fw_load_done = true; + goto _exit_; + } + +- /* change to IRAM */ +- regmap_update_bits(rt1320->regmap, 0xf01e, 0x80, 0x00); +- + request_firmware(&fw, filename, dev); + if (fw) { + fwheader = (struct rt1320_dspfwheader *)fw->data; +@@ -1672,7 +2173,6 @@ struct rt1320_dspfwheader { + + if (fwheader->sync != 0x0a1c5679) { + dev_err(dev, "%s: FW sync error\n", __func__); +- release_firmware(fw); + goto _exit_; + } + +@@ -1709,9 +2209,11 @@ struct rt1320_dspfwheader { + dev_fw_match = true; + break; + case RT1321_DEV_ID: +- if (ptr_img->addr == 0x3fc00000) +- if (fw_data[9] == '1') ++ if (ptr_img->addr == 0x3fc00000) { ++ if (fw_data[7] == '1') + dev_fw_match = true; ++ has_0x3fc00000 = 1; ++ } + break; + default: + dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +@@ -1721,6 +2223,22 @@ struct rt1320_dspfwheader { + fw_offset += ptr_img->size; + } + ++ if (rt1320->dev_id == RT1321_DEV_ID && rt1320->version_id == RT1321_VA2) { ++ /* ++ * For 1321 VA2, if the FW doesn't include the section for address 0x3fc00000, ++ * it means the FW will boot from ROM and force dev_fw_match to true to download FW by BRA. ++ */ ++ if (!has_0x3fc00000) { ++ boot_mode = 1; ++ dev_fw_match = true; ++ } ++ dev_dbg(dev, "%s: Boot from %s for VA2\n", __func__, (boot_mode ? "ROM" : "RAM")); ++ } ++ ++ /* change to IRAM */ ++ if (!boot_mode) ++ regmap_update_bits(rt1320->regmap, 0xf01e, 0x80, 0x00); ++ + if (dev_fw_match) { + dev_dbg(dev, "%s, starting BRA downloading FW..\n", __func__); + rt1320->bra_msg.dev_num = rt1320->sdw_slave->dev_num; +@@ -1732,7 +2250,6 @@ struct rt1320_dspfwheader { + } + + regcache_cache_bypass(rt1320->regmap, false); +- release_firmware(fw); + + if (!dev_fw_match) { + dev_err(dev, "%s: FW file doesn't match to device\n", __func__); +@@ -1743,24 +2260,39 @@ struct rt1320_dspfwheader { + goto _exit_; + } + +- /* run RAM code */ +- regmap_read(rt1320->regmap, 0x3fc2bfc0, &val); +- val |= 0x8; +- regmap_write(rt1320->regmap, 0x3fc2bfc0, val); +- +- /* clear frame counter */ + switch (rt1320->dev_id) { + case RT1320_DEV_ID: ++ /* run RAM code */ ++ regmap_read(rt1320->regmap, 0x3fc2bfc0, &val); ++ val |= 0x8; ++ regmap_write(rt1320->regmap, 0x3fc2bfc0, val); ++ /* clear frame counter */ + regmap_write(rt1320->regmap, 0x3fc2bfcb, 0x00); + regmap_write(rt1320->regmap, 0x3fc2bfca, 0x00); + regmap_write(rt1320->regmap, 0x3fc2bfc9, 0x00); + regmap_write(rt1320->regmap, 0x3fc2bfc8, 0x00); + break; + case RT1321_DEV_ID: ++ if (!boot_mode) { ++ /* run RAM code */ ++ regmap_read(rt1320->regmap, 0x3fc2dfc0, &val); ++ val |= 0x8; ++ regmap_write(rt1320->regmap, 0x3fc2dfc0, val); ++ } ++ /* clear frame counter */ + regmap_write(rt1320->regmap, 0x3fc2dfcb, 0x00); + regmap_write(rt1320->regmap, 0x3fc2dfca, 0x00); + regmap_write(rt1320->regmap, 0x3fc2dfc9, 0x00); + regmap_write(rt1320->regmap, 0x3fc2dfc8, 0x00); ++ /* enable handshake */ ++ regmap_write(rt1320->regmap, 0x3fc2dfc4, 0x00); ++ regmap_write(rt1320->regmap, 0xd470, 0xad); ++ /* minimum phase settings */ ++ regmap_write(rt1320->regmap, 0xc5c4, 0x10); ++ regmap_write(rt1320->regmap, 0x20003003, 0x31); ++ regmap_update_bits(rt1320->regmap, 0x20003002, 0x40, 0x00); ++ regmap_write(rt1320->regmap, 0xc5b3, 0x01); ++ regmap_write(rt1320->regmap, 0xc052, 0x11); + break; + } + +@@ -1843,14 +2375,35 @@ static void rt1320_vc_preset(struct rt1320_sdw_priv *rt1320) + + static void rt1321_preset(struct rt1320_sdw_priv *rt1320) + { ++ const struct reg_sequence *blindwrite; + unsigned int i, reg, val, delay; ++ unsigned int array_size; + +- for (i = 0; i < ARRAY_SIZE(rt1321_blind_write); i++) { +- reg = rt1321_blind_write[i].reg; +- val = rt1321_blind_write[i].def; +- delay = rt1321_blind_write[i].delay_us; ++ switch (rt1320->version_id) { ++ case RT1321_VA0: ++ blindwrite = rt1321_blind_write; ++ array_size = ARRAY_SIZE(rt1321_blind_write); ++ break; ++ case RT1321_VA1: ++ blindwrite = rt1321_va1_blind_write; ++ array_size = ARRAY_SIZE(rt1321_va1_blind_write); ++ break; ++ case RT1321_VA2: ++ blindwrite = rt1321_va2_blind_write; ++ array_size = ARRAY_SIZE(rt1321_va2_blind_write); ++ break; ++ default: ++ dev_err(&rt1320->sdw_slave->dev, "%s: Unknown version ID %d\n", ++ __func__, rt1320->version_id); ++ return; ++ } + +- if (reg == 0x3fc2dfc3) ++ for (i = 0; i < array_size; i++) { ++ reg = blindwrite[i].reg; ++ val = blindwrite[i].def; ++ delay = blindwrite[i].delay_us; ++ ++ if (reg == 0x1000cd56) + rt1320_load_mcu_patch(rt1320); + + regmap_write(rt1320->regmap, reg, val); +@@ -1892,6 +2445,24 @@ static int rt1320_io_init(struct device *dev, struct sdw_slave *slave) + regmap_read(rt1320->regmap, RT1320_DEV_ID_0, &val); + regmap_read(rt1320->regmap, RT1320_DEV_ID_1, &tmp); + rt1320->dev_id = (val << 8) | tmp; ++ ++ /* This is a workaround that reads the value twice to obtain the correct result. */ ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_0, &val); ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_1, &tmp); ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_0, &val); ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_1, &tmp); ++ val = (val << 8) | tmp; ++ ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ if (rt1320->version_id == 0x01) ++ rt1320->version_id = RT1321_VA2; ++ else if (val == RT1321_DEV_HV_VA0_ID) ++ rt1320->version_id = RT1321_VA0; ++ else if (val == RT1321_DEV_HV_VA1_ID) ++ rt1320->version_id = RT1321_VA1; ++ else ++ dev_err(dev, "%s: Unknown version ID 0x%x for RT1321\n", __func__, rt1320->version_id); ++ } + } + + regmap_read(rt1320->regmap, +@@ -2326,25 +2897,12 @@ static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -1725, 75, 0); + static int rt1320_r0_load(struct rt1320_sdw_priv *rt1320) + { + struct device *dev = regmap_get_device(rt1320->regmap); +- unsigned int fw_status_addr; +- unsigned int fw_ready; ++ int dspfw_status; + int ret = 0; + + if (!rt1320->r0_l_reg || !rt1320->r0_r_reg) + return -EINVAL; + +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return -EINVAL; +- } +- + regmap_write(rt1320->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x00); + ret = rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x00); +@@ -2353,9 +2911,8 @@ static int rt1320_r0_load(struct rt1320_sdw_priv *rt1320) + goto _timeout_; + } + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (!fw_ready) { ++ dspfw_status = rt1320_dspfw_status(rt1320); ++ if (dspfw_status <= 0) { + dev_dbg(dev, "%s, DSP FW is NOT ready\n", __func__); + goto _timeout_; + } +@@ -2458,7 +3015,7 @@ static int rt1320_dspfw_load_put(struct snd_kcontrol *kcontrol, + if (!rt1320->hw_init) + return 0; + +- ret = pm_runtime_resume(component->dev); ++ ret = pm_runtime_resume_and_get(component->dev); + if (ret < 0 && ret != -EACCES) + return ret; + +@@ -2469,6 +3026,8 @@ static int rt1320_dspfw_load_put(struct snd_kcontrol *kcontrol, + if (!ucontrol->value.integer.value[0]) + rt1320->fw_load_done = false; + ++ pm_runtime_mark_last_busy(component->dev); ++ pm_runtime_put_autosuspend(component->dev); + return 0; + } + +@@ -2819,6 +3378,37 @@ static int rt1320_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + return 0; + } + ++static int rt1320_bus_config(struct sdw_slave *slave, ++ struct sdw_bus_params *params) ++{ ++ struct rt1320_sdw_priv *rt1320 = dev_get_drvdata(&slave->dev); ++ unsigned int clk_base; ++ ++ regmap_read(rt1320->regmap, 0x004d, &clk_base); ++ dev_dbg(&rt1320->sdw_slave->dev, "%s clk_base=%x", __func__, clk_base); ++ ++ switch (clk_base) { ++ case RT1320_CLK_FREQ_19_2_MHZ: ++ case RT1320_CLK_FREQ_24MHZ: ++ regmap_write(rt1320->regmap, 0xc600, 0x04); ++ regmap_write(rt1320->regmap, 0xc601, 0x83); ++ regmap_write(rt1320->regmap, 0xc602, 0x1f); ++ regmap_write(rt1320->regmap, 0xc603, 0x40); ++ break; ++ case RT1320_CLK_FREQ_24_576MHZ: ++ case RT1320_CLK_FREQ_22_5792MHZ: ++ regmap_write(rt1320->regmap, 0xc600, 0x07); ++ regmap_write(rt1320->regmap, 0xc601, 0x80); ++ regmap_write(rt1320->regmap, 0xc602, 0x0f); ++ regmap_write(rt1320->regmap, 0xc603, 0x40); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ + /* + * slave_ops: callbacks for get_clock_stop_mode, clock_stop and + * port_prep are not defined for now +@@ -2826,12 +3416,13 @@ static int rt1320_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + static const struct sdw_slave_ops rt1320_slave_ops = { + .read_prop = rt1320_read_prop, + .update_status = rt1320_update_status, ++ .bus_config = rt1320_bus_config, + }; + + static int rt1320_sdw_component_probe(struct snd_soc_component *component) + { +- int ret; + struct rt1320_sdw_priv *rt1320 = snd_soc_component_get_drvdata(component); ++ int ret; + + rt1320->component = component; + +@@ -3062,10 +3653,23 @@ static int rt1320_dev_resume(struct device *dev) + return ret; + + regcache_cache_only(rt1320->regmap, false); +- regcache_sync(rt1320->regmap); ++ ret = regcache_sync(rt1320->regmap); ++ if (ret) ++ goto err_sync; ++ + regcache_cache_only(rt1320->mbq_regmap, false); +- regcache_sync(rt1320->mbq_regmap); ++ ret = regcache_sync(rt1320->mbq_regmap); ++ if (ret) ++ goto err_sync; ++ + return 0; ++ ++err_sync: ++ regcache_cache_only(rt1320->regmap, true); ++ regcache_cache_only(rt1320->mbq_regmap, true); ++ regcache_mark_dirty(rt1320->regmap); ++ regcache_mark_dirty(rt1320->mbq_regmap); ++ return ret; + } + + static const struct dev_pm_ops rt1320_pm = { +diff --git a/sound/soc/codecs/rt1320-sdw.h b/sound/soc/codecs/rt1320-sdw.h +--- a/sound/soc/codecs/rt1320-sdw.h ++++ b/sound/soc/codecs/rt1320-sdw.h +@@ -17,12 +17,17 @@ + + #define RT1320_DEV_ID 0x6981 + #define RT1321_DEV_ID 0x7045 ++#define RT1321_DEV_HV_VA0_ID 0x6997 ++#define RT1321_DEV_HV_VA1_ID 0x7071 + + /* imp-defined registers */ + #define RT1320_DEV_VERSION_ID_1 0xc404 + #define RT1320_DEV_ID_1 0xc405 + #define RT1320_DEV_ID_0 0xc406 + ++#define RT1320_HV_DEV_ID_0 0xf622 ++#define RT1320_HV_DEV_ID_1 0xf623 ++ + #define RT1320_POWER_STATE 0xc560 + + #define RT1321_PATCH_MAIN_VER 0x1000cffe +@@ -94,6 +99,12 @@ enum rt1320_version_id { + RT1320_VC, + }; + ++enum rt1321_version_id { ++ RT1321_VA0, ++ RT1321_VA1, ++ RT1321_VA2, ++}; ++ + #define RT1320_VER_B_ID 0x07392238 + #define RT1320_VAB_MCU_PATCH "realtek/rt1320/rt1320-patch-code-vab.bin" + #define RT1320_VC_MCU_PATCH "realtek/rt1320/rt1320-patch-code-vc.bin" +@@ -121,6 +132,7 @@ struct rt1320_datafixpoint { + int invrs; + }; + ++/* FW parameter id 1300 */ + typedef struct FwPara_HwSwGain { + unsigned int SwAdvGain; + unsigned int SwBasGain; +@@ -163,6 +175,13 @@ enum rt1320_rw_type { + RT1320_PARAM_READ = 3, + }; + ++enum { ++ RT1320_CLK_FREQ_19_2_MHZ = 1, ++ RT1320_CLK_FREQ_24MHZ = 2, ++ RT1320_CLK_FREQ_24_576MHZ = 3, ++ RT1320_CLK_FREQ_22_5792MHZ = 4, ++}; ++ + struct rt1320_sdw_priv { + struct snd_soc_component *component; + struct regmap *regmap; +diff --git a/sound/soc/codecs/rt5514-spi.c b/sound/soc/codecs/rt5514-spi.c +--- a/sound/soc/codecs/rt5514-spi.c ++++ b/sound/soc/codecs/rt5514-spi.c +@@ -6,6 +6,7 @@ + * Author: Oder Chiou + */ + ++#include + #include + #include + #include +@@ -79,17 +80,17 @@ static void rt5514_spi_copy_work(struct work_struct *work) + unsigned int cur_wp, remain_data; + u8 buf[8]; + +- mutex_lock(&rt5514_dsp->dma_lock); ++ guard(mutex)(&rt5514_dsp->dma_lock); + if (!rt5514_dsp->substream) { + dev_err(rt5514_dsp->dev, "No pcm substream\n"); +- goto done; ++ return; + } + + runtime = rt5514_dsp->substream->runtime; + period_bytes = snd_pcm_lib_period_bytes(rt5514_dsp->substream); + if (!period_bytes) { + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +- goto done; ++ return; + } + + if (rt5514_dsp->buf_size % period_bytes) +@@ -111,7 +112,7 @@ static void rt5514_spi_copy_work(struct work_struct *work) + + if (remain_data < period_bytes) { + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +- goto done; ++ return; + } + } + +@@ -146,9 +147,6 @@ static void rt5514_spi_copy_work(struct work_struct *work) + snd_pcm_period_elapsed(rt5514_dsp->substream); + + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +- +-done: +- mutex_unlock(&rt5514_dsp->dma_lock); + } + + static void rt5514_schedule_copy(struct rt5514_dsp *rt5514_dsp) +@@ -216,7 +214,7 @@ static int rt5514_spi_hw_params(struct snd_soc_component *component, + snd_soc_component_get_drvdata(component); + u8 buf[8]; + +- mutex_lock(&rt5514_dsp->dma_lock); ++ guard(mutex)(&rt5514_dsp->dma_lock); + rt5514_dsp->substream = substream; + rt5514_dsp->dma_offset = 0; + +@@ -225,8 +223,6 @@ static int rt5514_spi_hw_params(struct snd_soc_component *component, + if (buf[0] & RT5514_IRQ_STATUS_BIT) + rt5514_schedule_copy(rt5514_dsp); + +- mutex_unlock(&rt5514_dsp->dma_lock); +- + return 0; + } + +@@ -236,9 +232,8 @@ static int rt5514_spi_hw_free(struct snd_soc_component *component, + struct rt5514_dsp *rt5514_dsp = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5514_dsp->dma_lock); +- rt5514_dsp->substream = NULL; +- mutex_unlock(&rt5514_dsp->dma_lock); ++ scoped_guard(mutex, &rt5514_dsp->dma_lock) ++ rt5514_dsp->substream = NULL; + + cancel_delayed_work_sync(&rt5514_dsp->copy_work); + +diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c +--- a/sound/soc/codecs/rt5514.c ++++ b/sound/soc/codecs/rt5514.c +@@ -1073,12 +1073,18 @@ static int rt5514_set_bias_level(struct snd_soc_component *component, + * settings to make sure recording properly. + */ + if (rt5514->dsp_enabled) { +- rt5514->dsp_enabled = 0; +- regmap_multi_reg_write(rt5514->i2c_regmap, +- rt5514_i2c_patch, +- ARRAY_SIZE(rt5514_i2c_patch)); ++ ret = regmap_multi_reg_write(rt5514->i2c_regmap, ++ rt5514_i2c_patch, ++ ARRAY_SIZE(rt5514_i2c_patch)); ++ if (ret) ++ return ret; ++ + regcache_mark_dirty(rt5514->regmap); +- regcache_sync(rt5514->regmap); ++ ret = regcache_sync(rt5514->regmap); ++ if (ret) ++ return ret; ++ ++ rt5514->dsp_enabled = 0; + } + } + break; +diff --git a/sound/soc/codecs/rt5575-spi.c b/sound/soc/codecs/rt5575-spi.c +--- a/sound/soc/codecs/rt5575-spi.c ++++ b/sound/soc/codecs/rt5575-spi.c +@@ -93,7 +93,6 @@ static void rt5575_spi_burst_write(struct spi_device *spi, u32 addr, const u8 *t + int rt5575_spi_fw_load(struct spi_device *spi) + { + struct device *dev = &spi->dev; +- const struct firmware *firmware; + int i, ret; + static const char * const fw_path[] = { + "realtek/rt5575/rt5575_fw1.bin", +@@ -104,6 +103,7 @@ int rt5575_spi_fw_load(struct spi_device *spi) + static const u32 fw_addr[] = { 0x5f400000, 0x5f600000, 0x5f7fe000, 0x5f7ff000 }; + + for (i = 0; i < ARRAY_SIZE(fw_addr); i++) { ++ const struct firmware *firmware __free(firmware) = NULL; + ret = request_firmware(&firmware, fw_path[i], dev); + if (ret) { + dev_err(dev, "Request firmware failure: %d\n", ret); +@@ -111,7 +111,6 @@ int rt5575_spi_fw_load(struct spi_device *spi) + } + + rt5575_spi_burst_write(spi, fw_addr[i], firmware->data, firmware->size); +- release_firmware(firmware); + } + + return 0; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -6,6 +6,7 @@ + * Author: Bard Liao + */ + ++#include + #include + #include + #include +@@ -3323,92 +3324,89 @@ static void rt5645_jack_detect_work(struct work_struct *work) + if (!rt5645->component) + return; + +- mutex_lock(&rt5645->jd_mutex); +- +- switch (rt5645->pdata.jd_mode) { +- case 0: /* Not using rt5645 JD */ +- if (rt5645->gpiod_hp_det) { +- gpio_state = gpiod_get_value(rt5645->gpiod_hp_det); +- if (rt5645->pdata.inv_hp_pol) +- gpio_state ^= 1; +- dev_dbg(rt5645->component->dev, "gpio_state = %d\n", +- gpio_state); +- report = rt5645_jack_detect(rt5645->component, gpio_state); +- } +- snd_soc_jack_report(rt5645->hp_jack, +- report, SND_JACK_HEADPHONE); +- snd_soc_jack_report(rt5645->mic_jack, +- report, SND_JACK_MICROPHONE); +- mutex_unlock(&rt5645->jd_mutex); +- return; +- case 4: +- val = snd_soc_component_read(rt5645->component, RT5645_A_JD_CTRL1) & 0x0020; +- break; +- default: /* read rt5645 jd1_1 status */ +- val = snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x1000; +- break; +- +- } +- +- if (!val && (rt5645->jack_type == 0)) { /* jack in */ +- report = rt5645_jack_detect(rt5645->component, 1); +- } else if (!val && rt5645->jack_type == SND_JACK_HEADSET) { +- /* for push button and jack out */ +- btn_type = 0; +- if (snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x4) { +- /* button pressed */ +- report = SND_JACK_HEADSET; +- btn_type = rt5645_button_detect(rt5645->component); +- /* rt5650 can report three kinds of button behavior, +- one click, double click and hold. However, +- currently we will report button pressed/released +- event. So all the three button behaviors are +- treated as button pressed. */ +- switch (btn_type) { +- case 0x8000: +- case 0x4000: +- case 0x2000: +- report |= SND_JACK_BTN_0; +- break; +- case 0x1000: +- case 0x0800: +- case 0x0400: +- report |= SND_JACK_BTN_1; +- break; +- case 0x0200: +- case 0x0100: +- case 0x0080: +- report |= SND_JACK_BTN_2; +- break; +- case 0x0040: +- case 0x0020: +- case 0x0010: +- report |= SND_JACK_BTN_3; +- break; +- case 0x0000: /* unpressed */ +- break; +- default: +- dev_err(rt5645->component->dev, +- "Unexpected button code 0x%04x\n", +- btn_type); +- break; ++ scoped_guard(mutex, &rt5645->jd_mutex) { ++ switch (rt5645->pdata.jd_mode) { ++ case 0: /* Not using rt5645 JD */ ++ if (rt5645->gpiod_hp_det) { ++ gpio_state = gpiod_get_value(rt5645->gpiod_hp_det); ++ if (rt5645->pdata.inv_hp_pol) ++ gpio_state ^= 1; ++ dev_dbg(rt5645->component->dev, "gpio_state = %d\n", ++ gpio_state); ++ report = rt5645_jack_detect(rt5645->component, gpio_state); + } ++ snd_soc_jack_report(rt5645->hp_jack, ++ report, SND_JACK_HEADPHONE); ++ snd_soc_jack_report(rt5645->mic_jack, ++ report, SND_JACK_MICROPHONE); ++ return; ++ case 4: ++ val = snd_soc_component_read(rt5645->component, RT5645_A_JD_CTRL1) & 0x0020; ++ break; ++ default: /* read rt5645 jd1_1 status */ ++ val = snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x1000; ++ break; + } +- if (btn_type == 0)/* button release */ +- report = rt5645->jack_type; +- else { +- mod_timer(&rt5645->btn_check_timer, +- msecs_to_jiffies(100)); +- } +- } else { +- /* jack out */ +- report = 0; +- snd_soc_component_update_bits(rt5645->component, +- RT5645_INT_IRQ_ST, 0x1, 0x0); +- rt5645_jack_detect(rt5645->component, 0); +- } + +- mutex_unlock(&rt5645->jd_mutex); ++ if (!val && rt5645->jack_type == 0) { /* jack in */ ++ report = rt5645_jack_detect(rt5645->component, 1); ++ } else if (!val && rt5645->jack_type == SND_JACK_HEADSET) { ++ /* for push button and jack out */ ++ btn_type = 0; ++ if (snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x4) { ++ /* button pressed */ ++ report = SND_JACK_HEADSET; ++ btn_type = rt5645_button_detect(rt5645->component); ++ /* ++ * rt5650 can report three kinds of button behavior, ++ * one click, double click and hold. However, ++ * currently we will report button pressed/released ++ * event. So all the three button behaviors are ++ * treated as button pressed. ++ */ ++ switch (btn_type) { ++ case 0x8000: ++ case 0x4000: ++ case 0x2000: ++ report |= SND_JACK_BTN_0; ++ break; ++ case 0x1000: ++ case 0x0800: ++ case 0x0400: ++ report |= SND_JACK_BTN_1; ++ break; ++ case 0x0200: ++ case 0x0100: ++ case 0x0080: ++ report |= SND_JACK_BTN_2; ++ break; ++ case 0x0040: ++ case 0x0020: ++ case 0x0010: ++ report |= SND_JACK_BTN_3; ++ break; ++ case 0x0000: /* unpressed */ ++ break; ++ default: ++ dev_err(rt5645->component->dev, ++ "Unexpected button code 0x%04x\n", ++ btn_type); ++ break; ++ } ++ } ++ if (btn_type == 0)/* button release */ ++ report = rt5645->jack_type; ++ else ++ mod_timer(&rt5645->btn_check_timer, ++ msecs_to_jiffies(100)); ++ } else { ++ /* jack out */ ++ report = 0; ++ snd_soc_component_update_bits(rt5645->component, ++ RT5645_INT_IRQ_ST, 0x1, 0x0); ++ rt5645_jack_detect(rt5645->component, 0); ++ } ++ } + + snd_soc_jack_report(rt5645->hp_jack, report, SND_JACK_HEADPHONE); + snd_soc_jack_report(rt5645->mic_jack, report, SND_JACK_MICROPHONE); +@@ -3522,9 +3520,15 @@ static int rt5645_suspend(struct snd_soc_component *component) + static int rt5645_resume(struct snd_soc_component *component) + { + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); ++ int ret; + + regcache_cache_only(rt5645->regmap, false); +- regcache_sync(rt5645->regmap); ++ ret = regcache_sync(rt5645->regmap); ++ if (ret) { ++ regcache_cache_only(rt5645->regmap, true); ++ regcache_mark_dirty(rt5645->regmap); ++ return ret; ++ } + + return 0; + } +@@ -4335,9 +4339,15 @@ static int rt5645_sys_suspend(struct device *dev) + static int rt5645_sys_resume(struct device *dev) + { + struct rt5645_priv *rt5645 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(rt5645->regmap, false); +- regcache_sync(rt5645->regmap); ++ ret = regcache_sync(rt5645->regmap); ++ if (ret) { ++ regcache_cache_only(rt5645->regmap, true); ++ regcache_mark_dirty(rt5645->regmap); ++ return ret; ++ } + + if (rt5645->hp_jack) { + rt5645->jack_type = 0; +diff --git a/sound/soc/codecs/rt5665.c b/sound/soc/codecs/rt5665.c +--- a/sound/soc/codecs/rt5665.c ++++ b/sound/soc/codecs/rt5665.c +@@ -6,6 +6,7 @@ + * Author: Bard Liao + */ + ++#include + #include + #include + #include +@@ -1208,7 +1209,7 @@ static void rt5665_jack_detect_handler(struct work_struct *work) + usleep_range(10000, 15000); + } + +- mutex_lock(&rt5665->calibrate_mutex); ++ guard(mutex)(&rt5665->calibrate_mutex); + + val = snd_soc_component_read(rt5665->component, RT5665_AJD1_CTRL) & 0x0010; + if (!val) { +@@ -1274,8 +1275,6 @@ static void rt5665_jack_detect_handler(struct work_struct *work) + schedule_delayed_work(&rt5665->jd_check_work, 0); + else + cancel_delayed_work_sync(&rt5665->jd_check_work); +- +- mutex_unlock(&rt5665->calibrate_mutex); + } + + static const char * const rt5665_clk_sync[] = { +@@ -4564,7 +4563,7 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + { + int value, count; + +- mutex_lock(&rt5665->calibrate_mutex); ++ guard(mutex)(&rt5665->calibrate_mutex); + + regcache_cache_bypass(rt5665->regmap, true); + +@@ -4601,7 +4600,8 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + pr_err("HP Calibration Failure\n"); + regmap_write(rt5665->regmap, RT5665_RESET, 0); + regcache_cache_bypass(rt5665->regmap, false); +- goto out_unlock; ++ rt5665->calibration_done = true; ++ return; + } + + count++; +@@ -4620,7 +4620,8 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + pr_err("MONO Calibration Failure\n"); + regmap_write(rt5665->regmap, RT5665_RESET, 0); + regcache_cache_bypass(rt5665->regmap, false); +- goto out_unlock; ++ rt5665->calibration_done = true; ++ return; + } + + count++; +@@ -4635,9 +4636,7 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + regmap_write(rt5665->regmap, RT5665_BIAS_CUR_CTRL_8, 0xa602); + regmap_write(rt5665->regmap, RT5665_ASRC_8, 0x0120); + +-out_unlock: + rt5665->calibration_done = true; +- mutex_unlock(&rt5665->calibrate_mutex); + } + + static void rt5665_calibrate_handler(struct work_struct *work) +diff --git a/sound/soc/codecs/rt5668.c b/sound/soc/codecs/rt5668.c +--- a/sound/soc/codecs/rt5668.c ++++ b/sound/soc/codecs/rt5668.c +@@ -6,6 +6,7 @@ + * Author: Bard Liao + */ + ++#include + #include + #include + #include +@@ -986,7 +987,7 @@ static void rt5668_jack_detect_handler(struct work_struct *work) + return; + } + +- mutex_lock(&rt5668->calibrate_mutex); ++ guard(mutex)(&rt5668->calibrate_mutex); + + val = snd_soc_component_read(rt5668->component, RT5668_AJD1_CTRL) + & RT5668_JDH_RS_MASK; +@@ -1053,8 +1054,6 @@ static void rt5668_jack_detect_handler(struct work_struct *work) + schedule_delayed_work(&rt5668->jd_check_work, 0); + else + cancel_delayed_work_sync(&rt5668->jd_check_work); +- +- mutex_unlock(&rt5668->calibrate_mutex); + } + + static const struct snd_kcontrol_new rt5668_snd_controls[] = { +@@ -2356,7 +2355,7 @@ static void rt5668_calibrate(struct rt5668_priv *rt5668) + { + int value, count; + +- mutex_lock(&rt5668->calibrate_mutex); ++ guard(mutex)(&rt5668->calibrate_mutex); + + rt5668_reset(rt5668->regmap); + regmap_write(rt5668->regmap, RT5668_PWR_ANLG_1, 0xa2bf); +@@ -2400,9 +2399,6 @@ static void rt5668_calibrate(struct rt5668_priv *rt5668) + /* restore settings */ + regmap_write(rt5668->regmap, RT5668_STO1_ADC_MIXER, 0xc0c4); + regmap_write(rt5668->regmap, RT5668_PWR_DIG_1, 0x0000); +- +- mutex_unlock(&rt5668->calibrate_mutex); +- + } + + static int rt5668_i2c_probe(struct i2c_client *i2c) +diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c +--- a/sound/soc/codecs/rt5677-spi.c ++++ b/sound/soc/codecs/rt5677-spi.c +@@ -6,6 +6,7 @@ + * Author: Oder Chiou + */ + ++#include + #include + #include + #include +@@ -133,9 +134,8 @@ static int rt5677_spi_hw_params( + struct rt5677_dsp *rt5677_dsp = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5677_dsp->dma_lock); ++ guard(mutex)(&rt5677_dsp->dma_lock); + rt5677_dsp->substream = substream; +- mutex_unlock(&rt5677_dsp->dma_lock); + + return 0; + } +@@ -147,9 +147,8 @@ static int rt5677_spi_hw_free( + struct rt5677_dsp *rt5677_dsp = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5677_dsp->dma_lock); ++ guard(mutex)(&rt5677_dsp->dma_lock); + rt5677_dsp->substream = NULL; +- mutex_unlock(&rt5677_dsp->dma_lock); + + return 0; + } +@@ -311,17 +310,17 @@ static void rt5677_spi_copy_work(struct work_struct *work) + int ret = 0; + + /* Ensure runtime->dma_area buffer does not go away while copying. */ +- mutex_lock(&rt5677_dsp->dma_lock); ++ guard(mutex)(&rt5677_dsp->dma_lock); + if (!rt5677_dsp->substream) { + dev_err(rt5677_dsp->dev, "No pcm substream\n"); +- goto done; ++ return; + } + + runtime = rt5677_dsp->substream->runtime; + + if (rt5677_spi_mic_write_offset(&mic_write_offset)) { + dev_err(rt5677_dsp->dev, "No mic_write_offset\n"); +- goto done; ++ return; + } + + /* If this is the first time that we've asked for streaming data after +@@ -355,7 +354,7 @@ static void rt5677_spi_copy_work(struct work_struct *work) + ret = rt5677_spi_copy(rt5677_dsp, copy_bytes); + if (ret) { + dev_err(rt5677_dsp->dev, "Copy failed %d\n", ret); +- goto done; ++ return; + } + rt5677_dsp->avail_bytes += copy_bytes; + if (rt5677_dsp->avail_bytes >= period_bytes) { +@@ -367,8 +366,6 @@ static void rt5677_spi_copy_work(struct work_struct *work) + + delay = bytes_to_frames(runtime, period_bytes) / runtime->rate; + schedule_delayed_work(&rt5677_dsp->copy_work, secs_to_jiffies(delay)); +-done: +- mutex_unlock(&rt5677_dsp->dma_lock); + } + + static int rt5677_spi_pcm_new(struct snd_soc_component *component, +@@ -507,10 +504,8 @@ int rt5677_spi_read(u32 addr, void *rxbuf, size_t len) + header[3] = ((addr + offset) & 0x0000ff00) >> 8; + header[4] = ((addr + offset) & 0x000000ff) >> 0; + +- mutex_lock(&spi_mutex); +- status |= spi_sync(g_spi, &m); +- mutex_unlock(&spi_mutex); +- ++ scoped_guard(mutex, &spi_mutex) ++ status |= spi_sync(g_spi, &m); + + /* Copy data back to caller buffer */ + rt5677_spi_reverse(cb + offset, len - offset, body, t[1].len); +@@ -564,9 +559,8 @@ int rt5677_spi_write(u32 addr, const void *txbuf, size_t len) + offset += t.len; + t.len += RT5677_SPI_HEADER + 1; + +- mutex_lock(&spi_mutex); +- status |= spi_sync(g_spi, &m); +- mutex_unlock(&spi_mutex); ++ scoped_guard(mutex, &spi_mutex) ++ status |= spi_sync(g_spi, &m); + } + return status; + } +@@ -591,10 +585,10 @@ void rt5677_spi_hotword_detected(void) + return; + } + +- mutex_lock(&rt5677_dsp->dma_lock); +- dev_info(rt5677_dsp->dev, "Hotword detected\n"); +- rt5677_dsp->new_hotword = true; +- mutex_unlock(&rt5677_dsp->dma_lock); ++ scoped_guard(mutex, &rt5677_dsp->dma_lock) { ++ dev_info(rt5677_dsp->dev, "Hotword detected\n"); ++ rt5677_dsp->new_hotword = true; ++ } + + schedule_delayed_work(&rt5677_dsp->copy_work, 0); + } +diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c +--- a/sound/soc/codecs/rt5677.c ++++ b/sound/soc/codecs/rt5677.c +@@ -6,6 +6,8 @@ + * Author: Oder Chiou + */ + ++#include ++#include + #include + #include + #include +@@ -563,46 +565,43 @@ static int rt5677_dsp_mode_i2c_write_addr(struct rt5677_priv *rt5677, + struct snd_soc_component *component = rt5677->component; + int ret; + +- mutex_lock(&rt5677->dsp_cmd_lock); ++ guard(mutex)(&rt5677->dsp_cmd_lock); + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_MSB, + addr >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr msb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_LSB, + addr & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr lsb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_MSB, + value >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set data msb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_LSB, + value & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set data lsb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_OP_CODE, + opcode); + if (ret < 0) { + dev_err(component->dev, "Failed to set op code value: %d\n", ret); +- goto err; ++ return ret; + } + +-err: +- mutex_unlock(&rt5677->dsp_cmd_lock); +- + return ret; + } + +@@ -622,36 +621,33 @@ static int rt5677_dsp_mode_i2c_read_addr( + int ret; + unsigned int msb, lsb; + +- mutex_lock(&rt5677->dsp_cmd_lock); ++ guard(mutex)(&rt5677->dsp_cmd_lock); + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_MSB, + addr >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr msb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_LSB, + addr & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr lsb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_OP_CODE, + 0x0002); + if (ret < 0) { + dev_err(component->dev, "Failed to set op code value: %d\n", ret); +- goto err; ++ return ret; + } + + regmap_read(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_MSB, &msb); + regmap_read(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_LSB, &lsb); + *value = (msb << 16) | lsb; + +-err: +- mutex_unlock(&rt5677->dsp_cmd_lock); +- + return ret; + } + +@@ -853,11 +849,11 @@ static int rt5677_parse_and_load_dsp(struct rt5677_priv *rt5677, const u8 *buf, + + static int rt5677_load_dsp_from_file(struct rt5677_priv *rt5677) + { +- const struct firmware *fwp; + struct device *dev = rt5677->component->dev; +- int ret = 0; ++ int ret; + + /* Load dsp firmware from rt5677_elf_vad file */ ++ const struct firmware *fwp __free(firmware) = NULL; + ret = request_firmware(&fwp, "rt5677_elf_vad", dev); + if (ret) { + dev_err(dev, "Request rt5677_elf_vad failed %d\n", ret); +@@ -865,9 +861,7 @@ static int rt5677_load_dsp_from_file(struct rt5677_priv *rt5677) + } + dev_info(dev, "Requested rt5677_elf_vad (%zu)\n", fwp->size); + +- ret = rt5677_parse_and_load_dsp(rt5677, fwp->data, fwp->size); +- release_firmware(fwp); +- return ret; ++ return rt5677_parse_and_load_dsp(rt5677, fwp->data, fwp->size); + } + + static int rt5677_set_dsp_vad(struct snd_soc_component *component, bool on) +@@ -940,21 +934,20 @@ static void rt5677_dsp_work(struct work_struct *work) + activity = false; + + /* Don't turn off the DSP while handling irqs */ +- mutex_lock(&rt5677->irq_lock); +- /* Set DSP CPU to Stop */ +- regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, +- RT5677_PWR_DSP_CPU, RT5677_PWR_DSP_CPU); ++ scoped_guard(mutex, &rt5677->irq_lock) { ++ /* Set DSP CPU to Stop */ ++ regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, ++ RT5677_PWR_DSP_CPU, RT5677_PWR_DSP_CPU); + +- rt5677_set_dsp_mode(rt5677, false); ++ rt5677_set_dsp_mode(rt5677, false); + +- /* Disable and clear VAD interrupt */ +- regmap_write(rt5677->regmap, RT5677_VAD_CTRL1, 0x2184); ++ /* Disable and clear VAD interrupt */ ++ regmap_write(rt5677->regmap, RT5677_VAD_CTRL1, 0x2184); + +- /* Set GPIO1 pin back to be IRQ output for jack detect */ +- regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, +- RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ); +- +- mutex_unlock(&rt5677->irq_lock); ++ /* Set GPIO1 pin back to be IRQ output for jack detect */ ++ regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, ++ RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ); ++ } + } + } + +@@ -4767,6 +4760,21 @@ static int rt5677_gpio_direction_in(struct gpio_chip *chip, unsigned offset) + return rt5677_update_gpio_bits(rt5677, offset, m, v); + } + ++static int rt5677_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) ++{ ++ struct rt5677_priv *rt5677 = gpiochip_get_data(chip); ++ unsigned int shift = RT5677_GPIOx_DIR_SFT + (offset % 5) * 3; ++ unsigned int bank = offset / 5; ++ unsigned int reg = bank ? RT5677_GPIO_CTRL3 : RT5677_GPIO_CTRL2; ++ int ret; ++ ++ ret = regmap_test_bits(rt5677->regmap, reg, BIT(shift)); ++ if (ret < 0) ++ return ret; ++ ++ return ret ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; ++} ++ + /* + * Configures the GPIO as + * 0 - floating +@@ -4834,6 +4842,7 @@ static int rt5677_to_irq(struct gpio_chip *chip, unsigned offset) + static const struct gpio_chip rt5677_template_chip = { + .label = RT5677_DRV_NAME, + .owner = THIS_MODULE, ++ .get_direction = rt5677_gpio_get_direction, + .direction_output = rt5677_gpio_direction_out, + .set = rt5677_gpio_set, + .direction_input = rt5677_gpio_direction_in, +@@ -4980,11 +4989,11 @@ static int rt5677_read(void *context, unsigned int reg, unsigned int *val) + + if (rt5677->is_dsp_mode) { + if (reg > 0xff) { +- mutex_lock(&rt5677->dsp_pri_lock); +- rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, +- reg & 0xff); +- rt5677_dsp_mode_i2c_read(rt5677, RT5677_PRIV_DATA, val); +- mutex_unlock(&rt5677->dsp_pri_lock); ++ scoped_guard(mutex, &rt5677->dsp_pri_lock) { ++ rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, ++ reg & 0xff); ++ rt5677_dsp_mode_i2c_read(rt5677, RT5677_PRIV_DATA, val); ++ } + } else { + rt5677_dsp_mode_i2c_read(rt5677, reg, val); + } +@@ -5002,12 +5011,12 @@ static int rt5677_write(void *context, unsigned int reg, unsigned int val) + + if (rt5677->is_dsp_mode) { + if (reg > 0xff) { +- mutex_lock(&rt5677->dsp_pri_lock); +- rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, +- reg & 0xff); +- rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_DATA, +- val); +- mutex_unlock(&rt5677->dsp_pri_lock); ++ scoped_guard(mutex, &rt5677->dsp_pri_lock) { ++ rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, ++ reg & 0xff); ++ rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_DATA, ++ val); ++ } + } else { + rt5677_dsp_mode_i2c_write(rt5677, reg, val); + } +@@ -5399,7 +5408,7 @@ static void rt5677_resume_irq_check(struct work_struct *work) + * Without this explicit check, unplug the headset right after suspend + * starts, then after resume the headset is still shown as plugged in. + */ +- mutex_lock(&rt5677->irq_lock); ++ guard(mutex)(&rt5677->irq_lock); + for (i = 0; i < RT5677_IRQ_NUM; i++) { + if (rt5677->irq_en & rt5677_irq_descs[i].enable_mask) { + virq = irq_find_mapping(rt5677->domain, i); +@@ -5407,7 +5416,6 @@ static void rt5677_resume_irq_check(struct work_struct *work) + handle_nested_irq(virq); + } + } +- mutex_unlock(&rt5677->irq_lock); + } + + static void rt5677_irq_bus_lock(struct irq_data *data) +diff --git a/sound/soc/codecs/rt5682-sdw.c b/sound/soc/codecs/rt5682-sdw.c +--- a/sound/soc/codecs/rt5682-sdw.c ++++ b/sound/soc/codecs/rt5682-sdw.c +@@ -6,6 +6,7 @@ + // Author: Oder Chiou + // + ++#include + #include + #include + #include +@@ -410,7 +411,9 @@ static int rt5682_io_init(struct device *dev, struct sdw_slave *slave) + if (rt5682->first_hw_init) { + regcache_cache_bypass(rt5682->regmap, false); + regcache_mark_dirty(rt5682->regmap); +- regcache_sync(rt5682->regmap); ++ ret = regcache_sync(rt5682->regmap); ++ if (ret) ++ goto err_sync; + + /* volatile registers */ + regmap_update_bits(rt5682->regmap, RT5682_CBJ_CTRL_2, +@@ -472,8 +475,16 @@ static int rt5682_io_init(struct device *dev, struct sdw_slave *slave) + /* Mark Slave initialization complete */ + rt5682->hw_init = true; + rt5682->first_hw_init = true; ++ goto out; ++ ++err_sync: ++ regcache_cache_bypass(rt5682->regmap, false); ++ regcache_cache_only(rt5682->sdw_regmap, true); ++ regcache_cache_only(rt5682->regmap, true); ++ regcache_mark_dirty(rt5682->regmap); + + err_nodev: ++out: + pm_runtime_put_autosuspend(&slave->dev); + + dev_dbg(&slave->dev, "%s hw_init complete: %d\n", __func__, ret); +@@ -660,12 +671,11 @@ static int rt5682_interrupt_callback(struct sdw_slave *slave, + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + +- mutex_lock(&rt5682->disable_irq_lock); ++ guard(mutex)(&rt5682->disable_irq_lock); + if (status->control_port & 0x4 && !rt5682->disable_irq) { + mod_delayed_work(system_power_efficient_wq, + &rt5682->jack_detect_work, msecs_to_jiffies(rt5682->irq_work_delay_time)); + } +- mutex_unlock(&rt5682->disable_irq_lock); + + return 0; + } +@@ -736,11 +746,11 @@ static int rt5682_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt5682->disable_irq_lock); +- rt5682->disable_irq = true; +- ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, +- SDW_SCP_INT1_IMPL_DEF, 0); +- mutex_unlock(&rt5682->disable_irq_lock); ++ scoped_guard(mutex, &rt5682->disable_irq_lock) { ++ rt5682->disable_irq = true; ++ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, ++ SDW_SCP_INT1_IMPL_DEF, 0); ++ } + + if (ret < 0) { + /* log but don't prevent suspend from happening */ +@@ -760,12 +770,12 @@ static int rt5682_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt5682->disable_irq_lock); +- if (rt5682->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); +- rt5682->disable_irq = false; ++ scoped_guard(mutex, &rt5682->disable_irq_lock) { ++ if (rt5682->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); ++ rt5682->disable_irq = false; ++ } + } +- mutex_unlock(&rt5682->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT5682_PROBE_TIMEOUT); +@@ -776,7 +786,13 @@ static int rt5682_dev_resume(struct device *dev) + + regcache_cache_only(rt5682->sdw_regmap, false); + regcache_cache_only(rt5682->regmap, false); +- regcache_sync(rt5682->regmap); ++ ret = regcache_sync(rt5682->regmap); ++ if (ret) { ++ regcache_cache_only(rt5682->sdw_regmap, true); ++ regcache_cache_only(rt5682->regmap, true); ++ regcache_mark_dirty(rt5682->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt5682.c b/sound/soc/codecs/rt5682.c +--- a/sound/soc/codecs/rt5682.c ++++ b/sound/soc/codecs/rt5682.c +@@ -6,6 +6,7 @@ + // Author: Bard Liao + // + ++#include + #include + #include + #include +@@ -3125,7 +3126,7 @@ void rt5682_calibrate(struct rt5682_priv *rt5682) + { + int value, count; + +- mutex_lock(&rt5682->calibrate_mutex); ++ guard(mutex)(&rt5682->calibrate_mutex); + + rt5682_reset(rt5682); + regmap_write(rt5682->regmap, RT5682_I2C_CTRL, 0x000f); +@@ -3175,8 +3176,6 @@ void rt5682_calibrate(struct rt5682_priv *rt5682) + regmap_write(rt5682->regmap, RT5682_CALIB_ADC_CTRL, 0x2005); + regmap_write(rt5682->regmap, RT5682_STO1_ADC_MIXER, 0xc0c4); + regmap_write(rt5682->regmap, RT5682_CAL_REC, 0x0c0c); +- +- mutex_unlock(&rt5682->calibrate_mutex); + } + EXPORT_SYMBOL_GPL(rt5682_calibrate); + +diff --git a/sound/soc/codecs/rt5682s.c b/sound/soc/codecs/rt5682s.c +--- a/sound/soc/codecs/rt5682s.c ++++ b/sound/soc/codecs/rt5682s.c +@@ -6,6 +6,7 @@ + // Author: Derek Fang + // + ++#include + #include + #include + #include +@@ -648,7 +649,7 @@ static void rt5682s_sar_power_mode(struct snd_soc_component *component, int mode + { + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5682s->sar_mutex); ++ guard(mutex)(&rt5682s->sar_mutex); + + switch (mode) { + case SAR_PWR_SAVING: +@@ -695,8 +696,6 @@ static void rt5682s_sar_power_mode(struct snd_soc_component *component, int mode + dev_err(component->dev, "Invalid SAR Power mode: %d\n", mode); + break; + } +- +- mutex_unlock(&rt5682s->sar_mutex); + } + + static void rt5682s_enable_push_button_irq(struct snd_soc_component *component) +@@ -2534,7 +2533,7 @@ static int rt5682s_wclk_prepare(struct clk_hw *hw) + if (!rt5682s_clk_check(rt5682s)) + return -EINVAL; + +- mutex_lock(&rt5682s->wclk_mutex); ++ guard(mutex)(&rt5682s->wclk_mutex); + + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, + RT5682S_PWR_VREF2 | RT5682S_PWR_FV2 | RT5682S_PWR_MB, +@@ -2556,8 +2555,6 @@ static int rt5682s_wclk_prepare(struct clk_hw *hw) + + rt5682s->wclk_enabled = 1; + +- mutex_unlock(&rt5682s->wclk_mutex); +- + return 0; + } + +@@ -2570,7 +2567,7 @@ static void rt5682s_wclk_unprepare(struct clk_hw *hw) + if (!rt5682s_clk_check(rt5682s)) + return; + +- mutex_lock(&rt5682s->wclk_mutex); ++ guard(mutex)(&rt5682s->wclk_mutex); + + if (!rt5682s->jack_type) + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, +@@ -2585,8 +2582,6 @@ static void rt5682s_wclk_unprepare(struct clk_hw *hw) + rt5682s_set_pllb_power(rt5682s, 0); + + rt5682s->wclk_enabled = 0; +- +- mutex_unlock(&rt5682s->wclk_mutex); + } + + static unsigned long rt5682s_wclk_recalc_rate(struct clk_hw *hw, +@@ -2997,7 +2992,7 @@ static void rt5682s_calibrate(struct rt5682s_priv *rt5682s) + { + unsigned int count, value; + +- mutex_lock(&rt5682s->calibrate_mutex); ++ guard(mutex)(&rt5682s->calibrate_mutex); + + regmap_write(rt5682s->regmap, RT5682S_PWR_ANLG_1, 0xaa80); + usleep_range(15000, 20000); +@@ -3034,8 +3029,6 @@ static void rt5682s_calibrate(struct rt5682s_priv *rt5682s) + regmap_write(rt5682s->regmap, RT5682S_PWR_DIG_1, 0x00c0); + regmap_write(rt5682s->regmap, RT5682S_PWR_ANLG_1, 0x0800); + regmap_write(rt5682s->regmap, RT5682S_GLB_CLK, 0x0000); +- +- mutex_unlock(&rt5682s->calibrate_mutex); + } + + static const struct regmap_config rt5682s_regmap = { +diff --git a/sound/soc/codecs/rt700-sdw.c b/sound/soc/codecs/rt700-sdw.c +--- a/sound/soc/codecs/rt700-sdw.c ++++ b/sound/soc/codecs/rt700-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -415,12 +416,11 @@ static int rt700_interrupt_callback(struct sdw_slave *slave, + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + +- mutex_lock(&rt700->disable_irq_lock); ++ guard(mutex)(&rt700->disable_irq_lock); + if (status->control_port & 0x4 && !rt700->disable_irq) { + mod_delayed_work(system_power_efficient_wq, + &rt700->jack_detect_work, msecs_to_jiffies(250)); + } +- mutex_unlock(&rt700->disable_irq_lock); + + return 0; + } +@@ -458,10 +458,8 @@ static void rt700_sdw_remove(struct sdw_slave *slave) + { + struct rt700_priv *rt700 = dev_get_drvdata(&slave->dev); + +- if (rt700->hw_init) { +- cancel_delayed_work_sync(&rt700->jack_detect_work); +- cancel_delayed_work_sync(&rt700->jack_btn_check_work); +- } ++ cancel_delayed_work_sync(&rt700->jack_detect_work); ++ cancel_delayed_work_sync(&rt700->jack_btn_check_work); + + pm_runtime_disable(&slave->dev); + } +@@ -501,11 +499,11 @@ static int rt700_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt700->disable_irq_lock); +- rt700->disable_irq = true; +- ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, +- SDW_SCP_INT1_IMPL_DEF, 0); +- mutex_unlock(&rt700->disable_irq_lock); ++ scoped_guard(mutex, &rt700->disable_irq_lock) { ++ rt700->disable_irq = true; ++ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, ++ SDW_SCP_INT1_IMPL_DEF, 0); ++ } + + if (ret < 0) { + /* log but don't prevent suspend from happening */ +diff --git a/sound/soc/codecs/rt711-sdca-sdw.c b/sound/soc/codecs/rt711-sdca-sdw.c +--- a/sound/soc/codecs/rt711-sdca-sdw.c ++++ b/sound/soc/codecs/rt711-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -415,13 +416,13 @@ static int rt711_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt711_sdca->disable_irq_lock); +- rt711_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt711_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt711_sdca->disable_irq_lock) { ++ rt711_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -443,13 +444,15 @@ static int rt711_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt711->disable_irq_lock); +- if (rt711->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt711->disable_irq = false; ++ scoped_guard(mutex, &rt711->disable_irq_lock) { ++ if (rt711->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt711->disable_irq = false; ++ } + } +- mutex_unlock(&rt711->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT711_PROBE_TIMEOUT); +diff --git a/sound/soc/codecs/rt711-sdca.c b/sound/soc/codecs/rt711-sdca.c +--- a/sound/soc/codecs/rt711-sdca.c ++++ b/sound/soc/codecs/rt711-sdca.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -450,7 +451,7 @@ static void rt711_sdca_btn_check_handler(struct work_struct *work) + + static void rt711_sdca_jack_init(struct rt711_sdca_priv *rt711) + { +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + + if (rt711->hs_jack) { + /* Enable HID1 event & set button RTC mode */ +@@ -515,8 +516,6 @@ static void rt711_sdca_jack_init(struct rt711_sdca_priv *rt711) + + dev_dbg(&rt711->slave->dev, "in %s disable\n", __func__); + } +- +- mutex_unlock(&rt711->calibrate_mutex); + } + + static int rt711_sdca_set_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/rt711-sdw.c b/sound/soc/codecs/rt711-sdw.c +--- a/sound/soc/codecs/rt711-sdw.c ++++ b/sound/soc/codecs/rt711-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -422,12 +423,11 @@ static int rt711_interrupt_callback(struct sdw_slave *slave, + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + +- mutex_lock(&rt711->disable_irq_lock); ++ guard(mutex)(&rt711->disable_irq_lock); + if (status->control_port & 0x4 && !rt711->disable_irq) { + mod_delayed_work(system_power_efficient_wq, + &rt711->jack_detect_work, msecs_to_jiffies(250)); + } +- mutex_unlock(&rt711->disable_irq_lock); + + return 0; + } +@@ -509,11 +509,11 @@ static int rt711_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt711->disable_irq_lock); +- rt711->disable_irq = true; +- ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, +- SDW_SCP_INT1_IMPL_DEF, 0); +- mutex_unlock(&rt711->disable_irq_lock); ++ scoped_guard(mutex, &rt711->disable_irq_lock) { ++ rt711->disable_irq = true; ++ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, ++ SDW_SCP_INT1_IMPL_DEF, 0); ++ } + + if (ret < 0) { + /* log but don't prevent suspend from happening */ +@@ -535,12 +535,12 @@ static int rt711_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt711->disable_irq_lock); +- if (rt711->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); +- rt711->disable_irq = false; ++ scoped_guard(mutex, &rt711->disable_irq_lock) { ++ if (rt711->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); ++ rt711->disable_irq = false; ++ } + } +- mutex_unlock(&rt711->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT711_PROBE_TIMEOUT); +diff --git a/sound/soc/codecs/rt711.c b/sound/soc/codecs/rt711.c +--- a/sound/soc/codecs/rt711.c ++++ b/sound/soc/codecs/rt711.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -89,24 +90,24 @@ static int rt711_calibration(struct rt711_priv *rt711) + struct regmap *regmap = rt711->regmap; + int ret = 0; + +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + dev = regmap_get_device(regmap); + + /* Calibration manual mode */ + rt711_index_update_bits(regmap, RT711_VENDOR_REG, RT711_FSM_CTL, +- 0xf, 0x0); ++ 0xf, 0x0); + + /* trigger */ + rt711_index_update_bits(regmap, RT711_VENDOR_CALI, +- RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_CALI_TRIGGER, +- RT711_DAC_DC_CALI_TRIGGER); ++ RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_CALI_TRIGGER, ++ RT711_DAC_DC_CALI_TRIGGER); + + /* wait for calibration process */ + rt711_index_read(regmap, RT711_VENDOR_CALI, +- RT711_DAC_DC_CALI_CTL1, &val); ++ RT711_DAC_DC_CALI_CTL1, &val); + + while (val & RT711_DAC_DC_CALI_TRIGGER) { + if (loop >= 500) { +@@ -119,16 +120,15 @@ static int rt711_calibration(struct rt711_priv *rt711) + + usleep_range(10000, 11000); + rt711_index_read(regmap, RT711_VENDOR_CALI, +- RT711_DAC_DC_CALI_CTL1, &val); ++ RT711_DAC_DC_CALI_CTL1, &val); + } + + /* depop mode */ + rt711_index_update_bits(regmap, RT711_VENDOR_REG, +- RT711_FSM_CTL, 0xf, RT711_DEPOP_CTL); ++ RT711_FSM_CTL, 0xf, RT711_DEPOP_CTL); + + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); +- mutex_unlock(&rt711->calibrate_mutex); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + + dev_dbg(dev, "%s calibration complete, ret=%d\n", __func__, ret); + return ret; +@@ -362,24 +362,24 @@ static void rt711_jack_init(struct rt711_priv *rt711) + { + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(rt711->component); + +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + /* power on */ + if (snd_soc_dapm_get_bias_level(dapm) <= SND_SOC_BIAS_STANDBY) + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + if (rt711->hs_jack) { + /* unsolicited response & IRQ control */ + regmap_write(rt711->regmap, +- RT711_SET_MIC2_UNSOLICITED_ENABLE, 0x82); ++ RT711_SET_MIC2_UNSOLICITED_ENABLE, 0x82); + regmap_write(rt711->regmap, +- RT711_SET_HP_UNSOLICITED_ENABLE, 0x81); ++ RT711_SET_HP_UNSOLICITED_ENABLE, 0x81); + regmap_write(rt711->regmap, +- RT711_SET_INLINE_UNSOLICITED_ENABLE, 0x83); ++ RT711_SET_INLINE_UNSOLICITED_ENABLE, 0x83); + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, +- 0x10, 0x2420); ++ 0x10, 0x2420); + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, +- 0x19, 0x2e11); ++ 0x19, 0x2e11); + + switch (rt711->jd_src) { + case RT711_JD1: +@@ -449,8 +449,7 @@ static void rt711_jack_init(struct rt711_priv *rt711) + /* power off */ + if (snd_soc_dapm_get_bias_level(dapm) <= SND_SOC_BIAS_STANDBY) + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); +- mutex_unlock(&rt711->calibrate_mutex); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + } + + static int rt711_set_jack_detect(struct snd_soc_component *component, +@@ -511,7 +510,7 @@ static int rt711_set_amp_gain_put(struct snd_kcontrol *kcontrol, + unsigned int read_ll, read_rl; + int i; + +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + + /* Can't use update bit function, so read the original value first */ + addr_h = mc->reg; +@@ -599,7 +598,6 @@ static int rt711_set_amp_gain_put(struct snd_kcontrol *kcontrol, + regmap_write(rt711->regmap, + RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + +- mutex_unlock(&rt711->calibrate_mutex); + return 0; + } + +@@ -908,11 +906,11 @@ static int rt711_set_bias_level(struct snd_soc_component *component, + break; + + case SND_SOC_BIAS_STANDBY: +- mutex_lock(&rt711->calibrate_mutex); +- regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, +- AC_PWRST_D3); +- mutex_unlock(&rt711->calibrate_mutex); ++ scoped_guard(mutex, &rt711->calibrate_mutex) { ++ regmap_write(rt711->regmap, ++ RT711_SET_AUDIO_POWER_STATE, ++ AC_PWRST_D3); ++ } + break; + + default: +diff --git a/sound/soc/codecs/rt712-sdca-dmic.c b/sound/soc/codecs/rt712-sdca-dmic.c +--- a/sound/soc/codecs/rt712-sdca-dmic.c ++++ b/sound/soc/codecs/rt712-sdca-dmic.c +@@ -916,10 +916,23 @@ static int rt712_sdca_dmic_dev_resume(struct device *dev) + } + + regcache_cache_only(rt712->regmap, false); +- regcache_sync(rt712->regmap); ++ ret = regcache_sync(rt712->regmap); ++ if (ret) ++ goto err_sync; ++ + regcache_cache_only(rt712->mbq_regmap, false); +- regcache_sync(rt712->mbq_regmap); ++ ret = regcache_sync(rt712->mbq_regmap); ++ if (ret) ++ goto err_sync; ++ + return 0; ++ ++err_sync: ++ regcache_cache_only(rt712->regmap, true); ++ regcache_cache_only(rt712->mbq_regmap, true); ++ regcache_mark_dirty(rt712->regmap); ++ regcache_mark_dirty(rt712->mbq_regmap); ++ return ret; + } + + static const struct dev_pm_ops rt712_sdca_dmic_pm = { +diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c +--- a/sound/soc/codecs/rt712-sdca-sdw.c ++++ b/sound/soc/codecs/rt712-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -427,13 +428,13 @@ static int rt712_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt712_sdca->disable_irq_lock); +- rt712_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt712_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt712_sdca->disable_irq_lock) { ++ rt712_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -455,14 +456,15 @@ static int rt712_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt712->disable_irq_lock); +- if (rt712->disable_irq == true) { +- +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt712->disable_irq = false; ++ scoped_guard(mutex, &rt712->disable_irq_lock) { ++ if (rt712->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt712->disable_irq = false; ++ } + } +- mutex_unlock(&rt712->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT712_PROBE_TIMEOUT); +@@ -472,9 +474,20 @@ static int rt712_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt712->regmap, false); +- regcache_sync(rt712->regmap); ++ ret = regcache_sync(rt712->regmap); ++ if (ret) { ++ regcache_cache_only(rt712->regmap, true); ++ return ret; ++ } ++ + regcache_cache_only(rt712->mbq_regmap, false); +- regcache_sync(rt712->mbq_regmap); ++ ret = regcache_sync(rt712->mbq_regmap); ++ if (ret) { ++ regcache_cache_only(rt712->mbq_regmap, true); ++ regcache_cache_only(rt712->regmap, true); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -7,6 +7,7 @@ + // + + #include ++#include + #include + #include + #include +@@ -403,7 +404,7 @@ static void rt712_sdca_btn_check_handler(struct work_struct *work) + + static void rt712_sdca_jack_init(struct rt712_sdca_priv *rt712) + { +- mutex_lock(&rt712->calibrate_mutex); ++ guard(mutex)(&rt712->calibrate_mutex); + + if (rt712->hs_jack) { + /* Enable HID1 event & set button RTC mode */ +@@ -450,8 +451,6 @@ static void rt712_sdca_jack_init(struct rt712_sdca_priv *rt712) + + dev_dbg(&rt712->slave->dev, "in %s disable\n", __func__); + } +- +- mutex_unlock(&rt712->calibrate_mutex); + } + + static int rt712_sdca_set_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c +--- a/sound/soc/codecs/rt721-sdca-sdw.c ++++ b/sound/soc/codecs/rt721-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -466,13 +467,13 @@ static int rt721_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt721_sdca->disable_irq_lock); +- rt721_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt721_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt721_sdca->disable_irq_lock) { ++ rt721_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -494,13 +495,15 @@ static int rt721_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt721->disable_irq_lock); +- if (rt721->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt721->disable_irq = false; ++ scoped_guard(mutex, &rt721->disable_irq_lock) { ++ if (rt721->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt721->disable_irq = false; ++ } + } +- mutex_unlock(&rt721->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT721_PROBE_TIMEOUT); +@@ -510,9 +513,20 @@ static int rt721_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt721->regmap, false); +- regcache_sync(rt721->regmap); ++ ret = regcache_sync(rt721->regmap); ++ if (ret) { ++ regcache_cache_only(rt721->regmap, true); ++ return ret; ++ } ++ + regcache_cache_only(rt721->mbq_regmap, false); +- regcache_sync(rt721->mbq_regmap); ++ ret = regcache_sync(rt721->mbq_regmap); ++ if (ret) { ++ regcache_cache_only(rt721->mbq_regmap, true); ++ regcache_cache_only(rt721->regmap, true); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c +--- a/sound/soc/codecs/rt721-sdca.c ++++ b/sound/soc/codecs/rt721-sdca.c +@@ -5,7 +5,7 @@ + // Copyright(c) 2024 Realtek Semiconductor Corp. + // + // +- ++#include + #include + #include + #include +@@ -289,7 +289,7 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) + + static void rt721_sdca_jack_init(struct rt721_sdca_priv *rt721) + { +- mutex_lock(&rt721->calibrate_mutex); ++ guard(mutex)(&rt721->calibrate_mutex); + if (rt721->hs_jack) { + sdw_write_no_pm(rt721->slave, SDW_SCP_SDCA_INTMASK1, + SDW_SCP_SDCA_INTMASK_SDCA_0); +@@ -309,7 +309,6 @@ static void rt721_sdca_jack_init(struct rt721_sdca_priv *rt721) + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_GE_REL_CTRL1, 0x4000, 0x4000); + } +- mutex_unlock(&rt721->calibrate_mutex); + } + + static int rt721_sdca_set_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/rt722-sdca-sdw.c b/sound/soc/codecs/rt722-sdca-sdw.c +--- a/sound/soc/codecs/rt722-sdca-sdw.c ++++ b/sound/soc/codecs/rt722-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -517,13 +518,13 @@ static int rt722_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt722_sdca->disable_irq_lock); +- rt722_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt722_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt722_sdca->disable_irq_lock) { ++ rt722_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -545,13 +546,15 @@ static int rt722_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt722->disable_irq_lock); +- if (rt722->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt722->disable_irq = false; ++ scoped_guard(mutex, &rt722->disable_irq_lock) { ++ if (rt722->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt722->disable_irq = false; ++ } + } +- mutex_unlock(&rt722->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT722_PROBE_TIMEOUT); +@@ -561,7 +564,13 @@ static int rt722_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt722->regmap, false); +- regcache_sync(rt722->regmap); ++ ret = regcache_sync(rt722->regmap); ++ if (ret) { ++ regcache_cache_only(rt722->regmap, true); ++ regcache_mark_dirty(rt722->regmap); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c +--- a/sound/soc/codecs/rt722-sdca.c ++++ b/sound/soc/codecs/rt722-sdca.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -294,7 +295,7 @@ static void rt722_sdca_btn_check_handler(struct work_struct *work) + + static void rt722_sdca_jack_init(struct rt722_sdca_priv *rt722) + { +- mutex_lock(&rt722->calibrate_mutex); ++ guard(mutex)(&rt722->calibrate_mutex); + if (rt722->hs_jack) { + /* set SCP_SDCA_IntMask1[0]=1 */ + sdw_write_no_pm(rt722->slave, SDW_SCP_SDCA_INTMASK1, +@@ -317,7 +318,6 @@ static void rt722_sdca_jack_init(struct rt722_sdca_priv *rt722) + rt722_sdca_index_update_bits(rt722, RT722_VENDOR_HDA_CTL, + RT722_GE_RELATED_CTL2, 0x4000, 0x4000); + } +- mutex_unlock(&rt722->calibrate_mutex); + } + + static int rt722_sdca_set_jack_detect(struct snd_soc_component *component, +@@ -352,8 +352,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + static const char func_tag[] = "FUNC"; + static const char xu_tag[] = "XU"; + const char *dmi_vendor, *dmi_product, *dmi_sku; +- char *cae_filename; +- const struct firmware *cae_fw = NULL; + unsigned int cae_st_spk, cae_st_hp, cae_st_mic; + unsigned int func, value; + unsigned int combined_val; +@@ -385,7 +383,8 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + space = strchr(dmi_sku, ' '); + s_len = space ? space - dmi_sku : strlen(dmi_sku); + +- cae_filename = kasprintf(GFP_KERNEL, ++ char *cae_filename __free(kfree) = ++ kasprintf(GFP_KERNEL, + "realtek/rt722/rt722_RAE_%.*s_%.*s_%.*s.dat", + v_len, dmi_vendor, + p_len, dmi_product, +@@ -399,8 +398,8 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + regmap_write(rt722->regmap, RT722_MIC_CAE_PARAM39, 0x5f); + usleep_range(50000, 60000); + ++ const struct firmware *cae_fw __free(firmware) = NULL; + request_firmware(&cae_fw, cae_filename, dev); +- kfree(cae_filename); + if (!cae_fw) { + dev_err(dev, "%s: Failed to load CAE firmware\n", __func__); + return -ENOENT; +@@ -555,7 +554,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + regcache_cache_bypass(rt722->regmap, false); + rt722->cae_update_done = 1; + dev_dbg(dev, "%s: CAE FW update done.\n", __func__); +- release_firmware(cae_fw); + return 0; + + verify_abort: +@@ -565,7 +563,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + out_release: + rt722_sdca_index_update_bits(rt722, RT722_VENDOR_REG, + RT722_MISC_CTRL1, 0x8000, 0x0000); +- release_firmware(cae_fw); + dev_err(dev, "%s: CAE FW update aborted (ret=%d).\n", __func__, ret); + return ret; + } +@@ -1906,6 +1903,9 @@ int rt722_sdca_io_init(struct device *dev, struct sdw_slave *slave) + rt722_sdca_amp_preset(rt722); + rt722_sdca_jack_preset(rt722); + ++ if (rt722->hs_jack && (!rt722->first_hw_init)) ++ rt722_sdca_jack_init(rt722); ++ + if (rt722->first_hw_init) { + regcache_cache_bypass(rt722->regmap, false); + regcache_mark_dirty(rt722->regmap); +diff --git a/sound/soc/codecs/rt766-sdca-sdw.c b/sound/soc/codecs/rt766-sdca-sdw.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca-sdw.c +@@ -0,0 +1,339 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// rt766-sdca-sdw.c -- rt766 SDCA ALSA SoC audio driver ++// ++// Copyright(c) 2026 Realtek Semiconductor Corp. ++// ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "rt766-sdca.h" ++#include "rt766-sdca-sdw.h" ++ ++#define RT766_PROBE_TIMEOUT 5000 ++ ++static bool rt766_sdca_readable_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4: ++ case RT766_VERSION_ID ... RT766_BOND_LATCH_ID: ++ case 0xc344 ... 0xc345: ++ case 0xc900: ++ case 0xc920: ++ case 0xd540 ... 0xd542: ++ case 0xf01e: ++ case RT766_HP_POWER_STATE ... RT766_HP_FSM_CTL2_1: ++ case 0x310100: ++ case RT766_MCU_PATCH_ADDR1_START ... RT766_MCU_PATCH_ADDR1_END: ++ case RT766_MCU_PATCH_ADDR2_START ... RT766_MCU_PATCH_ADDR2_END: ++ case RT766_MUTE_REG(UAJ, USER_FU41, 1): ++ case RT766_MUTE_REG(UAJ, USER_FU41, 2): ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 2): ++ case RT766_MUTE_REG(UAJ, USER_FU36, 1): ++ case RT766_MUTE_REG(UAJ, USER_FU36, 2): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 2): ++ case RT766_PDE_REQ_REG(UAJ, PDE47): ++ case RT766_PDE_REQ_REG(UAJ, PDE34): ++ case RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_FUNC_STATUS_REG(UAJ): /* 0x40480000 */ ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE47): /* 0x40481400 */ ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE34): /* 0x40481480 */ ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1): ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2): ++ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE): ++ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE): /* 0x40600490 */ ++ case RT766_PDE_REQ_REG(MIC, PDE11): ++ case RT766_MUTE_REG(MIC, USER_FU113, 1): ++ case RT766_MUTE_REG(MIC, USER_FU113, 2): ++ case RT766_MUTE_REG(MIC, USER_FU113, 3): ++ case RT766_MUTE_REG(MIC, USER_FU113, 4): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 1): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 2): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 3): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 4): ++ case RT766_FUNC_STATUS_REG(MIC): /* 0x40880000 */ ++ case RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_PDE_ACTUAL_REG(MIC, PDE11): /* 0x40881500 */ ++ case RT766_FUNC_STATUS_REG(HID): /* 0x40c80000 */ ++ /* 0x40c80080 - 0x40c80098 */ ++ case RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER) ... ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH): ++ case RT766_MUTE_REG(AMP, USER_FU21, 1): ++ case RT766_MUTE_REG(AMP, USER_FU21, 2): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 1): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 2): ++ case RT766_PDE_REQ_REG(AMP, PDE23): ++ case RT766_FUNC_STATUS_REG(AMP): /* 0x41080000 */ ++ case RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER): ++ case RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_PDE_ACTUAL_REG(AMP, PDE23): /* 0x41081980 */ ++ case RT766_BUF_ADDR_HID1 ... RT766_BUF_ADDR_HID2: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static bool rt766_sdca_volatile_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4: ++ case RT766_VERSION_ID ... RT766_BOND_LATCH_ID: ++ case 0xc344 ... 0xc345: ++ case 0xc900: ++ case 0xc920: ++ case 0xd540 ... 0xd542: ++ case 0xf01e: ++ case RT766_HP_POWER_STATE ... RT766_HP_FSM_CTL2_1: ++ case 0x310100: ++ case RT766_MCU_PATCH_ADDR1_START ... RT766_MCU_PATCH_ADDR1_END: ++ case RT766_MCU_PATCH_ADDR2_START ... RT766_MCU_PATCH_ADDR2_END: ++ case RT766_FUNC_STATUS_REG(UAJ): ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE47): ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE34): ++ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE): ++ case RT766_FUNC_STATUS_REG(MIC): ++ case RT766_PDE_ACTUAL_REG(MIC, PDE11): ++ case RT766_FUNC_STATUS_REG(HID): ++ case RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER) ... ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH): ++ case RT766_FUNC_STATUS_REG(AMP): ++ case RT766_PDE_ACTUAL_REG(AMP, PDE23): ++ case RT766_BUF_ADDR_HID1 ... RT766_BUF_ADDR_HID2: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static int rt766_sdca_mbq_size(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 2): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 2): ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1): ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 1): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 2): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 3): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 4): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 1): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 2): ++ return 2; ++ default: ++ return 1; ++ } ++} ++ ++static const struct regmap_sdw_mbq_cfg rt766_sdca_mbq_cfg = { ++ .mbq_size = rt766_sdca_mbq_size, ++}; ++ ++static const struct regmap_config rt766_sdca_regmap = { ++ .reg_bits = 32, ++ .val_bits = 16, ++ .readable_reg = rt766_sdca_readable_register, ++ .volatile_reg = rt766_sdca_volatile_register, ++ .reg_defaults = rt766_sdca_defaults, ++ .num_reg_defaults = ARRAY_SIZE(rt766_sdca_defaults), ++ .max_register = SDW_SDCA_MAX_REGISTER, ++ .cache_type = REGCACHE_MAPLE, ++ .use_single_read = true, ++ .use_single_write = true, ++}; ++ ++static int rt766_sdca_update_status(struct sdw_slave *slave, ++ enum sdw_slave_status status) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(&slave->dev); ++ ++ if (status == SDW_SLAVE_UNATTACHED) ++ rt766->hw_init = false; ++ ++ if (status == SDW_SLAVE_ATTACHED) { ++ if (rt766->hs_jack) { ++ /* ++ * Due to the SCP_SDCA_INTMASK will be cleared by any reset, and then ++ * if the device attached again, we will need to set the setting back. ++ * It could avoid losing the jack detection interrupt. ++ * This also could sync with the cache value as the rt766_sdca_jack_init set. ++ */ ++ sdw_write_no_pm(rt766->slave, SDW_SCP_SDCA_INTMASK3, ++ SDW_SCP_SDCA_INTMASK_SDCA_16); ++ sdw_write_no_pm(rt766->slave, SDW_SCP_SDCA_INTMASK4, ++ SDW_SCP_SDCA_INTMASK_SDCA_24); ++ } ++ } ++ ++ /* ++ * Perform initialization only if slave status is present and ++ * hw_init flag is false ++ */ ++ if (rt766->hw_init || status != SDW_SLAVE_ATTACHED) ++ return 0; ++ ++ /* perform I/O transfers required for Slave initialization */ ++ return rt766_sdca_io_init(&slave->dev, slave); ++} ++ ++static int rt766_sdca_read_prop(struct sdw_slave *slave) ++{ ++ struct sdw_slave_prop *prop = &slave->prop; ++ int ret; ++ ++ ret = sdw_slave_read_prop(slave); ++ if (ret < 0) ++ return ret; ++ ++ prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; ++ prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; ++ /* ++ * SDCA interrupts are routed through SoundWire domain IRQ. ++ */ ++ prop->use_domain_irq = true; ++ ++ return 0; ++} ++ ++static const struct sdw_slave_ops rt766_sdca_slave_ops = { ++ .read_prop = rt766_sdca_read_prop, ++ .update_status = rt766_sdca_update_status, ++}; ++ ++static int rt766_sdca_sdw_probe(struct sdw_slave *slave, ++ const struct sdw_device_id *id) ++{ ++ struct regmap *regmap; ++ ++ /* Regmap Initialization */ ++ regmap = devm_regmap_init_sdw_mbq_cfg(&slave->dev, slave, ++ &rt766_sdca_regmap, &rt766_sdca_mbq_cfg); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); ++ ++ return rt766_sdca_init(&slave->dev, regmap, slave); ++} ++ ++static void rt766_sdca_sdw_remove(struct sdw_slave *slave) ++{ ++ pm_runtime_disable(&slave->dev); ++} ++ ++static const struct sdw_device_id rt766_sdca_id[] = { ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x3, 0x1, 0), ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x3, 0x1, 0), ++ {}, ++}; ++MODULE_DEVICE_TABLE(sdw, rt766_sdca_id); ++ ++static int rt766_sdca_dev_suspend(struct device *dev) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ ++ if (!rt766->hw_init) ++ return 0; ++ ++ regcache_cache_only(rt766->regmap, true); ++ return 0; ++} ++ ++static int rt766_sdca_dev_system_suspend(struct device *dev) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ struct sdw_slave *slave = dev_to_sdw_dev(dev); ++ int ret1, ret2; ++ ++ if (!rt766->hw_init) ++ return 0; ++ ++ /* ++ * prevent new interrupts from being handled after the ++ * deferred work completes and before the parent disables ++ * interrupts on the link ++ */ ++ mutex_lock(&rt766->disable_irq_lock); ++ rt766->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK3, ++ SDW_SCP_SDCA_INTMASK_SDCA_16, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK4, ++ SDW_SCP_SDCA_INTMASK_SDCA_24, 0); ++ mutex_unlock(&rt766->disable_irq_lock); ++ ++ if (ret1 < 0 || ret2 < 0) { ++ /* log but don't prevent suspend from happening */ ++ dev_dbg(&slave->dev, "%s: could not disable SDCA interrupts\n:", __func__); ++ } ++ ++ return rt766_sdca_dev_suspend(dev); ++} ++ ++static int rt766_sdca_dev_resume(struct device *dev) ++{ ++ struct sdw_slave *slave = dev_to_sdw_dev(dev); ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ int ret; ++ ++ if (!rt766->first_hw_init) ++ return 0; ++ ++ if (!slave->unattach_request) { ++ mutex_lock(&rt766->disable_irq_lock); ++ if (rt766->disable_irq == true) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK3, SDW_SCP_SDCA_INTMASK_SDCA_16); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK4, SDW_SCP_SDCA_INTMASK_SDCA_24); ++ rt766->disable_irq = false; ++ } ++ mutex_unlock(&rt766->disable_irq_lock); ++ goto regmap_sync; ++ } ++ ++ ret = sdw_slave_wait_for_init(slave, RT766_PROBE_TIMEOUT); ++ if (ret) { ++ sdw_show_ping_status(slave->bus, true); ++ return ret; ++ } ++ ++regmap_sync: ++ regcache_cache_only(rt766->regmap, false); ++ ret = regcache_sync(rt766->regmap); ++ if (ret) { ++ regcache_cache_only(rt766->regmap, true); ++ regcache_mark_dirty(rt766->regmap); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops rt766_sdca_pm = { ++ SYSTEM_SLEEP_PM_OPS(rt766_sdca_dev_system_suspend, rt766_sdca_dev_resume) ++ RUNTIME_PM_OPS(rt766_sdca_dev_suspend, rt766_sdca_dev_resume, NULL) ++}; ++ ++static struct sdw_driver rt766_sdca_sdw_driver = { ++ .driver = { ++ .name = "rt766-sdca", ++ .pm = pm_ptr(&rt766_sdca_pm), ++ }, ++ .probe = rt766_sdca_sdw_probe, ++ .remove = rt766_sdca_sdw_remove, ++ .ops = &rt766_sdca_slave_ops, ++ .id_table = rt766_sdca_id, ++}; ++module_sdw_driver(rt766_sdca_sdw_driver); ++ ++MODULE_DESCRIPTION("ASoC RT766 SDCA SDW driver"); ++MODULE_AUTHOR("Shuming Fan "); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("SND_SOC_SDCA"); +diff --git a/sound/soc/codecs/rt766-sdca-sdw.h b/sound/soc/codecs/rt766-sdca-sdw.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca-sdw.h +@@ -0,0 +1,61 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * rt766-sdca-sdw.h -- RT766 SDCA ALSA SoC audio driver header ++ * ++ * Copyright(c) 2026 Realtek Semiconductor Corp. ++ */ ++ ++#ifndef __RT766_SDW_H__ ++#define __RT766_SDW_H__ ++ ++#include ++#include ++ ++static const struct reg_default rt766_sdca_defaults[] = { ++ /* 0x40400289 - 0x4040028a */ ++ { RT766_MUTE_REG(UAJ, USER_FU41, 1), 0x01 }, ++ { RT766_MUTE_REG(UAJ, USER_FU41, 2), 0x01 }, ++ /* 0x40400291 - 0x40400292 */ ++ { RT766_VOLUME_REG(UAJ, USER_FU41, 1), 0x0000 }, ++ { RT766_VOLUME_REG(UAJ, USER_FU41, 2), 0x0000 }, ++ /* 0x40400789 - 0x4040078a */ ++ { RT766_MUTE_REG(UAJ, USER_FU36, 1), 0x01 }, ++ { RT766_MUTE_REG(UAJ, USER_FU36, 2), 0x01 }, ++ /* 0x40400791 - 0x40400792 */ ++ { RT766_VOLUME_REG(UAJ, USER_FU36, 1), 0x0000 }, ++ { RT766_VOLUME_REG(UAJ, USER_FU36, 2), 0x0000 }, ++ { RT766_PDE_REQ_REG(UAJ, PDE47), 0x03 }, /* 0x40401408 */ ++ { RT766_PDE_REQ_REG(UAJ, PDE34), 0x03 }, /* 0x40401488 */ ++ { RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x40480080 */ ++ { RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x40480880 */ ++ /* 0x40600259 - 0x4060025a */ ++ { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1), 0xfe00 }, ++ { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2), 0xfe00 }, ++ { RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE), 0x00 }, /* 0x40600488 */ ++ ++ { RT766_PDE_REQ_REG(MIC, PDE11), 0x03 }, /* 0x40801508 */ ++ /* 0x40801809 - 0x4080180c */ ++ { RT766_MUTE_REG(MIC, USER_FU113, 1), 0x01 }, ++ { RT766_MUTE_REG(MIC, USER_FU113, 2), 0x01 }, ++ { RT766_MUTE_REG(MIC, USER_FU113, 3), 0x01 }, ++ { RT766_MUTE_REG(MIC, USER_FU113, 4), 0x01 }, ++ /* 0x40801811 - 0x40801814 */ ++ { RT766_VOLUME_REG(MIC, USER_FU113, 1), 0x0000 }, ++ { RT766_VOLUME_REG(MIC, USER_FU113, 2), 0x0000 }, ++ { RT766_VOLUME_REG(MIC, USER_FU113, 3), 0x0000 }, ++ { RT766_VOLUME_REG(MIC, USER_FU113, 4), 0x0000 }, ++ { RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x40880900 */ ++ ++ /* 0x41000189 - 0x4100018a */ ++ { RT766_MUTE_REG(AMP, USER_FU21, 1), 0x01 }, ++ { RT766_MUTE_REG(AMP, USER_FU21, 2), 0x01 }, ++ /* 0x41000191 - 0x41000192 */ ++ { RT766_VOLUME_REG(AMP, USER_FU21, 1), 0x0000 }, ++ { RT766_VOLUME_REG(AMP, USER_FU21, 2), 0x0000 }, ++ { RT766_PDE_REQ_REG(AMP, PDE23), 0x03 }, /* 0x41001988 */ ++ { RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER), 0x00 }, /* 0x41080200 */ ++ { RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x41081080 */ ++ ++}; ++ ++#endif /* __RT766_SDW_H__ */ +diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca.c +@@ -0,0 +1,1378 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// rt766-sdca.c -- rt766 SDCA ALSA SoC audio driver ++// ++// Copyright(c) 2026 Realtek Semiconductor Corp. ++// ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "rt766-sdca.h" ++#include "rt-sdw-common.h" ++ ++static int rt766_sdca_btn_detect(struct sdca_interrupt *interrupt) ++{ ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ unsigned char *buf = NULL; ++ unsigned int offset, owner, length; ++ unsigned int det_mode, idx, val; ++ int ret; ++ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE), ++ &det_mode); ++ if (ret < 0) ++ goto io_error; ++ ++ /* get current UMP message owner */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER), ++ &owner); ++ if (ret < 0) ++ goto io_error; ++ ++ /* if owner is device then there is no button event from device */ ++ if (owner == 1) ++ return 0; ++ ++ if (det_mode) { ++ /* read UMP message length */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH), ++ &length); ++ if (ret < 0) ++ goto _end_btn_det_; ++ ++ /* read UMP message offset */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGEOFFSET), ++ &offset); ++ if (ret < 0) ++ goto _end_btn_det_; ++ ++ buf = devm_kzalloc(&rt766->slave->dev, length, GFP_KERNEL); ++ if (!buf) { ++ dev_err(&rt766->slave->dev, "%s: alloc buf failed\n", __func__); ++ goto _end_btn_det_; ++ } ++ ++ for (idx = 0; idx < length; idx++) { ++ ret = regmap_read(rt766->regmap, ++ RT766_BUF_ADDR_HID1 + offset + idx, &val); ++ if (ret < 0) ++ goto _end_btn_det_; ++ buf[idx] = val & 0xff; ++ } ++ ++ if (rt766->hid) ++ hid_input_report(rt766->hid, HID_INPUT_REPORT, ++ buf, length, 1); ++ } ++ ++_end_btn_det_: ++ if (buf) ++ devm_kfree(&rt766->slave->dev, buf); ++ ++ /* Host is owner, so set back to device */ ++ if (owner == 0) { ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER), 0x01); ++ } ++ ++ return 0; ++ ++io_error: ++ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); ++ return ret; ++} ++ ++static irqreturn_t rt766_sdca_irq_btn_handler(int irq, void *data) ++{ ++ struct sdca_interrupt *interrupt = data; ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ ++ if (!rt766->hs_jack) ++ return IRQ_HANDLED; ++ ++ if (!rt766->component->card || !rt766->component->card->instantiated) ++ return IRQ_HANDLED; ++ ++ mutex_lock(&rt766->disable_irq_lock); ++ if (!rt766->disable_irq) ++ rt766_sdca_btn_detect(interrupt); ++ mutex_unlock(&rt766->disable_irq_lock); ++ return IRQ_HANDLED; ++} ++ ++static int rt766_sdca_headset_detect(struct rt766_sdca_priv *rt766) ++{ ++ unsigned int det_mode; ++ int ret; ++ ++ /* get detected_mode */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE), ++ &det_mode); ++ if (ret < 0) ++ goto io_error; ++ ++ switch (det_mode) { ++ case 0x00: ++ rt766->jack_type = 0; ++ break; ++ case 0x03: ++ rt766->jack_type = SND_JACK_HEADPHONE; ++ break; ++ case 0x05: ++ rt766->jack_type = SND_JACK_HEADSET; ++ break; ++ } ++ ++ /* write selected_mode */ ++ if (det_mode) { ++ ret = regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE), ++ det_mode); ++ if (ret < 0) ++ goto io_error; ++ } ++ ++ dev_dbg(&rt766->slave->dev, ++ "%s, detected_mode=0x%x\n", __func__, det_mode); ++ ++ return 0; ++ ++io_error: ++ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); ++ return ret; ++} ++ ++static irqreturn_t rt766_sdca_irq_jd_handler(int irq, void *data) ++{ ++ struct sdca_interrupt *interrupt = data; ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ ++ if (!rt766->hs_jack) ++ return IRQ_HANDLED; ++ ++ if (!rt766->component->card || !rt766->component->card->instantiated) ++ return IRQ_HANDLED; ++ ++ mutex_lock(&rt766->disable_irq_lock); ++ if (!rt766->disable_irq) ++ rt766_sdca_headset_detect(rt766); ++ mutex_unlock(&rt766->disable_irq_lock); ++ ++ dev_dbg(&rt766->slave->dev, ++ "in %s, jack_type=%d\n", __func__, rt766->jack_type); ++ ++ snd_soc_jack_report(rt766->hs_jack, rt766->jack_type, SND_JACK_HEADSET); ++ return IRQ_HANDLED; ++} ++ ++static void rt766_sdca_destroy_hid_device(struct sdca_interrupt *interrupt) ++{ ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ ++ hid_destroy_device(rt766->hid); ++} ++ ++static int rt766_sdca_irq_ctl(struct rt766_sdca_priv *rt766, ++ struct sdca_function_data *function, ++ struct snd_soc_component *component, ++ struct sdca_interrupt_info *info, ++ bool enabled) ++{ ++ struct device *dev = &rt766->slave->dev; ++ struct sdca_interrupt *interrupt; ++ struct sdca_control *control; ++ struct sdca_entity *entity; ++ irq_handler_t handler; ++ int i, j, irq, ret; ++ ++ for (i = 0; i < function->num_entities; i++) { ++ entity = &function->entities[i]; ++ ++ for (j = 0; j < entity->num_controls; j++) { ++ control = &entity->controls[j]; ++ irq = control->interrupt_position; ++ ++ switch (SDCA_CTL_TYPE(entity->type, control->sel)) { ++ case SDCA_CTL_TYPE_S(GE, DETECTED_MODE): ++ handler = rt766_sdca_irq_jd_handler; ++ break; ++ case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER): ++ handler = rt766_sdca_irq_btn_handler; ++ break; ++ default: ++ continue; ++ } ++ ++ interrupt = &info->irqs[irq]; ++ ++ if (enabled) { ++ ret = sdca_irq_data_populate(dev, rt766->regmap, component, ++ function, entity, control, ++ interrupt); ++ if (ret) ++ return ret; ++ ++ if (handler == rt766_sdca_irq_btn_handler) { ++ ret = sdca_add_hid_device(interrupt); ++ if (ret) ++ return ret; ++ ++ interrupt->free_priv = rt766_sdca_destroy_hid_device; ++ rt766->hid = interrupt->priv; ++ } ++ ++ interrupt->priv = rt766; ++ ret = sdca_irq_request(dev, info, irq, interrupt->name, ++ handler, interrupt); ++ if (ret) { ++ dev_err(dev, "failed to request irq %s: %d\n", ++ interrupt->name, ret); ++ sdca_irq_cleanup_late(dev, function, info); ++ return ret; ++ } ++ dev_dbg(dev, "Requesting IRQ %d InterruptName=%s\n", irq, interrupt->name); ++ } else { ++ sdca_irq_cleanup_late(dev, function, info); ++ dev_dbg(dev, "Freeing IRQ %d\n", irq); ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++static int rt766_sdca_set_jack_detect(struct snd_soc_component *component, ++ struct snd_soc_jack *hs_jack, void *data) ++{ ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int ret; ++ ++ if (!rt766->uaj_func_data) { ++ dev_err(&rt766->slave->dev, "The SDCA UAJ function is not supported.\n"); ++ return -EINVAL; ++ } ++ ++ rt766->hs_jack = hs_jack; ++ ++ if (!rt766->first_hw_init) ++ return 0; ++ ++ ret = pm_runtime_resume_and_get(component->dev); ++ if (ret < 0) { ++ if (ret != -EACCES) { ++ dev_err(component->dev, "%s: failed to resume %d\n", __func__, ret); ++ return ret; ++ } ++ ++ /* pm_runtime not enabled yet */ ++ dev_dbg(component->dev, "%s: skipping jack init for now\n", __func__); ++ return 0; ++ } ++ ++ /* disable interrupts if hs_jack is not set */ ++ if (!rt766->hs_jack) { ++ if (rt766->uaj_func_data) ++ rt766_sdca_irq_ctl(rt766, rt766->uaj_func_data, ++ rt766->component, rt766->irq_info, false); ++ ++ if (rt766->hid_func_data) ++ rt766_sdca_irq_ctl(rt766, rt766->hid_func_data, ++ rt766->component, rt766->irq_info, false); ++ } ++ ++ pm_runtime_put_autosuspend(component->dev); ++ ++ return 0; ++} ++ ++static int rt766_sdca_set_fu_ctl(struct rt766_sdca_priv *rt766, int func_num, int fu_num) ++{ ++ unsigned int fu01_reg, fu02_reg; ++ unsigned int ch_01, ch_02; ++ unsigned int ch_mute; ++ unsigned int fu_reg; ++ int err, i; ++ ++ switch (fu_num) { ++ case RT766_SDCA_ENT_USER_FU41: ++ ch_01 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_l_mute) ? 0x01 : 0x00; ++ ch_02 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_r_mute) ? 0x01 : 0x00; ++ break; ++ case RT766_SDCA_ENT_USER_FU36: ++ ch_01 = (rt766->fu36_dapm_mute || rt766->fu36_mixer_l_mute) ? 0x01 : 0x00; ++ ch_02 = (rt766->fu36_dapm_mute || rt766->fu36_mixer_r_mute) ? 0x01 : 0x00; ++ break; ++ case RT766_SDCA_ENT_USER_FU21: ++ ch_01 = (rt766->fu21_dapm_mute || rt766->fu21_mixer_l_mute) ? 0x01 : 0x00; ++ ch_02 = (rt766->fu21_dapm_mute || rt766->fu21_mixer_r_mute) ? 0x01 : 0x00; ++ break; ++ case RT766_SDCA_ENT_USER_FU113: ++ for (i = 0; i < ARRAY_SIZE(rt766->fu113_mixer_mute); i++) { ++ ch_mute = (rt766->fu113_dapm_mute || rt766->fu113_mixer_mute[i]) ? 0x01 : 0x00; ++ fu_reg = SDW_SDCA_CTL(func_num, fu_num, SDCA_CTL_FU_MUTE, 1) + i; ++ err = regmap_write(rt766->regmap, fu_reg, ch_mute); ++ if (err < 0) ++ return err; ++ } ++ return 0; ++ } ++ ++ fu01_reg = SDW_SDCA_CTL(func_num, fu_num, SDCA_CTL_FU_MUTE, 1); ++ fu02_reg = SDW_SDCA_CTL(func_num, fu_num, SDCA_CTL_FU_MUTE, 2); ++ err = regmap_write(rt766->regmap, fu01_reg, ch_01); ++ if (err < 0) ++ return err; ++ err = regmap_write(rt766->regmap, fu02_reg, ch_02); ++ if (err < 0) ++ return err; ++ ++ return 0; ++} ++ ++static int rt766_sdca_fu41_playback_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = !rt766->fu41_mixer_l_mute; ++ ucontrol->value.integer.value[1] = !rt766->fu41_mixer_r_mute; ++ return 0; ++} ++ ++static int rt766_sdca_fu41_playback_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err; ++ ++ if (rt766->fu41_mixer_l_mute == !ucontrol->value.integer.value[0] && ++ rt766->fu41_mixer_r_mute == !ucontrol->value.integer.value[1]) ++ return 0; ++ ++ rt766->fu41_mixer_l_mute = !ucontrol->value.integer.value[0]; ++ rt766->fu41_mixer_r_mute = !ucontrol->value.integer.value[1]; ++ ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU41); ++ if (err < 0) ++ return err; ++ ++ return 1; ++} ++ ++static int rt766_sdca_fu36_capture_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = !rt766->fu36_mixer_l_mute; ++ ucontrol->value.integer.value[1] = !rt766->fu36_mixer_r_mute; ++ return 0; ++} ++ ++static int rt766_sdca_fu36_capture_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err; ++ ++ if (rt766->fu36_mixer_l_mute == !ucontrol->value.integer.value[0] && ++ rt766->fu36_mixer_r_mute == !ucontrol->value.integer.value[1]) ++ return 0; ++ ++ rt766->fu36_mixer_l_mute = !ucontrol->value.integer.value[0]; ++ rt766->fu36_mixer_r_mute = !ucontrol->value.integer.value[1]; ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU36); ++ if (err < 0) ++ return err; ++ ++ return 1; ++} ++ ++static int rt766_sdca_fu21_playback_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = !rt766->fu21_mixer_l_mute; ++ ucontrol->value.integer.value[1] = !rt766->fu21_mixer_r_mute; ++ return 0; ++} ++ ++static int rt766_sdca_fu21_playback_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err; ++ ++ if (rt766->fu21_mixer_l_mute == !ucontrol->value.integer.value[0] && ++ rt766->fu21_mixer_r_mute == !ucontrol->value.integer.value[1]) ++ return 0; ++ ++ rt766->fu21_mixer_l_mute = !ucontrol->value.integer.value[0]; ++ rt766->fu21_mixer_r_mute = !ucontrol->value.integer.value[1]; ++ ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_USER_FU21); ++ if (err < 0) ++ return err; ++ ++ return 1; ++} ++ ++static int rt766_sdca_fu113_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu113_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_USER_FU113); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu113_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_USER_FU113); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_dmic_set_gain_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct rt_sdca_dmic_kctrl_priv *p = ++ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value; ++ const unsigned int interval_offset = 0xc0; ++ unsigned int regvalue, ctl, i; ++ ++ /* check all channels */ ++ for (i = 0; i < p->count; i++) { ++ regmap_read(rt766->regmap, p->reg_base + i, ®value); ++ ctl = p->max - (((0x1e00 - regvalue) & 0xffff) / interval_offset); ++ ++ ucontrol->value.integer.value[i] = ctl; ++ } ++ ++ return 0; ++} ++ ++static int rt766_sdca_dmic_set_gain_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt_sdca_dmic_kctrl_priv *p = ++ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value; ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ const unsigned int interval_offset = 0xc0; ++ unsigned int gain_val[4]; ++ unsigned int i, changed = 0; ++ unsigned int regvalue[4]; ++ int err; ++ ++ /* check all channels */ ++ for (i = 0; i < p->count; i++) { ++ regmap_read(rt766->regmap, p->reg_base + i, ®value[i]); ++ ++ gain_val[i] = ucontrol->value.integer.value[i]; ++ if (gain_val[i] > p->max) ++ gain_val[i] = p->max; ++ ++ gain_val[i] = 0x1e00 - ((p->max - gain_val[i]) * interval_offset); ++ gain_val[i] &= 0xffff; ++ ++ if (regvalue[i] != gain_val[i]) ++ changed = 1; ++ } ++ ++ if (!changed) ++ return 0; ++ ++ for (i = 0; i < p->count; i++) { ++ err = regmap_write(rt766->regmap, p->reg_base + i, gain_val[i]); ++ if (err < 0) ++ dev_err(&rt766->slave->dev, "0x%08x can't be set\n", p->reg_base + i); ++ } ++ ++ return changed; ++} ++ ++static int rt766_sdca_dmic_fu113_capture_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ unsigned int i; ++ ++ for (i = 0; i < 4; i++) ++ ucontrol->value.integer.value[i] = !rt766->fu113_mixer_mute[i]; ++ return 0; ++} ++ ++static int rt766_sdca_dmic_fu113_capture_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err, changed = 0, i; ++ ++ for (i = 0; i < 4; i++) { ++ if (rt766->fu113_mixer_mute[i] != !ucontrol->value.integer.value[i]) ++ changed = 1; ++ rt766->fu113_mixer_mute[i] = !ucontrol->value.integer.value[i]; ++ } ++ ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_USER_FU113); ++ if (err < 0) ++ return err; ++ return changed; ++} ++ ++static int rt766_sdca_fu41_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu41_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU41); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu41_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU41); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_pde_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event, int func_num, int pde_num, const char *pde_ent) ++{ ++ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct sdca_function_data *func_data; ++ unsigned char ps0 = 0x0, ps3 = 0x3; ++ const struct sdca_entity *entity; ++ unsigned int pde_req_reg; ++ int from_ps, to_ps; ++ int ret; ++ ++ pde_req_reg = SDW_SDCA_CTL(func_num, pde_num, SDCA_CTL_PDE_REQUESTED_PS, 0); ++ ++ switch (func_num) { ++ case RT766_FUNC_NUM_UAJ: ++ func_data = rt766->uaj_func_data; ++ break; ++ case RT766_FUNC_NUM_AMP: ++ func_data = rt766->sa_func_data; ++ break; ++ case RT766_FUNC_NUM_MIC: ++ func_data = rt766->sm_func_data; ++ break; ++ default: ++ dev_err(component->dev, "%s: unsupported func_num %d\n", ++ __func__, func_num); ++ return -EINVAL; ++ } ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ regmap_write(rt766->regmap, pde_req_reg, ps0); ++ from_ps = ps3; ++ to_ps = ps0; ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ regmap_write(rt766->regmap, pde_req_reg, ps3); ++ from_ps = ps0; ++ to_ps = ps3; ++ break; ++ } ++ ++ entity = sdca_find_entity_by_label(func_data, pde_ent); ++ if (!entity) { ++ dev_err(component->dev, "%s: failed to find entity %s\n", ++ __func__, pde_ent); ++ return -EINVAL; ++ } ++ ++ ret = sdca_asoc_pde_poll_actual_ps(rt766->regmap, ++ func_num, ++ pde_num, ++ from_ps, to_ps, ++ entity->pde.max_delay, ++ entity->pde.num_max_delay); ++ if (ret) ++ dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n", ++ __func__, from_ps, to_ps, ret); ++ ++ return ret; ++} ++ ++static int rt766_sdca_pde47_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_PDE47, "PDE 47"); ++} ++ ++static int rt766_sdca_fu36_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu36_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU36); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu36_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU36); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_pde34_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_PDE34, "PDE 34"); ++} ++ ++static int rt766_sdca_fu21_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu21_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_USER_FU21); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu21_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_USER_FU21); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_pde23_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_PDE23, "PDE 23"); ++} ++ ++static int rt766_sdca_pde11_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_PDE11, "PDE 11"); ++} ++ ++static int rt766_dmic_fu_info(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_info *uinfo) ++{ ++ struct rt_sdca_dmic_kctrl_priv *p = ++ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value; ++ ++ if (p->max == 1) ++ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; ++ else ++ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; ++ uinfo->count = p->count; ++ uinfo->value.integer.min = 0; ++ uinfo->value.integer.max = p->max; ++ return 0; ++} ++ ++static const char * const rt766_rx_data_ch_select[] = { ++ "L,R", ++ "R,L", ++ "L,L", ++ "R,R", ++ "L,L+R", ++ "R,L+R", ++ "L+R,L", ++ "L+R,R", ++ "L+R,L+R", ++}; ++ ++static SOC_ENUM_SINGLE_DECL(rt766_rx_data_ch_enum, ++ RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER), 0, ++ rt766_rx_data_ch_select); ++ ++static const DECLARE_TLV_DB_SCALE(hp_vol_tlv, -9525, 75, 0); ++static const DECLARE_TLV_DB_SCALE(spk_vol_tlv, -6525, 75, 0); ++static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, -1725, 75, 0); ++static const DECLARE_TLV_DB_SCALE(boost_vol_tlv, -200, 200, 0); ++ ++#define RT766_P75DB_STEP 0xC0 /* 0.75 dB in Q7.8 format */ ++#define RT766_2DB_STEP 0x200 /* 2 dB in Q7.8 format */ ++#define RT766_HP_VOL_MIN (-127) /* -95.25 dB / 0.75 dB step */ ++#define RT766_HS_VOL_MIN (-23) /* -17.25 dB / 0.75 dB step */ ++#define RT766_HS_BOOST_VOL_MIN (-1) /* -2 dB / 2 dB step */ ++#define RT766_SPK_VOL_MIN (-87) /* -65.25 dB / 0.75 dB step */ ++#define RT766_P_VOL_MAX 0 /* 0 dB / 0.75 dB step */ ++#define RT766_HS_VOL_MAX 40 /* 30 dB / 0.75 dB step */ ++#define RT766_HS_BOOST_VOL_MAX 20 /* 40 dB / 2 dB step */ ++ ++static const struct snd_kcontrol_new rt766_sdca_controls[] = { ++ SOC_DOUBLE_EXT("FU41 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0, ++ rt766_sdca_fu41_playback_get, rt766_sdca_fu41_playback_put), ++ SDCA_DOUBLE_Q78_TLV("FU41 Playback Volume", ++ RT766_VOLUME_REG(UAJ, USER_FU41, 1), ++ RT766_VOLUME_REG(UAJ, USER_FU41, 2), ++ RT766_HP_VOL_MIN, RT766_P_VOL_MAX, RT766_P75DB_STEP, hp_vol_tlv), ++ SOC_DOUBLE_EXT("FU36 Capture Switch", SND_SOC_NOPM, 0, 1, 1, 0, ++ rt766_sdca_fu36_capture_get, rt766_sdca_fu36_capture_put), ++ SDCA_DOUBLE_Q78_TLV("FU36 Capture Volume", ++ RT766_VOLUME_REG(UAJ, USER_FU36, 1), ++ RT766_VOLUME_REG(UAJ, USER_FU36, 2), ++ RT766_HS_VOL_MIN, RT766_HS_VOL_MAX, RT766_P75DB_STEP, mic_vol_tlv), ++ SDCA_DOUBLE_Q78_TLV("FU33 Boost Volume", ++ RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1), ++ RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2), ++ RT766_HS_BOOST_VOL_MIN, RT766_HS_BOOST_VOL_MAX, RT766_2DB_STEP, boost_vol_tlv), ++ ++ SOC_DOUBLE_EXT("FU21 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0, ++ rt766_sdca_fu21_playback_get, rt766_sdca_fu21_playback_put), ++ SDCA_DOUBLE_Q78_TLV("FU21 Playback Volume", ++ RT766_VOLUME_REG(AMP, USER_FU21, 1), ++ RT766_VOLUME_REG(AMP, USER_FU21, 2), ++ RT766_SPK_VOL_MIN, RT766_P_VOL_MAX, RT766_P75DB_STEP, spk_vol_tlv), ++ SOC_ENUM("RX Channel Select", rt766_rx_data_ch_enum), ++ ++ RT_SDCA_FU_CTRL("FU113 Capture Switch", ++ RT766_MUTE_REG(MIC, USER_FU113, 1), 1, 1, 4, rt766_dmic_fu_info, ++ rt766_sdca_dmic_fu113_capture_get, rt766_sdca_dmic_fu113_capture_put), ++ RT_SDCA_EXT_TLV("FU113 Capture Volume", ++ RT766_VOLUME_REG(MIC, USER_FU113, 1), ++ rt766_sdca_dmic_set_gain_get, rt766_sdca_dmic_set_gain_put, ++ 4, 0x3f, mic_vol_tlv, rt766_dmic_fu_info), ++}; ++ ++static const struct snd_soc_dapm_widget rt766_sdca_dapm_widgets[] = { ++ /* UAJ */ ++ SND_SOC_DAPM_OUTPUT("HP"), ++ SND_SOC_DAPM_INPUT("MIC2"), ++ SND_SOC_DAPM_SUPPLY("PDE 47", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde47_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_SUPPLY("PDE 34", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde34_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_DAC_E("FU 41", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu41_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_ADC_E("FU 36", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu36_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_AIF_IN("DP3RX", "DP3 Playback", 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("DP12TX", "DP12 Capture", 0, SND_SOC_NOPM, 0, 0), ++ ++ /* AMP */ ++ SND_SOC_DAPM_OUTPUT("SPOL"), ++ SND_SOC_DAPM_OUTPUT("SPOR"), ++ SND_SOC_DAPM_DAC_E("FU 21", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu21_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_SUPPLY("PDE 23", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde23_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_AIF_IN("DP1RX", "DP1 Playback", 0, SND_SOC_NOPM, 0, 0), ++ ++ /* DMIC */ ++ SND_SOC_DAPM_INPUT("DMIC1"), ++ SND_SOC_DAPM_INPUT("DMIC2"), ++ SND_SOC_DAPM_SUPPLY("PDE 11", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde11_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_ADC_E("FU 113", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu113_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_AIF_OUT("DP8TX", "DP8 Capture", 0, SND_SOC_NOPM, 0, 0), ++}; ++ ++static const struct snd_soc_dapm_route rt766_sdca_audio_map[] = { ++ { "FU 41", NULL, "DP3RX" }, ++ { "DP12TX", NULL, "FU 36" }, ++ { "FU 36", NULL, "PDE 34" }, ++ { "FU 36", NULL, "MIC2" }, ++ { "HP", NULL, "PDE 47" }, ++ { "HP", NULL, "FU 41" }, ++ ++ { "FU 21", NULL, "DP1RX" }, ++ { "FU 21", NULL, "PDE 23" }, ++ { "SPOL", NULL, "FU 21" }, ++ { "SPOR", NULL, "FU 21" }, ++ ++ {"DP8TX", NULL, "FU 113"}, ++ {"FU 113", NULL, "PDE 11"}, ++ {"FU 113", NULL, "DMIC1"}, ++ {"FU 113", NULL, "DMIC2"}, ++}; ++ ++static int rt766_sdca_probe(struct snd_soc_component *component) ++{ ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct device *dev = &rt766->slave->dev; ++ int ret; ++ ++ rt766->component = component; ++ ++ ret = pm_runtime_resume(component->dev); ++ if (ret < 0 && ret != -EACCES) ++ return ret; ++ ++ if (rt766->uaj_func_data) { ++ dev_dbg(dev, "%s : irq %d\n", __func__, rt766->slave->irq); ++ ++ rt766->irq_info = devm_sdca_irq_allocate(dev, rt766->regmap, rt766->slave->irq); ++ if (IS_ERR(rt766->irq_info)) ++ return PTR_ERR(rt766->irq_info); ++ ++ ret = rt766_sdca_irq_ctl(rt766, rt766->uaj_func_data, ++ component, rt766->irq_info, true); ++ if (ret < 0) { ++ dev_err(dev, "Failed to request UAJ SDCA IRQ: %d\n", ret); ++ return ret; ++ } ++ ++ if (rt766->hid_func_data) { ++ ret = rt766_sdca_irq_ctl(rt766, rt766->hid_func_data, ++ component, rt766->irq_info, true); ++ if (ret < 0) { ++ dev_err(dev, "Failed to request HID SDCA IRQ: %d\n", ret); ++ return ret; ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++static void rt766_sdca_remove(struct snd_soc_component *component) ++{ ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ sdca_irq_cleanup_late(component->dev, rt766->uaj_func_data, rt766->irq_info); ++ sdca_irq_cleanup_late(component->dev, rt766->hid_func_data, rt766->irq_info); ++} ++ ++static const struct snd_soc_component_driver soc_sdca_dev_rt766 = { ++ .probe = rt766_sdca_probe, ++ .remove = rt766_sdca_remove, ++ .controls = rt766_sdca_controls, ++ .num_controls = ARRAY_SIZE(rt766_sdca_controls), ++ .dapm_widgets = rt766_sdca_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(rt766_sdca_dapm_widgets), ++ .dapm_routes = rt766_sdca_audio_map, ++ .num_dapm_routes = ARRAY_SIZE(rt766_sdca_audio_map), ++ .set_jack = rt766_sdca_set_jack_detect, ++ .endianness = 1, ++}; ++ ++static int rt766_sdca_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, ++ int direction) ++{ ++ snd_soc_dai_dma_data_set(dai, direction, sdw_stream); ++ ++ return 0; ++} ++ ++static void rt766_sdca_shutdown(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ snd_soc_dai_set_dma_data(dai, substream, NULL); ++} ++ ++static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_component *component = dai->component; ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct sdw_stream_config stream_config; ++ struct sdw_port_config port_config; ++ enum sdw_data_direction direction; ++ struct sdw_stream_runtime *sdw_stream; ++ unsigned int sampling_rate; ++ int retval, port; ++ ++ dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); ++ sdw_stream = snd_soc_dai_get_dma_data(dai, substream); ++ ++ if (!sdw_stream) ++ return -EINVAL; ++ ++ if (!rt766->slave) ++ return -EINVAL; ++ ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); ++ ++ /* SoundWire specific configuration */ ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ direction = SDW_DATA_DIR_RX; ++ if (dai->id == RT766_AIF1) ++ port = 3; ++ else if (dai->id == RT766_AIF2) ++ port = 1; ++ else ++ return -EINVAL; ++ } else { ++ direction = SDW_DATA_DIR_TX; ++ if (dai->id == RT766_AIF1) ++ port = 12; ++ else if (dai->id == RT766_AIF3) ++ port = 8; ++ else ++ return -EINVAL; ++ } ++ ++ port_config.num = port; ++ retval = sdw_stream_add_slave(rt766->slave, &stream_config, ++ &port_config, 1, sdw_stream); ++ if (retval) { ++ dev_err(dai->dev, "%s: Unable to configure port\n", __func__); ++ return retval; ++ } ++ ++ if (params_channels(params) > 16) { ++ dev_err(component->dev, "%s: Unsupported channels %d\n", ++ __func__, params_channels(params)); ++ return -EINVAL; ++ } ++ ++ /* sampling rate configuration */ ++ switch (params_rate(params)) { ++ case 44100: ++ sampling_rate = RT766_SDCA_RATE_44100HZ; ++ break; ++ case 48000: ++ sampling_rate = RT766_SDCA_RATE_48000HZ; ++ break; ++ case 96000: ++ sampling_rate = RT766_SDCA_RATE_96000HZ; ++ break; ++ case 192000: ++ sampling_rate = RT766_SDCA_RATE_192000HZ; ++ break; ++ default: ++ dev_err(component->dev, "%s: Rate %d is not supported\n", ++ __func__, params_rate(params)); ++ return -EINVAL; ++ } ++ ++ /* set sampling frequency */ ++ switch (dai->id) { ++ case RT766_AIF1: ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ break; ++ case RT766_AIF2: ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ break; ++ case RT766_AIF3: ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ break; ++ default: ++ dev_err(component->dev, "%s: Wrong DAI id\n", __func__); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int rt766_sdca_pcm_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_component *component = dai->component; ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct sdw_stream_runtime *sdw_stream = ++ snd_soc_dai_get_dma_data(dai, substream); ++ ++ if (!rt766->slave) ++ return -EINVAL; ++ ++ sdw_stream_remove_slave(rt766->slave, sdw_stream); ++ return 0; ++} ++ ++#define RT766_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \ ++ SNDRV_PCM_RATE_192000) ++#define RT766_DAC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) ++#define RT766_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ ++ SNDRV_PCM_FMTBIT_S32_LE) ++ ++static const struct snd_soc_dai_ops rt766_sdca_ops = { ++ .hw_params = rt766_sdca_pcm_hw_params, ++ .hw_free = rt766_sdca_pcm_hw_free, ++ .set_stream = rt766_sdca_set_sdw_stream, ++ .shutdown = rt766_sdca_shutdown, ++}; ++ ++static struct snd_soc_dai_driver rt766_sdca_dai[] = { ++ { ++ .name = "rt766-sdca-aif1", ++ .id = RT766_AIF1, ++ .playback = { ++ .stream_name = "DP3 Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_DAC_FORMATS, ++ }, ++ .capture = { ++ .stream_name = "DP12 Capture", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_ADC_FORMATS, ++ }, ++ .ops = &rt766_sdca_ops, ++ .symmetric_rate = 1, ++ }, ++ { ++ .name = "rt766-sdca-aif2", ++ .id = RT766_AIF2, ++ .playback = { ++ .stream_name = "DP1 Playback", ++ .channels_min = 1, ++ .channels_max = 4, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_DAC_FORMATS, ++ }, ++ .ops = &rt766_sdca_ops, ++ }, ++ { ++ .name = "rt766-sdca-aif3", ++ .id = RT766_AIF3, ++ .capture = { ++ .stream_name = "DP8 Capture", ++ .channels_min = 1, ++ .channels_max = 4, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_ADC_FORMATS, ++ }, ++ .ops = &rt766_sdca_ops, ++ } ++}; ++ ++static unsigned int rt766_find_dt_rates(struct device *dev, struct sdca_function_data *function, ++ const char *label) ++{ ++ struct snd_soc_pcm_stream stream; ++ struct sdca_entity *entity; ++ int i, ret; ++ ++ for (i = 0; i < function->num_entities; i++) { ++ entity = &function->entities[i]; ++ ++ if (strcmp(entity->label, label)) ++ continue; ++ ++ /* Can't check earlier as only terminals have an iot member. */ ++ if (!entity->iot.is_dataport) ++ continue; ++ ++ ret = sdca_asoc_populate_rate_format(dev, function, entity, &stream); ++ if (ret < 0) { ++ dev_dbg(dev, "%s: failed to parse rates for entity %s\n", ++ __func__, entity->label); ++ return 0; ++ } ++ ++ dev_dbg(dev, "%s: %s supports rates 0x%08x\n", __func__, entity->label, stream.rates); ++ } ++ ++ return stream.rates; ++} ++ ++int rt766_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave *slave) ++{ ++ struct sdca_function_data *func_data_ptr; ++ struct snd_soc_dai_driver *dai_drv; ++ struct rt766_sdca_priv *rt766; ++ unsigned int rates; ++ int ret; ++ int i; ++ ++ rt766 = devm_kzalloc(dev, sizeof(*rt766), GFP_KERNEL); ++ if (!rt766) ++ return -ENOMEM; ++ ++ dev_set_drvdata(dev, rt766); ++ rt766->slave = slave; ++ rt766->regmap = regmap; ++ ++ regcache_cache_only(rt766->regmap, true); ++ ++ ret = devm_mutex_init(dev, &rt766->disable_irq_lock); ++ if (ret < 0) { ++ dev_err(dev, "Failed to initialize mutex\n"); ++ return ret; ++ } ++ ++ /* ++ * Mark hw_init to false ++ * HW init will be performed when device reports present ++ */ ++ rt766->hw_init = false; ++ rt766->first_hw_init = false; ++ rt766->fu41_dapm_mute = true; ++ rt766->fu41_mixer_l_mute = rt766->fu41_mixer_r_mute = false; ++ rt766->fu36_dapm_mute = true; ++ rt766->fu36_mixer_l_mute = rt766->fu36_mixer_r_mute = true; ++ rt766->fu21_dapm_mute = true; ++ rt766->fu21_mixer_l_mute = rt766->fu21_mixer_r_mute = false; ++ rt766->fu113_dapm_mute = true; ++ rt766->fu113_mixer_mute[0] = rt766->fu113_mixer_mute[1] = ++ rt766->fu113_mixer_mute[2] = rt766->fu113_mixer_mute[3] = true; ++ ++ dai_drv = devm_kzalloc(dev, sizeof(struct snd_soc_dai_driver) * ARRAY_SIZE(rt766_sdca_dai), GFP_KERNEL); ++ if (!dai_drv) { ++ dev_err(dev, "Failed to allocate memory for DAI driver\n"); ++ ret = -ENOMEM; ++ goto _sdw_init_err_; ++ } ++ memcpy(dai_drv, rt766_sdca_dai, sizeof(struct snd_soc_dai_driver) * ARRAY_SIZE(rt766_sdca_dai)); ++ ++ /* get SDCA function data */ ++ dev_dbg(dev, "SDCA functions found: %d", slave->sdca_data.num_functions); ++ for (i = 0; i < slave->sdca_data.num_functions; i++) { ++ func_data_ptr = devm_kzalloc(dev, sizeof(*func_data_ptr), GFP_KERNEL); ++ if (!func_data_ptr) { ++ dev_err(dev, "Failed to allocate memory for function data\n"); ++ ret = -ENOMEM; ++ goto _free_dai_drv_; ++ } ++ ++ func_data_ptr->desc = &slave->sdca_data.function[i]; ++ ret = sdca_parse_function(dev, func_data_ptr); ++ if (ret) { ++ devm_kfree(dev, func_data_ptr); ++ goto _free_dai_drv_; ++ } ++ dev_dbg(dev, "Function type=%d, num_entities=%d", ++ slave->sdca_data.function[i].type, func_data_ptr->num_entities); ++ ++ switch (slave->sdca_data.function[i].type) { ++ case SDCA_FUNCTION_TYPE_UAJ: ++ rt766->uaj_func_data = func_data_ptr; ++ /* ++ * Some machines may only support a subset of the sample rates supported by the codec. ++ * Therefore, we need to parse the supported sample rates from the DisCo table and ++ * configure them in the DAI. If the DisCo table does not provide sample rate information, ++ * we will fall back to the default supported rates defined in the codec driver. ++ */ ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 41"); ++ if (rates) ++ dai_drv[RT766_DAI_UAJ].playback.rates = rates; ++ ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 36"); ++ if (rates) ++ dai_drv[RT766_DAI_UAJ].capture.rates = rates; ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_AMP: ++ rt766->sa_func_data = func_data_ptr; ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 21"); ++ if (rates) ++ dai_drv[RT766_DAI_AMP].playback.rates = rates; ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_MIC: ++ rt766->sm_func_data = func_data_ptr; ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 113"); ++ if (rates) ++ dai_drv[RT766_DAI_MIC].capture.rates = rates; ++ break; ++ case SDCA_FUNCTION_TYPE_HID: ++ rt766->hid_func_data = func_data_ptr; ++ break; ++ default: ++ dev_dbg(dev, "Unexpected SDCA function type found: %d", ++ slave->sdca_data.function[i].type); ++ } ++ } ++ ++ ret = devm_snd_soc_register_component(dev, ++ &soc_sdca_dev_rt766, dai_drv, ARRAY_SIZE(rt766_sdca_dai)); ++ if (ret < 0) ++ goto _free_dai_drv_; ++ ++ /* set autosuspend parameters */ ++ pm_runtime_set_autosuspend_delay(dev, 3000); ++ pm_runtime_use_autosuspend(dev); ++ ++ /* make sure the device does not suspend immediately */ ++ pm_runtime_mark_last_busy(dev); ++ ++ pm_runtime_enable(dev); ++ dev_dbg(dev, "%s\n", __func__); ++ return 0; ++ ++_free_dai_drv_: ++ if (dai_drv) ++ devm_kfree(dev, dai_drv); ++ ++_sdw_init_err_: ++ return ret; ++} ++ ++static int rt766_func_initialize(struct rt766_sdca_priv *rt766, struct sdca_function_data *func_data) ++{ ++ struct device *dev = &rt766->slave->dev; ++ unsigned int func_status_reg; ++ unsigned int func_status; ++ int ret; ++ ++ switch (func_data->desc->type) { ++ case SDCA_FUNCTION_TYPE_UAJ: ++ func_status_reg = RT766_FUNC_STATUS_REG(UAJ); ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_AMP: ++ func_status_reg = RT766_FUNC_STATUS_REG(AMP); ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_MIC: ++ func_status_reg = RT766_FUNC_STATUS_REG(MIC); ++ break; ++ case SDCA_FUNCTION_TYPE_HID: ++ func_status_reg = RT766_FUNC_STATUS_REG(HID); ++ break; ++ default: ++ dev_dbg(dev, "Unexpected SDCA function type found: %d", ++ func_data->desc->type); ++ return -EINVAL; ++ } ++ ++ regmap_read(rt766->regmap, func_status_reg, &func_status); ++ dev_dbg(dev, "%s, %s func_status=0x%x\n", __func__, func_data->desc->name, func_status); ++ ++ if ((func_status & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) || (!rt766->first_hw_init)) { ++ ret = sdca_regmap_write_init(dev, rt766->regmap, func_data); ++ if (ret) { ++ dev_err(dev, "%s initialization table update failed\n", func_data->desc->name); ++ goto _func_init_err_; ++ } ++ ++ regmap_write(rt766->regmap, func_status_reg, ++ SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION); ++ } ++ ++ return 0; ++ ++_func_init_err_: ++ dev_err(dev, "%s: %s init writes failed, err=%d", __func__, func_data->desc->name, ret); ++ return ret; ++} ++ ++int rt766_sdca_io_init(struct device *dev, struct sdw_slave *slave) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ unsigned int val; ++ ++ rt766->disable_irq = false; ++ ++ if (rt766->hw_init) ++ return 0; ++ ++ regcache_cache_only(rt766->regmap, false); ++ if (rt766->first_hw_init) { ++ regcache_cache_bypass(rt766->regmap, true); ++ } else { ++ /* ++ * PM runtime status is marked as 'active' only when a Slave reports as Attached ++ */ ++ ++ /* update count of parent 'active' children */ ++ pm_runtime_set_active(&slave->dev); ++ } ++ ++ pm_runtime_get_noresume(&slave->dev); ++ ++ regmap_read(rt766->regmap, RT766_BOND_LATCH_ID, &val); ++ dev_dbg(&slave->dev, "%s bond ID=0x%x (%s)\n", __func__, val, (val == 0x1) ? "RT767" : "RT766"); ++ ++ /* check function status and initialize if needed */ ++ if (rt766->uaj_func_data) ++ rt766_func_initialize(rt766, rt766->uaj_func_data); ++ if (rt766->sa_func_data) ++ rt766_func_initialize(rt766, rt766->sa_func_data); ++ if (rt766->sm_func_data) ++ rt766_func_initialize(rt766, rt766->sm_func_data); ++ if (rt766->hid_func_data) ++ rt766_func_initialize(rt766, rt766->hid_func_data); ++ ++ if (rt766->first_hw_init) { ++ regcache_cache_bypass(rt766->regmap, false); ++ regcache_mark_dirty(rt766->regmap); ++ } else { ++ rt766->first_hw_init = true; ++ } ++ ++ /* Mark Slave initialization complete */ ++ rt766->hw_init = true; ++ ++ dev_dbg(&slave->dev, "%s hw_init complete\n", __func__); ++ ++ pm_runtime_put_autosuspend(&slave->dev); ++ ++ return 0; ++} ++ ++MODULE_DESCRIPTION("ASoC RT766 SDCA SDW driver"); ++MODULE_AUTHOR("Shuming Fan "); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("SND_SOC_SDCA"); +diff --git a/sound/soc/codecs/rt766-sdca.h b/sound/soc/codecs/rt766-sdca.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca.h +@@ -0,0 +1,140 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * rt766-sdca.h -- RT766 SDCA ALSA SoC audio driver header ++ * ++ * Copyright(c) 2026 Realtek Semiconductor Corp. ++ */ ++ ++#ifndef __RT766_H__ ++#define __RT766_H__ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++struct rt766_sdca_priv { ++ struct regmap *regmap; ++ struct snd_soc_component *component; ++ struct sdw_slave *slave; ++ bool hw_init; ++ bool first_hw_init; ++ struct snd_soc_jack *hs_jack; ++ struct mutex disable_irq_lock; /* SDCA irq lock protection */ ++ bool disable_irq; ++ int jack_type; ++ bool fu41_dapm_mute; ++ bool fu41_mixer_l_mute; ++ bool fu41_mixer_r_mute; ++ bool fu113_dapm_mute; ++ bool fu113_mixer_mute[4]; ++ bool fu21_dapm_mute; ++ bool fu21_mixer_l_mute; ++ bool fu21_mixer_r_mute; ++ bool fu36_dapm_mute; ++ bool fu36_mixer_l_mute; ++ bool fu36_mixer_r_mute; ++ struct sdca_function_data *uaj_func_data; ++ struct sdca_function_data *sm_func_data; ++ struct sdca_function_data *sa_func_data; ++ struct sdca_function_data *hid_func_data; ++ struct sdca_interrupt_info *irq_info; ++ struct hid_device *hid; ++}; ++ ++/* vendor registers */ ++#define RT766_VERSION_ID 0xc404 ++#define RT766_DEV_ID1 0xc405 ++#define RT766_DEV_ID0 0xc406 ++#define RT766_BOND_LATCH_ID 0xc407 ++ ++#define RT766_HP_POWER_STATE 0x1000004 ++#define RT766_HP_FSM_CTL2_1 0x100000d ++ ++/* MCU Patch address */ ++#define RT766_MCU_PATCH_ADDR1_START 0x10010000 ++#define RT766_MCU_PATCH_ADDR1_END 0x10011fff ++#define RT766_MCU_PATCH_ADDR2_START 0x10020000 ++#define RT766_MCU_PATCH_ADDR2_END 0x10023fff ++ ++/* Buffer address for HID */ ++#define RT766_BUF_ADDR_HID1 0x44030000 ++#define RT766_BUF_ADDR_HID2 0x44030020 ++ ++/* SDCA (Channel) */ ++#define RT766_CH_1 0x01 ++#define RT766_CH_2 0x02 ++#define RT766_CH_3 0x03 ++#define RT766_CH_4 0x04 ++ ++/* RT766 SDCA Control - function number */ ++#define RT766_FUNC_NUM_UAJ 0x01 ++#define RT766_FUNC_NUM_MIC 0x02 ++#define RT766_FUNC_NUM_HID 0x03 ++#define RT766_FUNC_NUM_AMP 0x04 ++ ++/* RT766 SDCA entity */ ++#define RT766_SDCA_ENT_0 0x00 ++#define RT766_SDCA_ENT_HID101 0x01 ++#define RT766_SDCA_ENT_GE49 0x49 ++#define RT766_SDCA_ENT_USER_FU41 0x05 ++#define RT766_SDCA_ENT_USER_FU36 0x0f ++#define RT766_SDCA_ENT_USER_FU21 0x03 ++#define RT766_SDCA_ENT_USER_FU113 0x30 ++#define RT766_SDCA_ENT_PDE23 0x33 ++#define RT766_SDCA_ENT_PDE47 0x28 ++#define RT766_SDCA_ENT_PDE11 0x2a ++#define RT766_SDCA_ENT_PDE34 0x29 ++#define RT766_SDCA_ENT_CS41 0x01 ++#define RT766_SDCA_ENT_CS36 0x11 ++#define RT766_SDCA_ENT_CS113 0x12 ++#define RT766_SDCA_ENT_CS21 0x21 ++#define RT766_SDCA_ENT_PLATFORM_FU33 0x44 ++#define RT766_SDCA_ENT_PPU21 0x04 ++ ++/* sample frequency index */ ++#define RT766_SDCA_RATE_44100HZ 0x08 ++#define RT766_SDCA_RATE_48000HZ 0x09 ++#define RT766_SDCA_RATE_96000HZ 0x0b ++#define RT766_SDCA_RATE_192000HZ 0x0d ++ ++/* SDCA Register macros */ ++#define RT766_MUTE_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_MUTE, RT766_CH_##ch) ++ ++#define RT766_VOLUME_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_CHANNEL_VOLUME, RT766_CH_##ch) ++ ++#define RT766_GAIN_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_GAIN, RT766_CH_##ch) ++ ++#define RT766_PDE_REQ_REG(func, pde) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##pde, SDCA_CTL_PDE_REQUESTED_PS, 0) ++ ++#define RT766_PDE_ACTUAL_REG(func, pde) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##pde, SDCA_CTL_PDE_ACTUAL_PS, 0) ++ ++#define RT766_FUNC_STATUS_REG(func) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_0, SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0) ++ ++#define RT766_SDCA_CTL(func, ent, ctl) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##ent, ctl, 0) ++ ++enum { ++ RT766_AIF1, ++ RT766_AIF2, ++ RT766_AIF3, ++}; ++ ++enum { ++ RT766_DAI_UAJ, ++ RT766_DAI_AMP, ++ RT766_DAI_MIC, ++}; ++ ++int rt766_sdca_io_init(struct device *dev, struct sdw_slave *slave); ++int rt766_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave *slave); ++#endif /* __RT766_H__ */ +diff --git a/sound/soc/codecs/rt9120.c b/sound/soc/codecs/rt9120.c +--- a/sound/soc/codecs/rt9120.c ++++ b/sound/soc/codecs/rt9120.c +@@ -606,12 +606,19 @@ static int rt9120_runtime_suspend(struct device *dev) + static int rt9120_runtime_resume(struct device *dev) + { + struct rt9120_data *data = dev_get_drvdata(dev); ++ int ret; + + if (data->pwdnn_gpio) { + gpiod_set_value(data->pwdnn_gpio, 1); + msleep(RT9120_CHIPON_WAITMS); + regcache_cache_only(data->regmap, false); +- regcache_sync(data->regmap); ++ ret = regcache_sync(data->regmap); ++ if (ret) { ++ regcache_cache_only(data->regmap, true); ++ regcache_mark_dirty(data->regmap); ++ gpiod_set_value(data->pwdnn_gpio, 0); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/sigmadsp.c b/sound/soc/codecs/sigmadsp.c +--- a/sound/soc/codecs/sigmadsp.c ++++ b/sound/soc/codecs/sigmadsp.c +@@ -5,6 +5,7 @@ + * Copyright 2009-2014 Analog Devices Inc. + */ + ++#include + #include + #include + #include +@@ -135,7 +136,7 @@ static int sigmadsp_ctrl_put(struct snd_kcontrol *kcontrol, + uint8_t *data; + int ret = 0; + +- mutex_lock(&sigmadsp->lock); ++ guard(mutex)(&sigmadsp->lock); + + data = ucontrol->value.bytes.data; + +@@ -148,8 +149,6 @@ static int sigmadsp_ctrl_put(struct snd_kcontrol *kcontrol, + ctrl->cached = true; + } + +- mutex_unlock(&sigmadsp->lock); +- + return ret; + } + +@@ -160,7 +159,7 @@ static int sigmadsp_ctrl_get(struct snd_kcontrol *kcontrol, + struct sigmadsp *sigmadsp = snd_kcontrol_chip(kcontrol); + int ret = 0; + +- mutex_lock(&sigmadsp->lock); ++ guard(mutex)(&sigmadsp->lock); + + if (!ctrl->cached) { + ret = sigmadsp_read(sigmadsp, ctrl->addr, ctrl->cache, +@@ -174,8 +173,6 @@ static int sigmadsp_ctrl_get(struct snd_kcontrol *kcontrol, + ctrl->num_bytes); + } + +- mutex_unlock(&sigmadsp->lock); +- + return ret; + } + +@@ -487,7 +484,7 @@ static void devm_sigmadsp_release(struct device *dev, void *res) + static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + { + const struct sigma_firmware_header *ssfw_head; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + u32 crc; + +@@ -495,7 +492,7 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + ret = request_firmware(&fw, name, sigmadsp->dev); + if (ret) { + pr_debug("%s: request_firmware() failed with %i\n", __func__, ret); +- goto done; ++ return ret; + } + + /* then verify the header */ +@@ -509,13 +506,13 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + */ + if (fw->size < sizeof(*ssfw_head) || fw->size >= 0x4000000) { + dev_err(sigmadsp->dev, "Failed to load firmware: Invalid size\n"); +- goto done; ++ return -EINVAL; + } + + ssfw_head = (void *)fw->data; + if (memcmp(ssfw_head->magic, SIGMA_MAGIC, ARRAY_SIZE(ssfw_head->magic))) { + dev_err(sigmadsp->dev, "Failed to load firmware: Invalid magic\n"); +- goto done; ++ return -EINVAL; + } + + crc = crc32(0, fw->data + sizeof(*ssfw_head), +@@ -524,7 +521,7 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + if (crc != le32_to_cpu(ssfw_head->crc)) { + dev_err(sigmadsp->dev, "Failed to load firmware: Wrong crc checksum: expected %x got %x\n", + le32_to_cpu(ssfw_head->crc), crc); +- goto done; ++ return -EINVAL; + } + + switch (ssfw_head->version) { +@@ -545,9 +542,6 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + if (ret) + sigmadsp_firmware_release(sigmadsp); + +-done: +- release_firmware(fw); +- + return ret; + } + +@@ -677,10 +671,10 @@ static void sigmadsp_activate_ctrl(struct sigmadsp *sigmadsp, + return; + changed = snd_ctl_activate_id(card, &ctrl->kcontrol->id, active); + if (active && changed > 0) { +- mutex_lock(&sigmadsp->lock); +- if (ctrl->cached) +- sigmadsp_ctrl_write(sigmadsp, ctrl, ctrl->cache); +- mutex_unlock(&sigmadsp->lock); ++ scoped_guard(mutex, &sigmadsp->lock) { ++ if (ctrl->cached) ++ sigmadsp_ctrl_write(sigmadsp, ctrl, ctrl->cache); ++ } + } + } + +diff --git a/sound/soc/codecs/sma1303.c b/sound/soc/codecs/sma1303.c +--- a/sound/soc/codecs/sma1303.c ++++ b/sound/soc/codecs/sma1303.c +@@ -1777,6 +1777,8 @@ static void sma1303_i2c_remove(struct i2c_client *client) + struct sma1303_priv *sma1303 = + (struct sma1303_priv *) i2c_get_clientdata(client); + ++ if (sma1303->attr_grp) ++ sysfs_remove_group(sma1303->kobj, sma1303->attr_grp); + cancel_delayed_work_sync(&sma1303->check_fault_work); + } + +diff --git a/sound/soc/codecs/sma1307.c b/sound/soc/codecs/sma1307.c +--- a/sound/soc/codecs/sma1307.c ++++ b/sound/soc/codecs/sma1307.c +@@ -1690,7 +1690,7 @@ static void sma1307_check_fault_worker(struct work_struct *work) + + static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *file) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int size, offset, num_mode; + int ret; + +@@ -1703,22 +1703,18 @@ static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *fil + return; + } else if ((fw->size) < SMA1307_SETTING_HEADER_SIZE) { + dev_err(sma1307->dev, "%s: Invalid file\n", __func__); +- release_firmware(fw); + sma1307->set.status = false; + return; + } + + int *data __free(kfree) = kzalloc(fw->size, GFP_KERNEL); + if (!data) { +- release_firmware(fw); + sma1307->set.status = false; + return; + } + size = fw->size >> 2; + memcpy(data, fw->data, fw->size); + +- release_firmware(fw); +- + /* HEADER */ + sma1307->set.header_size = SMA1307_SETTING_HEADER_SIZE; + sma1307->set.checksum = data[sma1307->set.header_size - 2]; +diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c +--- a/sound/soc/codecs/sta32x.c ++++ b/sound/soc/codecs/sta32x.c +@@ -17,6 +17,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -269,9 +270,9 @@ static int sta32x_coefficient_get(struct snd_kcontrol *kcontrol, + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud, val; +- int i, ret = 0; ++ int i; + +- mutex_lock(&sta32x->coeff_lock); ++ guard(mutex)(&sta32x->coeff_lock); + + /* preserve reserved bits in STA32X_CFUD */ + regmap_read(sta32x->regmap, STA32X_CFUD, &cfud); +@@ -283,24 +284,20 @@ static int sta32x_coefficient_get(struct snd_kcontrol *kcontrol, + regmap_write(sta32x->regmap, STA32X_CFUD, cfud); + + regmap_write(sta32x->regmap, STA32X_CFADDR2, index); +- if (numcoef == 1) { ++ if (numcoef == 1) + regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x04); +- } else if (numcoef == 5) { ++ else if (numcoef == 5) + regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x08); +- } else { +- ret = -EINVAL; +- goto exit_unlock; +- } ++ else ++ return -EINVAL; ++ + + for (i = 0; i < 3 * numcoef; i++) { + regmap_read(sta32x->regmap, STA32X_B1CF1 + i, &val); + ucontrol->value.bytes.data[i] = val; + } + +-exit_unlock: +- mutex_unlock(&sta32x->coeff_lock); +- +- return ret; ++ return 0; + } + + static int sta32x_coefficient_put(struct snd_kcontrol *kcontrol, +diff --git a/sound/soc/codecs/sta350.c b/sound/soc/codecs/sta350.c +--- a/sound/soc/codecs/sta350.c ++++ b/sound/soc/codecs/sta350.c +@@ -16,6 +16,7 @@ + + #define pr_fmt(fmt) KBUILD_MODNAME ":%s:%d: " fmt, __func__, __LINE__ + ++#include + #include + #include + #include +@@ -306,9 +307,9 @@ static int sta350_coefficient_get(struct snd_kcontrol *kcontrol, + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud, val; +- int i, ret = 0; ++ int i; + +- mutex_lock(&sta350->coeff_lock); ++ guard(mutex)(&sta350->coeff_lock); + + /* preserve reserved bits in STA350_CFUD */ + regmap_read(sta350->regmap, STA350_CFUD, &cfud); +@@ -320,24 +321,19 @@ static int sta350_coefficient_get(struct snd_kcontrol *kcontrol, + regmap_write(sta350->regmap, STA350_CFUD, cfud); + + regmap_write(sta350->regmap, STA350_CFADDR2, index); +- if (numcoef == 1) { ++ if (numcoef == 1) + regmap_write(sta350->regmap, STA350_CFUD, cfud | 0x04); +- } else if (numcoef == 5) { ++ else if (numcoef == 5) + regmap_write(sta350->regmap, STA350_CFUD, cfud | 0x08); +- } else { +- ret = -EINVAL; +- goto exit_unlock; +- } ++ else ++ return -EINVAL; + + for (i = 0; i < 3 * numcoef; i++) { + regmap_read(sta350->regmap, STA350_B1CF1 + i, &val); + ucontrol->value.bytes.data[i] = val; + } + +-exit_unlock: +- mutex_unlock(&sta350->coeff_lock); +- +- return ret; ++ return 0; + } + + static int sta350_coefficient_put(struct snd_kcontrol *kcontrol, +diff --git a/sound/soc/codecs/tac5xx2-sdw.c b/sound/soc/codecs/tac5xx2-sdw.c +--- a/sound/soc/codecs/tac5xx2-sdw.c ++++ b/sound/soc/codecs/tac5xx2-sdw.c +@@ -88,6 +88,9 @@ + #define TAC_FW_FILE_HDR 20 + #define TAC_MAX_FW_CHUNKS 512 + ++#define TAC_UAJ_PREP_CONNECTED 0xff ++#define TAC_UAJ_PREP_DISCONNECTED 0xdf ++ + struct tac_fw_hdr { + u32 size; + u32 version_offset; +@@ -138,6 +141,7 @@ struct tac5xx2_prv { + bool hw_init; + bool first_hw_init_done; + u32 part_id; ++ u32 rev_id; + struct snd_soc_jack *hs_jack; + int jack_type; + /* Custom fw binary. UMP File Download is not used. */ +@@ -261,7 +265,6 @@ static const struct reg_default tac_reg_default[] = { + {TAC_REG_SDW(0, 0, 0x78), 0x0}, + {TAC_REG_SDW(0, 0, 0x7b), 0x0}, + {TAC_REG_SDW(0, 0, 0x7c), 0xd0}, +- {TAC_REG_SDW(0, 0, 0x7d), 0x0}, + {TAC_REG_SDW(0, 0, 0x7e), 0x0}, + {TAC_REG_SDW(0, 1, 0x1), 0x0}, + {TAC_REG_SDW(0, 1, 0x2), 0x0}, +@@ -507,18 +510,6 @@ static const struct regmap_config tac_regmap = { + .use_single_write = true, + }; + +-/* Check if device has DSP algo that needs status monitoring */ +-static bool tac_has_dsp_algo(struct tac5xx2_prv *tac_dev) +-{ +- switch (tac_dev->part_id) { +- case 0x5682: +- case 0x2883: +- return true; +- default: +- return false; +- } +-} +- + /* Check if device has UAJ (Universal Audio Jack) support */ + static bool tac_has_uaj_support(struct tac5xx2_prv *tac_dev) + { +@@ -800,7 +791,7 @@ static int tac_sdw_hw_params(struct snd_pcm_substream *substream, + return ret; + } + +- ret = sdca_asoc_pde_poll_actual_ps(tac_dev->dev, tac_dev->regmap, function_id, pde_entity, ++ ret = sdca_asoc_pde_poll_actual_ps(tac_dev->regmap, function_id, pde_entity, + SDCA_PDE_PS3, SDCA_PDE_PS0, NULL, 0); + if (ret) + dev_err(tac_dev->dev, "failed to transition func %d, pde %d from PS3 -> PS0, err=%d\n", +@@ -847,7 +838,7 @@ static int tac_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + return ret; + } + +- ret = sdca_asoc_pde_poll_actual_ps(tac_dev->dev, tac_dev->regmap, function_id, ++ ret = sdca_asoc_pde_poll_actual_ps(tac_dev->regmap, function_id, + pde_entity, SDCA_PDE_PS0, SDCA_PDE_PS3, + NULL, 0); + if (ret) +@@ -942,6 +933,7 @@ static int tac5xx2_sdca_button_detect(struct tac5xx2_prv *tac_dev) + static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + { + int val, ret; ++ u8 jack_prep; + + ret = regmap_read(tac_dev->regmap, + SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_GE35, +@@ -951,6 +943,8 @@ static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + return ret; + } + ++ jack_prep = TAC_UAJ_PREP_CONNECTED; ++ + switch (val) { + case 4: + tac_dev->jack_type = SND_JACK_MICROPHONE; +@@ -964,6 +958,7 @@ static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + case 0: + default: + tac_dev->jack_type = 0; ++ jack_prep = TAC_UAJ_PREP_DISCONNECTED; + break; + } + +@@ -973,26 +968,44 @@ static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + if (ret) + dev_err(tac_dev->dev, "Failed to update the jack type to device"); + ++ /* ++ * When uaj is uplugged and booted, we end up with the channel prepare ++ * timeout error for the uaj ports. This writes allows the channel prepare ++ * to succeed when the uaj is not plugged in. ++ */ ++ if (tac_dev->rev_id >= 0x30) { ++ ret = regmap_write(tac_dev->regmap, TAC_REG_SDW(0, 3, 127), jack_prep); ++ if (ret) ++ dev_warn(tac_dev->dev, "Failed to write jack_prep register: %d\n", ret); ++ } ++ + return 0; + } + + static int tac5xx2_jack_init(struct tac5xx2_prv *tac_dev) + { ++ u32 jd_int_mask, hid_int_mask; + int ret = 0; + ++ if (tac_dev->rev_id >= 0x30) { ++ jd_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_12; ++ hid_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_17; ++ } else { ++ jd_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_11; ++ hid_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_16; ++ } ++ + if (!tac_dev->hs_jack) + goto disable_interrupts; + +- ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_11); ++ ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK2, jd_int_mask); + if (ret) { + dev_err(tac_dev->dev, + "Failed to register jack detection interrupt: %d\n", ret); + goto disable_interrupts; + } + +- ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK3, +- SDW_SCP_SDCA_INTMASK_SDCA_16); ++ ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK3, hid_int_mask); + if (ret) { + dev_err(tac_dev->dev, + "Failed to register for button detect interrupt: %d\n", ret); +@@ -1051,6 +1064,7 @@ static int tac_interrupt_callback(struct sdw_slave *slave, + unsigned int sdca_int2, sdca_int3, jack_report_mask = 0; + struct tac5xx2_prv *tac_dev = dev_get_drvdata(&slave->dev); + struct device *dev = &slave->dev; ++ u32 headset_detect_chk, hid_detect_chk; + int btn_type = 0; + int ret = 0; + +@@ -1081,14 +1095,22 @@ static int tac_interrupt_callback(struct sdw_slave *slave, + dev_dbg(dev, "SDCA_INT2: 0x%02x, SDCA_INT3: 0x%02x\n", + sdca_int2, sdca_int3); + +- if (sdca_int2 & SDW_SCP_SDCA_INT_SDCA_11) { ++ if (tac_dev->rev_id >= 0x30) { ++ headset_detect_chk = SDW_SCP_SDCA_INT_SDCA_12; ++ hid_detect_chk = SDW_SCP_SDCA_INT_SDCA_17; ++ } else { ++ headset_detect_chk = SDW_SCP_SDCA_INT_SDCA_11; ++ hid_detect_chk = SDW_SCP_SDCA_INT_SDCA_16; ++ } ++ ++ if (sdca_int2 & headset_detect_chk) { + ret = tac5xx2_sdca_headset_detect(tac_dev); + if (ret < 0) + goto clear; + jack_report_mask |= SND_JACK_HEADSET; + } + +- if (sdca_int3 & SDW_SCP_SDCA_INT_SDCA_16) { ++ if (sdca_int3 & hid_detect_chk) { + btn_type = tac5xx2_sdca_button_detect(tac_dev); + if (btn_type < 0) + btn_type = 0; +@@ -1695,6 +1717,15 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) + goto io_init_err; + } + ++ if (!tac_dev->rev_id) { ++ ret = regmap_read(tac_dev->regmap, TAC_REV_ID, &tac_dev->rev_id); ++ if (ret) { ++ dev_err(tac_dev->dev, "failed to rev id, err=%d\n", ret); ++ goto io_init_err; ++ } ++ dev_dbg(tac_dev->dev, "detected rev_id 0x%x", tac_dev->rev_id); ++ } ++ + if (tac_dev->fw_files && tac_dev->fw_file_cnt > 0) { + ret = tac_download_fw_to_hw(tac_dev); + if (ret) { +@@ -1940,7 +1971,7 @@ static s32 tac_sdw_probe(struct sdw_slave *peripheral, + "failed to allocate %s function data", + func_name); + function_data->desc = &peripheral->sdca_data.function[i]; +- ret = sdca_parse_function(dev, peripheral, function_data); ++ ret = sdca_parse_function(dev, function_data); + if (!ret) + *func_ptr = function_data; + else +@@ -1959,6 +1990,7 @@ static s32 tac_sdw_probe(struct sdw_slave *peripheral, + tac_dev->hw_init = false; + tac_dev->first_hw_init_done = false; + tac_dev->part_id = id->part_id; ++ tac_dev->rev_id = 0x0; + dev_set_drvdata(dev, tac_dev); + + regmap = devm_regmap_init_sdw_mbq_cfg(&peripheral->dev, peripheral, +@@ -1972,17 +2004,13 @@ static s32 tac_sdw_probe(struct sdw_slave *peripheral, + tac_dev->jack_type = 0; + init_completion(&tac_dev->fw_caching_complete); + +- if (tac_has_dsp_algo(tac_dev)) { +- tac_generate_fw_name(peripheral, tac_dev->fw_binaryname, +- sizeof(tac_dev->fw_binaryname)); ++ tac_generate_fw_name(peripheral, tac_dev->fw_binaryname, ++ sizeof(tac_dev->fw_binaryname)); + +- ret = tac_load_and_cache_firmware_async(tac_dev); +- if (ret) { +- complete_all(&tac_dev->fw_caching_complete); +- dev_dbg(dev, "failed to load fw: %d, use rom mode\n", ret); +- } +- } else { ++ ret = tac_load_and_cache_firmware_async(tac_dev); ++ if (ret) { + complete_all(&tac_dev->fw_caching_complete); ++ dev_dbg(dev, "failed to load fw: %d, use rom mode\n", ret); + } + + ret = tac_init(tac_dev); +diff --git a/sound/soc/codecs/tas2552.c b/sound/soc/codecs/tas2552.c +--- a/sound/soc/codecs/tas2552.c ++++ b/sound/soc/codecs/tas2552.c +@@ -495,13 +495,21 @@ static int tas2552_runtime_suspend(struct device *dev) + static int tas2552_runtime_resume(struct device *dev) + { + struct tas2552_data *tas2552 = dev_get_drvdata(dev); ++ int ret; + + gpiod_set_value_cansleep(tas2552->enable_gpio, 1); + + tas2552_sw_shutdown(tas2552, 0); + + regcache_cache_only(tas2552->regmap, false); +- regcache_sync(tas2552->regmap); ++ ret = regcache_sync(tas2552->regmap); ++ if (ret) { ++ regcache_cache_only(tas2552->regmap, true); ++ regcache_mark_dirty(tas2552->regmap); ++ tas2552_sw_shutdown(tas2552, 1); ++ gpiod_set_value_cansleep(tas2552->enable_gpio, 0); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/tas2781-comlib-i2c.c b/sound/soc/codecs/tas2781-comlib-i2c.c +--- a/sound/soc/codecs/tas2781-comlib-i2c.c ++++ b/sound/soc/codecs/tas2781-comlib-i2c.c +@@ -6,6 +6,7 @@ + // + // Author: Shenghao Ding + ++#include + #include + #include + #include +@@ -342,7 +343,7 @@ int tascodec_init(struct tasdevice_priv *tas_priv, void *codec, + /* Codec Lock Hold to ensure that codec_probe and firmware parsing and + * loading do not simultaneously execute. + */ +- mutex_lock(&tas_priv->codec_lock); ++ guard(mutex)(&tas_priv->codec_lock); + + if (tas_priv->name_prefix) + scnprintf(tas_priv->rca_binaryname, 64, "%s-%sRCA%d.bin", +@@ -360,8 +361,6 @@ int tascodec_init(struct tasdevice_priv *tas_priv, void *codec, + dev_err(tas_priv->dev, "request_firmware_nowait err:0x%08x\n", + ret); + +- /* Codec Lock Release*/ +- mutex_unlock(&tas_priv->codec_lock); + return ret; + } + EXPORT_SYMBOL_GPL(tascodec_init); +diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c +--- a/sound/soc/codecs/tas2781-fmwlib.c ++++ b/sound/soc/codecs/tas2781-fmwlib.c +@@ -1849,16 +1849,26 @@ static int tasdev_load_blk(struct tasdevice_priv *tas_priv, + } + } + if (ret == -EAGAIN) { +- if (block->nr_retry > 0) ++ if (block->nr_retry > 0) { ++ /* Give the hardware time to stabilize before ++ * next block re-transmission attempt. ++ */ ++ usleep_range(2000, 2500); + continue; ++ } + } else if (ret < 0) /*err in current device, skip it*/ + break; + + if (block->is_pchksum_present) { + ret = tasdev_block_chksum(tas_priv, block, chn); + if (ret == -EAGAIN) { +- if (block->nr_retry > 0) ++ if (block->nr_retry > 0) { ++ /* Give the bus time to recover after ++ * a checksum mismatch error. ++ */ ++ usleep_range(2000, 2500); + continue; ++ } + } else if (ret < 0) /*err in current device, skip it*/ + break; + } +@@ -2243,7 +2253,7 @@ int tas2781_load_calibration(void *context, char *file_name, + { + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; + struct tasdevice *tasdev = &(tas_priv->tasdevice[i]); +- const struct firmware *fw_entry = NULL; ++ const struct firmware *fw_entry __free(firmware) = NULL; + struct tasdevice_fw *tas_fmw; + struct firmware fmw; + int offset = 0; +@@ -2253,60 +2263,50 @@ int tas2781_load_calibration(void *context, char *file_name, + if (ret) { + dev_err(tas_priv->dev, "%s: Request firmware %s failed\n", + __func__, file_name); +- goto out; ++ return ret; + } + + if (!fw_entry->size) { + dev_err(tas_priv->dev, "%s: file read error: size = %lu\n", + __func__, (unsigned long)fw_entry->size); +- ret = -EINVAL; +- goto out; ++ return -EINVAL; + } + fmw.size = fw_entry->size; + fmw.data = fw_entry->data; + + tas_fmw = tasdev->cali_data_fmw = kzalloc_obj(struct tasdevice_fw); +- if (!tasdev->cali_data_fmw) { +- ret = -ENOMEM; +- goto out; +- } ++ if (!tasdev->cali_data_fmw) ++ return -ENOMEM; ++ + tas_fmw->dev = tas_priv->dev; + offset = fw_parse_header(tas_priv, tas_fmw, &fmw, offset); + if (offset == -EINVAL) { + dev_err(tas_priv->dev, "fw_parse_header EXIT!\n"); +- ret = offset; +- goto out; ++ return -EINVAL; + } + offset = fw_parse_variable_hdr_cal(tas_priv, tas_fmw, &fmw, offset); + if (offset == -EINVAL) { + dev_err(tas_priv->dev, + "%s: fw_parse_variable_header_cal EXIT!\n", __func__); +- ret = offset; +- goto out; ++ return -EINVAL; + } + offset = fw_parse_program_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_program_data EXIT!\n"); +- ret = offset; +- goto out; ++ return offset; + } + offset = fw_parse_configuration_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_configuration_data EXIT!\n"); +- ret = offset; +- goto out; ++ return offset; + } + offset = fw_parse_calibration_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_calibration_data EXIT!\n"); +- ret = offset; +- goto out; ++ return offset; + } + +-out: +- release_firmware(fw_entry); +- +- return ret; ++ return 0; + } + EXPORT_SYMBOL_NS_GPL(tas2781_load_calibration, "SND_SOC_TAS2781_FMWLIB"); + +@@ -2399,7 +2399,7 @@ static int tasdevice_dspfw_ready(const struct firmware *fmw, + int tasdevice_dsp_parser(void *context) + { + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; +- const struct firmware *fw_entry; ++ const struct firmware *fw_entry __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw_entry, tas_priv->coef_binaryname, +@@ -2407,15 +2407,10 @@ int tasdevice_dsp_parser(void *context) + if (ret) { + dev_err(tas_priv->dev, "%s: load %s error\n", __func__, + tas_priv->coef_binaryname); +- goto out; ++ return ret; + } + +- ret = tasdevice_dspfw_ready(fw_entry, tas_priv); +- release_firmware(fw_entry); +- fw_entry = NULL; +- +-out: +- return ret; ++ return tasdevice_dspfw_ready(fw_entry, tas_priv); + } + EXPORT_SYMBOL_NS_GPL(tasdevice_dsp_parser, "SND_SOC_TAS2781_FMWLIB"); + +@@ -2797,11 +2792,12 @@ int tasdevice_prmg_load(void *context, int prm_no) + } + EXPORT_SYMBOL_NS_GPL(tasdevice_prmg_load, "SND_SOC_TAS2781_FMWLIB"); + +-void tasdevice_tuning_switch(void *context, int state) ++void tasdevice_tuning_switch(void *context, int state, bool is_cap) + { + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *) context; + struct tasdevice_fw *tas_fmw = tas_priv->fmw; +- int profile_cfg_id = tas_priv->rcabin.profile_cfg_id; ++ int profile_cfg_id = is_cap ? tas_priv->rcabin.capture_profile_id : ++ tas_priv->rcabin.profile_cfg_id; + + /* + * Only RCA-based Playback can still work with no dsp program running +@@ -2818,7 +2814,6 @@ void tasdevice_tuning_switch(void *context, int state) + if (state == 0) { + if (tas_fmw && tas_priv->cur_prog < tas_fmw->nr_programs) { + /* dsp mode or tuning mode */ +- profile_cfg_id = tas_priv->rcabin.profile_cfg_id; + tasdevice_select_tuningprm_cfg(tas_priv, + tas_priv->cur_prog, tas_priv->cur_conf, + profile_cfg_id); +diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c +--- a/sound/soc/codecs/tas2781-i2c.c ++++ b/sound/soc/codecs/tas2781-i2c.c +@@ -13,6 +13,7 @@ + // Author: Kevin Lu + // + ++#include + #include + #include + #include +@@ -617,6 +618,7 @@ static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *priv = snd_soc_component_get_drvdata(comp); ++ int i; + + guard(mutex)(&priv->codec_lock); + if (priv->chip_id == TAS2563) +@@ -624,6 +626,14 @@ static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, + else + tas2781_calib_stop_put(priv); + ++ /* ++ * Set reloading-firmware flag after calibration, the flag will work ++ * during next playback, then set to the program id after reloading ++ * firmware. ++ */ ++ for (i = 0; i < priv->ndev; i++) ++ priv->tasdevice[i].cur_prog = -1; ++ + return 1; + } + +@@ -843,12 +853,12 @@ static int tasdevice_digital_gain_get( + unsigned char data[4]; + int ret; + +- mutex_lock(&tas_dev->codec_lock); ++ guard(mutex)(&tas_dev->codec_lock); + /* Read the primary device */ + ret = tasdevice_dev_bulk_read(tas_dev, 0, reg, data, 4); + if (ret) { + dev_err(tas_dev->dev, "%s, get AMP vol error\n", __func__); +- goto out; ++ return ret; + } + + target = get_unaligned_be32(&data[0]); +@@ -868,8 +878,7 @@ static int tasdevice_digital_gain_get( + /* find out the member same as or closer to the current volume */ + ucontrol->value.integer.value[0] = + abs(target - ar_l) <= abs(target - ar_r) ? l : r; +-out: +- mutex_unlock(&tas_dev->codec_lock); ++ + return 0; + } + +@@ -882,29 +891,26 @@ static int tasdevice_digital_gain_put( + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *tas_dev = snd_soc_component_get_drvdata(codec); + int vol = ucontrol->value.integer.value[0]; +- int status = 0, max = mc->max, rc = 1; ++ int status = 0, max = mc->max; + int i, ret; + unsigned int reg = mc->reg; + unsigned int volrd, volwr; + unsigned char data[4]; + + vol = clamp(vol, 0, max); +- mutex_lock(&tas_dev->codec_lock); ++ guard(mutex)(&tas_dev->codec_lock); + /* Read the primary device */ + ret = tasdevice_dev_bulk_read(tas_dev, 0, reg, data, 4); + if (ret) { + dev_err(tas_dev->dev, "%s, get AMP vol error\n", __func__); +- rc = -1; +- goto out; ++ return -1; + } + + volrd = get_unaligned_be32(&data[0]); + volwr = get_unaligned_be32(tas_dev->dvc_tlv_table[vol]); + +- if (volrd == volwr) { +- rc = 0; +- goto out; +- } ++ if (volrd == volwr) ++ return 0; + + for (i = 0; i < tas_dev->ndev; i++) { + ret = tasdevice_dev_bulk_write(tas_dev, i, reg, +@@ -918,10 +924,9 @@ static int tasdevice_digital_gain_put( + } + + if (status) +- rc = -1; +-out: +- mutex_unlock(&tas_dev->codec_lock); +- return rc; ++ return -1; ++ ++ return 1; + } + + static const struct snd_kcontrol_new tasdevice_cali_controls[] = { +@@ -995,6 +1000,57 @@ static int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol, + return ret; + } + ++/** ++ * tasdevice_set_capture_profile_id - Set runtime capture profile index via ++ * ALSA control ++ * @kcontrol: ALSA kcontrol handle that triggers this operation ++ * @ucontrol: User space control value carrying the new profile index ++ * ++ * This mixer control handler validates the user-provided capture profile ID ++ * against the maximum valid index parsed from the loaded DSP firmware, ++ * then updates the runtime stored capture profile ID only if the new value ++ * differs from the current active one. It will immediately return -EINVAL ++ * if the submitted profile ID falls outside the valid range, including the ++ * edge case that no valid configuration blocks are detected in firmware. ++ * ++ * No actual DSP register write is performed in this handler. The updated ++ * profile ID will be applied to the hardware when the next ALSA capture ++ * stream starts up. Caller does not need to take extra codec lock here, ++ * as the ALSA control core already guarantees serialized execution. ++ * ++ * Return: 1 if profile ID value was changed, 0 if no modification needed, ++ * -EINVAL if the input profile ID is out of valid range ++ */ ++static int tasdevice_set_capture_profile_id(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); ++ struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); ++ unsigned int user_prof_id = ucontrol->value.integer.value[0]; ++ unsigned int max_valid_id; ++ int ret = 0; ++ ++ /* ++ * Align valid range with the bound defined in ++ * tasdevice_info_profile() ++ */ ++ max_valid_id = tas_priv->rcabin.ncfgs - 1; ++ ++ /* ++ * Reject invalid input including zero total configuration edge ++ * case ++ */ ++ if (tas_priv->rcabin.ncfgs == 0 || user_prof_id > max_valid_id) ++ return -EINVAL; ++ ++ if (tas_priv->rcabin.capture_profile_id != user_prof_id) { ++ tas_priv->rcabin.capture_profile_id = user_prof_id; ++ ret = 1; ++ } ++ ++ return ret; ++} ++ + static int tasdevice_info_active_num(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) + { +@@ -1075,6 +1131,41 @@ static int tasdevice_get_profile_id(struct snd_kcontrol *kcontrol, + return 0; + } + ++/** ++ * tasdevice_get_capture_profile_id - Report current active capture profile ++ * ID to user space ++ * @kcontrol: ALSA kcontrol structure passed from ALSA core ++ * @ucontrol: User-space control element value buffer to write the result back ++ * ++ * This function ensures the returned profile ID is always clamped inside the ++ * valid range advertised by the info callback, preventing accidental invalid ++ * values from being exposed to applications even if internal driver state is ++ * temporarily inconsistent. ++ * ++ * Returns 0 on successful fill of the control value, no error conditions ++ * are defined for this getter callback. ++ */ ++static int tasdevice_get_capture_profile_id(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); ++ struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); ++ unsigned int max_valid_id, current_prof_id; ++ ++ max_valid_id = tas_priv->rcabin.ncfgs > 0 ? ++ (tas_priv->rcabin.ncfgs - 1U) : 0; ++ ++ /* ++ * Cast current profile id to unsigned to match type with max_valid_id, ++ * avoid signedness mismatch; ++ */ ++ current_prof_id = (unsigned int)tas_priv->rcabin.capture_profile_id; ++ /* Prevent underflow when there are no loaded capture profiles. */ ++ ucontrol->value.integer.value[0] = min(current_prof_id, max_valid_id); ++ ++ return 0; ++} ++ + static int tasdevice_get_chip_id(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) + { +@@ -1117,6 +1208,41 @@ static int tasdevice_create_control(struct tasdevice_priv *tas_priv) + ret = snd_soc_add_component_controls(tas_priv->codec, + prof_ctrls, nr_controls < mix_index ? nr_controls : mix_index); + ++ mix_index = 0; ++ switch (tas_priv->chip_id) { ++ case TAS2563: ++ case TAS2568: ++ case TAS2570: ++ case TAS2572: ++ case TAS2573: ++ case TAS2574: ++ case TAS2781: ++ prof_ctrls = devm_kcalloc(tas_priv->dev, nr_controls, ++ sizeof(prof_ctrls[0]), GFP_KERNEL); ++ if (!prof_ctrls) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ /* Create a mixer item for selecting the capture profile */ ++ name = devm_kstrdup(tas_priv->dev, "Speaker Capture Profile Id", ++ GFP_KERNEL); ++ if (!name) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ prof_ctrls[mix_index].name = name; ++ prof_ctrls[mix_index].iface = SNDRV_CTL_ELEM_IFACE_MIXER; ++ prof_ctrls[mix_index].info = tasdevice_info_profile; ++ prof_ctrls[mix_index].get = tasdevice_get_capture_profile_id; ++ prof_ctrls[mix_index].put = tasdevice_set_capture_profile_id; ++ mix_index++; ++ ++ ret = snd_soc_add_component_controls(tas_priv->codec, ++ prof_ctrls, nr_controls < mix_index ? nr_controls : mix_index); ++ break; ++ } ++ + out: + return ret; + } +@@ -1330,6 +1456,43 @@ static void alpa_cali_update(struct bulk_reg_val *p, + p->val_len = 4; + } + ++static int create_tas2781_cali_start_ktrl(struct tasdevice_priv ++ *priv, struct snd_kcontrol_new *cali_ctrl) ++{ ++ struct soc_bytes_ext *ext_cali_start; ++ char *cali_start_name; ++ ++ ext_cali_start = devm_kzalloc(priv->dev, ++ sizeof(*ext_cali_start), GFP_KERNEL); ++ if (!ext_cali_start) ++ return -ENOMEM; ++ ++ cali_start_name = devm_kstrdup(priv->dev, ++ "Calibration Start", GFP_KERNEL); ++ if (!cali_start_name) ++ return -ENOMEM; ++ /* ++ * package structure for tas2781 ftc start: ++ * Pkg len (1 byte) ++ * Reg id (1 byte, constant 'r') ++ * book, page, register for pilot threshold, pilot tone ++ * and sine gain (12 bytes) ++ * for (i = 0; i < Device-Sum; i++) { ++ * Device #i index_info (1 byte) ++ * Sine gain for Device #i (8 bytes) ++ * } ++ */ ++ ext_cali_start->max = 14 + priv->ndev * 9; ++ cali_ctrl->name = cali_start_name; ++ cali_ctrl->iface = SNDRV_CTL_ELEM_IFACE_MIXER; ++ cali_ctrl->info = snd_soc_bytes_info_ext; ++ cali_ctrl->put = tas2781_calib_start_put; ++ cali_ctrl->get = tasdev_nop_get; ++ cali_ctrl->private_value = (unsigned long)ext_cali_start; ++ ++ return 0; ++} ++ + static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) + { + struct calidata *cali_data = &priv->cali_data; +@@ -1448,37 +1611,11 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) + */ + cali_data->data[0] = 0xff; + if (priv->chip_id == TAS2781) { +- struct soc_bytes_ext *ext_cali_start; +- char *cali_start_name; +- +- ext_cali_start = devm_kzalloc(priv->dev, +- sizeof(*ext_cali_start), GFP_KERNEL); +- if (!ext_cali_start) +- return -ENOMEM; +- +- cali_start_name = devm_kstrdup(priv->dev, +- "Calibration Start", GFP_KERNEL); +- if (!cali_start_name) +- return -ENOMEM; +- /* +- * package structure for tas2781 ftc start: +- * Pkg len (1 byte) +- * Reg id (1 byte, constant 'r') +- * book, page, register for pilot threshold, pilot tone +- * and sine gain (12 bytes) +- * for (i = 0; i < Device-Sum; i++) { +- * Device #i index_info (1 byte) +- * Sine gain for Device #i (8 bytes) +- * } +- */ +- ext_cali_start->max = 14 + priv->ndev * 9; +- cali_ctrls[i].name = cali_start_name; +- cali_ctrls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; +- cali_ctrls[i].info = snd_soc_bytes_info_ext; +- cali_ctrls[i].put = tas2781_calib_start_put; +- cali_ctrls[i].get = tasdev_nop_get; +- cali_ctrls[i].private_value = (unsigned long)ext_cali_start; ++ rc = create_tas2781_cali_start_ktrl(priv, &cali_ctrls[i]); ++ if (rc != 0) ++ return rc; + i++; ++ + } + + return snd_soc_add_component_controls(priv->codec, cali_ctrls, +@@ -1765,13 +1902,25 @@ static int tasdevice_dapm_event(struct snd_soc_dapm_widget *w, + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + int state = 0; + +- /* Codec Lock Hold */ +- mutex_lock(&tas_priv->codec_lock); ++ guard(mutex)(&tas_priv->codec_lock); + if (event == SND_SOC_DAPM_PRE_PMD) + state = 1; +- tasdevice_tuning_switch(tas_priv, state); +- /* Codec Lock Release*/ +- mutex_unlock(&tas_priv->codec_lock); ++ tasdevice_tuning_switch(tas_priv, state, false); ++ ++ return 0; ++} ++ ++static int tasdevice_capture_dapm_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *codec = snd_soc_dapm_to_component(w->dapm); ++ struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); ++ int state = 0; ++ ++ guard(mutex)(&tas_priv->codec_lock); ++ if (event == SND_SOC_DAPM_PRE_PMD) ++ state = 1; ++ tasdevice_tuning_switch(tas_priv, state, true); + + return 0; + } +@@ -1779,7 +1928,7 @@ static int tasdevice_dapm_event(struct snd_soc_dapm_widget *w, + static const struct snd_soc_dapm_widget tasdevice_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("ASI", "ASI Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT_E("ASI OUT", "ASI Capture", 0, SND_SOC_NOPM, +- 0, 0, tasdevice_dapm_event, ++ 0, 0, tasdevice_capture_dapm_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SPK("SPK", tasdevice_dapm_event), + SND_SOC_DAPM_OUTPUT("OUT"), +diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c +--- a/sound/soc/codecs/tas2783-sdw.c ++++ b/sound/soc/codecs/tas2783-sdw.c +@@ -12,6 +12,7 @@ + // Author: Baojun Xu + // Author: Kevin Lu + ++#include + #include + #include + #include +@@ -494,6 +495,56 @@ static bool tas2783_readable_register(struct device *dev, unsigned int reg) + return tas2783_sdca_mbq_size(dev, reg) > 0; + } + ++static bool tas2783_writeable_register(struct device *dev, unsigned int reg) ++{ ++ /* ++ * The Latency Control of every Entity, together with the Power Domain ++ * actual state and the protection status, is read-only. They are ++ * listed in tas2783_reg_default[] with a placeholder value, so without ++ * this a regcache_sync() would try to write them back and the ++ * peripheral would reject the transaction, aborting the sync. ++ */ ++ switch (reg) { ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU21, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU23, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU26, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x07, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS24, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS21, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS25, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS26, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS28, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PDE23, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_UDMPU23, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x05, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x11, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU21, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU26, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT21, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT29, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT26, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT28, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT24, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT23, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT25, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT28, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MU26, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT127, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU127, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS127, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x04, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x04, 0): ++ return false; ++ ++ default: ++ return tas2783_sdca_mbq_size(dev, reg) > 0; ++ } ++} ++ + static bool tas2783_volatile_register(struct device *dev, u32 reg) + { + switch (reg) { +@@ -515,6 +566,7 @@ static const struct regmap_config tas_regmap = { + .reg_bits = 32, + .val_bits = 8, + .readable_reg = tas2783_readable_register, ++ .writeable_reg = tas2783_writeable_register, + .volatile_reg = tas2783_volatile_register, + .reg_defaults = tas2783_reg_default, + .num_reg_defaults = ARRAY_SIZE(tas2783_reg_default), +@@ -692,12 +744,12 @@ static s32 tas2783_update_calibdata(struct tas2783_prv *tas_dev) + return 0; + } + +- mutex_lock(&tas_dev->calib_lock); +- ret = tas2783_validate_calibdata(tas_dev, tas_dev->cali_data.data, +- tas_dev->cali_data.read_sz); +- if (!ret) +- tas2783_set_calib_params_to_device(tas_dev, tmp_val); +- mutex_unlock(&tas_dev->calib_lock); ++ scoped_guard(mutex, &tas_dev->calib_lock) { ++ ret = tas2783_validate_calibdata(tas_dev, tas_dev->cali_data.data, ++ tas_dev->cali_data.read_sz); ++ if (!ret) ++ tas2783_set_calib_params_to_device(tas_dev, tmp_val); ++ } + + return ret; + } +@@ -926,22 +978,23 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, + dev_err(tas_dev->dev, + "clear latch failed, err=%d", ret); + +- mutex_lock(&tas_dev->pde_lock); +- /* +- * Sometimes, there is error returned during power on. +- * So added retry logic to ensure power on so that +- * port prepare succeeds +- */ +- do { +- ret = regmap_write(tas_dev->regmap, +- SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, +- TAS2783_SDCA_CTL_REQ_POW_STATE, 0), +- TAS2783_SDCA_POW_STATE_ON); +- if (!ret) +- break; +- usleep_range(2000, 2200); +- } while (retry--); +- mutex_unlock(&tas_dev->pde_lock); ++ scoped_guard(mutex, &tas_dev->pde_lock) { ++ /* ++ * Sometimes, there is error returned during power on. ++ * So added retry logic to ensure power on so that ++ * port prepare succeeds ++ */ ++ do { ++ ret = regmap_write(tas_dev->regmap, ++ SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, ++ TAS2783_SDCA_CTL_REQ_POW_STATE, 0), ++ TAS2783_SDCA_POW_STATE_ON); ++ if (!ret) ++ break; ++ usleep_range(2000, 2200); ++ } while (retry--); ++ } ++ + if (ret) + return ret; + +@@ -965,7 +1018,6 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, + static s32 tas_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { +- s32 ret; + struct snd_soc_component *component = dai->component; + struct tas2783_prv *tas_dev = + snd_soc_component_get_drvdata(component); +@@ -974,14 +1026,11 @@ static s32 tas_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + + sdw_stream_remove_slave(tas_dev->sdw_peripheral, sdw_stream); + +- mutex_lock(&tas_dev->pde_lock); +- ret = regmap_write(tas_dev->regmap, +- SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, +- TAS2783_SDCA_CTL_REQ_POW_STATE, 0), +- TAS2783_SDCA_POW_STATE_OFF); +- mutex_unlock(&tas_dev->pde_lock); +- +- return ret; ++ guard(mutex)(&tas_dev->pde_lock); ++ return regmap_write(tas_dev->regmap, ++ SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, ++ TAS2783_SDCA_CTL_REQ_POW_STATE, 0), ++ TAS2783_SDCA_POW_STATE_OFF); + } + + static const struct snd_soc_dai_ops tas_dai_ops = { +@@ -1098,7 +1147,13 @@ static s32 tas2783_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(tas_dev->regmap, false); +- regcache_sync(tas_dev->regmap); ++ ret = regcache_sync(tas_dev->regmap); ++ if (ret) { ++ regcache_cache_only(tas_dev->regmap, true); ++ regcache_mark_dirty(tas_dev->regmap); ++ return ret; ++ } ++ + return 0; + } + +@@ -1224,9 +1279,23 @@ static s32 tas_update_status(struct sdw_slave *slave, + if (tas_dev->hw_init || tas_dev->status != SDW_SLAVE_ATTACHED) + return 0; + +- /* updated the cache data to device */ + regcache_cache_only(tas_dev->regmap, false); +- regcache_sync(tas_dev->regmap); ++ ++ /* ++ * The device is attaching uninitialized: either this is the first ++ * attach, or it lost power (and with it all register and DSP state) ++ * while the controller was power-gated during system suspend. The ++ * cache still holds the pre-suspend values, and tas_io_init() below ++ * resets the device via TAS2783_SW_RESET anyway, so syncing it back ++ * is both useless and harmful: later read-modify-write updates would ++ * compare against stale data and skip the hardware write. ++ * ++ * Drop the cache instead, so that subsequent accesses see the real ++ * hardware state. Syncing after the reset is not an option either: ++ * the cache accepts registers for which tas2783_sdca_mbq_size() ++ * returns 0, and writing those back fails with -EINVAL. ++ */ ++ regcache_drop_region(tas_dev->regmap, 0, UINT_MAX); + + /* perform I/O transfers required for Slave initialization */ + return tas_io_init(&slave->dev, slave); +@@ -1329,7 +1398,7 @@ static s32 tas_sdw_probe(struct sdw_slave *peripheral, + function_data->desc = &peripheral->sdca_data.function[i]; + + /* Parse the function */ +- ret = sdca_parse_function(dev, peripheral, function_data); ++ ret = sdca_parse_function(dev, function_data); + if (!ret) + tas_dev->sa_func_data = function_data; + else +diff --git a/sound/soc/codecs/tas5805m.c b/sound/soc/codecs/tas5805m.c +--- a/sound/soc/codecs/tas5805m.c ++++ b/sound/soc/codecs/tas5805m.c +@@ -12,6 +12,7 @@ + // + // It has been simplified a little and reworked for the 5.x ALSA SoC API. + ++#include + #include + #include + #include +@@ -230,10 +231,9 @@ static int tas5805m_vol_get(struct snd_kcontrol *kcontrol, + struct tas5805m_priv *tas5805m = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + ucontrol->value.integer.value[0] = tas5805m->vol[0]; + ucontrol->value.integer.value[1] = tas5805m->vol[1]; +- mutex_unlock(&tas5805m->lock); + + return 0; + } +@@ -249,13 +249,12 @@ static int tas5805m_vol_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct tas5805m_priv *tas5805m = + snd_soc_component_get_drvdata(component); +- int ret = 0; + + if (!(volume_is_valid(ucontrol->value.integer.value[0]) && + volume_is_valid(ucontrol->value.integer.value[1]))) + return -EINVAL; + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + if (tas5805m->vol[0] != ucontrol->value.integer.value[0] || + tas5805m->vol[1] != ucontrol->value.integer.value[1]) { + tas5805m->vol[0] = ucontrol->value.integer.value[0]; +@@ -265,11 +264,10 @@ static int tas5805m_vol_put(struct snd_kcontrol *kcontrol, + tas5805m->is_powered); + if (tas5805m->is_powered) + tas5805m_refresh(tas5805m); +- ret = 1; ++ return 1; + } +- mutex_unlock(&tas5805m->lock); + +- return ret; ++ return 0; + } + + static const struct snd_kcontrol_new tas5805m_snd_controls[] = { +@@ -332,7 +330,7 @@ static void do_work(struct work_struct *work) + + dev_dbg(&tas5805m->i2c->dev, "DSP startup\n"); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + /* We mustn't issue any I2C transactions until the I2S + * clock is stable. Furthermore, we must allow a 5ms + * delay after the first set of register writes to +@@ -345,7 +343,6 @@ static void do_work(struct work_struct *work) + + tas5805m->is_powered = true; + tas5805m_refresh(tas5805m); +- mutex_unlock(&tas5805m->lock); + } + + static int tas5805m_dac_event(struct snd_soc_dapm_widget *w, +@@ -362,7 +359,7 @@ static int tas5805m_dac_event(struct snd_soc_dapm_widget *w, + dev_dbg(component->dev, "DSP shutdown\n"); + cancel_work_sync(&tas5805m->work); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + if (tas5805m->is_powered) { + tas5805m->is_powered = false; + +@@ -379,7 +376,6 @@ static int tas5805m_dac_event(struct snd_soc_dapm_widget *w, + + regmap_write(rm, REG_DEVICE_CTRL_2, DCTRL2_MODE_HIZ); + } +- mutex_unlock(&tas5805m->lock); + } + + return 0; +@@ -414,14 +410,13 @@ static int tas5805m_mute(struct snd_soc_dai *dai, int mute, int direction) + struct tas5805m_priv *tas5805m = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + dev_dbg(component->dev, "set mute=%d (is_powered=%d)\n", + mute, tas5805m->is_powered); + + tas5805m->is_muted = mute; + if (tas5805m->is_powered) + tas5805m_refresh(tas5805m); +- mutex_unlock(&tas5805m->lock); + + return 0; + } +@@ -462,7 +457,6 @@ static int tas5805m_i2c_probe(struct i2c_client *i2c) + struct tas5805m_priv *tas5805m; + char filename[128]; + const char *config_name; +- const struct firmware *fw; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &tas5805m_regmap); +@@ -507,24 +501,20 @@ static int tas5805m_i2c_probe(struct i2c_client *i2c) + + snprintf(filename, sizeof(filename), "tas5805m_dsp_%s.bin", + config_name); ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, filename, dev); + if (ret) + return ret; + + if ((fw->size < 2) || (fw->size & 1)) { + dev_err(dev, "firmware is invalid\n"); +- release_firmware(fw); + return -EINVAL; + } + + tas5805m->dsp_cfg_len = fw->size; + tas5805m->dsp_cfg_data = devm_kmemdup(dev, fw->data, fw->size, GFP_KERNEL); +- if (!tas5805m->dsp_cfg_data) { +- release_firmware(fw); ++ if (!tas5805m->dsp_cfg_data) + return -ENOMEM; +- } +- +- release_firmware(fw); + + /* Do the first part of the power-on here, while we can expect + * the I2S interface to be quiet. We must raise PDN# and then +diff --git a/sound/soc/codecs/tas675x.c b/sound/soc/codecs/tas675x.c +--- a/sound/soc/codecs/tas675x.c ++++ b/sound/soc/codecs/tas675x.c +@@ -1924,15 +1924,6 @@ static const struct reg_default tas675x_reg_defaults[] = { + { TAS675X_PWM_PHASE_M_CTRL_CH2_REG, 0x00 }, + { TAS675X_PWM_PHASE_M_CTRL_CH3_REG, 0x00 }, + { TAS675X_PWM_PHASE_M_CTRL_CH4_REG, 0x00 }, +- { TAS675X_DC_LDG_CTRL_REG, 0x00 }, +- { TAS675X_DC_LDG_LO_CTRL_REG, 0x00 }, +- { TAS675X_DC_LDG_TIME_CTRL_REG, 0x00 }, +- { TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 0x11 }, +- { TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 0x11 }, +- { TAS675X_AC_LDG_CTRL_REG, 0x10 }, +- { TAS675X_TWEETER_DETECT_CTRL_REG, 0x08 }, +- { TAS675X_TWEETER_DETECT_THRESH_REG, 0x00 }, +- { TAS675X_AC_LDG_FREQ_CTRL_REG, 0xC8 }, + { TAS675X_REPORT_ROUTING_1_REG, 0x00 }, + { TAS675X_OTSD_RECOVERY_EN_REG, 0x00 }, + { TAS675X_REPORT_ROUTING_2_REG, 0xA2 }, +@@ -1943,6 +1934,15 @@ static const struct reg_default tas675x_reg_defaults[] = { + { TAS675X_GPIO1_OUTPUT_SEL_REG, 0x00 }, + { TAS675X_GPIO2_OUTPUT_SEL_REG, 0x00 }, + { TAS675X_GPIO_CTRL_REG, TAS675X_GPIO_CTRL_RSTVAL }, ++ { TAS675X_DC_LDG_CTRL_REG, 0x00 }, ++ { TAS675X_DC_LDG_LO_CTRL_REG, 0x00 }, ++ { TAS675X_DC_LDG_TIME_CTRL_REG, 0x00 }, ++ { TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 0x11 }, ++ { TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 0x11 }, ++ { TAS675X_AC_LDG_CTRL_REG, 0x10 }, ++ { TAS675X_TWEETER_DETECT_CTRL_REG, 0x08 }, ++ { TAS675X_TWEETER_DETECT_THRESH_REG, 0x00 }, ++ { TAS675X_AC_LDG_FREQ_CTRL_REG, 0xC8 }, + { TAS675X_OTW_CTRL_CH1_CH2_REG, 0x11 }, + { TAS675X_OTW_CTRL_CH3_CH4_REG, 0x11 }, + }; +diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c +--- a/sound/soc/codecs/tlv320aic26.c ++++ b/sound/soc/codecs/tlv320aic26.c +@@ -320,8 +320,14 @@ static int aic26_probe(struct snd_soc_component *component) + return 0; + } + ++static void aic26_remove(struct snd_soc_component *component) ++{ ++ device_remove_file(component->dev, &dev_attr_keyclick); ++} ++ + static const struct snd_soc_component_driver aic26_soc_component_dev = { + .probe = aic26_probe, ++ .remove = aic26_remove, + .controls = aic26_snd_controls, + .num_controls = ARRAY_SIZE(aic26_snd_controls), + .dapm_widgets = tlv320aic26_dapm_widgets, +diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c +--- a/sound/soc/codecs/tlv320aic31xx.c ++++ b/sound/soc/codecs/tlv320aic31xx.c +@@ -1720,18 +1720,14 @@ static int tlv320dac3100_fw_load(struct aic31xx_priv *aic31xx, + static int tlv320dac3100_load_coeffs(struct aic31xx_priv *aic31xx, + const char *fw_name) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw, fw_name, aic31xx->dev); + if (ret) + return ret; + +- ret = tlv320dac3100_fw_load(aic31xx, fw->data, fw->size); +- +- release_firmware(fw); +- +- return ret; ++ return tlv320dac3100_fw_load(aic31xx, fw->data, fw->size); + } + + static int aic31xx_i2c_probe(struct i2c_client *i2c) +diff --git a/sound/soc/codecs/tlv320aic32x4-clk.c b/sound/soc/codecs/tlv320aic32x4-clk.c +--- a/sound/soc/codecs/tlv320aic32x4-clk.c ++++ b/sound/soc/codecs/tlv320aic32x4-clk.c +@@ -1,5 +1,5 @@ +-/* SPDX-License-Identifier: GPL-2.0 +- * ++// SPDX-License-Identifier: GPL-2.0 ++/* + * Clock Tree for the Texas Instruments TLV320AIC32x4 + * + * Copyright 2019 Annaliese McDermond +@@ -9,6 +9,7 @@ + + #include + #include ++#include + #include + #include + +@@ -57,7 +58,7 @@ static void clk_aic32x4_pll_unprepare(struct clk_hw *hw) + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + + regmap_update_bits(pll->regmap, AIC32X4_PLLPR, +- AIC32X4_PLLEN, 0); ++ AIC32X4_PLLEN, 0); + } + + static int clk_aic32x4_pll_is_prepared(struct clk_hw *hw) +@@ -75,7 +76,7 @@ static int clk_aic32x4_pll_is_prepared(struct clk_hw *hw) + } + + static int clk_aic32x4_pll_get_muldiv(struct clk_aic32x4 *pll, +- struct clk_aic32x4_pll_muldiv *settings) ++ struct clk_aic32x4_pll_muldiv *settings) + { + /* Change to use regmap_bulk_read? */ + unsigned int val; +@@ -106,19 +107,19 @@ static int clk_aic32x4_pll_get_muldiv(struct clk_aic32x4 *pll, + } + + static int clk_aic32x4_pll_set_muldiv(struct clk_aic32x4 *pll, +- struct clk_aic32x4_pll_muldiv *settings) ++ struct clk_aic32x4_pll_muldiv *settings) + { + int ret; + /* Change to use regmap_bulk_write for some if not all? */ + + ret = regmap_update_bits(pll->regmap, AIC32X4_PLLPR, +- AIC32X4_PLL_R_MASK, settings->r); ++ AIC32X4_PLL_R_MASK, settings->r); + if (ret < 0) + return ret; + + ret = regmap_update_bits(pll->regmap, AIC32X4_PLLPR, +- AIC32X4_PLL_P_MASK, +- settings->p << AIC32X4_PLL_P_SHIFT); ++ AIC32X4_PLL_P_MASK, ++ settings->p << AIC32X4_PLL_P_SHIFT); + if (ret < 0) + return ret; + +@@ -136,23 +137,21 @@ static int clk_aic32x4_pll_set_muldiv(struct clk_aic32x4 *pll, + return 0; + } + +-static unsigned long clk_aic32x4_pll_calc_rate( +- struct clk_aic32x4_pll_muldiv *settings, +- unsigned long parent_rate) ++static unsigned long clk_aic32x4_pll_calc_rate(struct clk_aic32x4_pll_muldiv *settings, ++ unsigned long parent_rate) + { + u64 rate; + /* + * We scale j by 10000 to account for the decimal part of P and divide + * it back out later. + */ +- rate = (u64) parent_rate * settings->r * +- ((settings->j * 10000) + settings->d); ++ rate = (u64)parent_rate * settings->r * ((settings->j * 10000) + settings->d); + +- return (unsigned long) DIV_ROUND_UP_ULL(rate, settings->p * 10000); ++ return (unsigned long)DIV_ROUND_UP_ULL(rate, settings->p * 10000); + } + + static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, +- unsigned long rate, unsigned long parent_rate) ++ unsigned long rate, unsigned long parent_rate) + { + u64 multiplier; + +@@ -165,14 +164,14 @@ static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, + * of the multiplier. This is because we can't do floating point + * math in the kernel. + */ +- multiplier = (u64) rate * settings->p * 10000; ++ multiplier = (u64)rate * settings->p * 10000; + do_div(multiplier, parent_rate); + + /* + * J can't be over 64, so R can scale this. + * R can't be greater than 4. + */ +- settings->r = ((u32) multiplier / 640000) + 1; ++ settings->r = ((u32)multiplier / 640000) + 1; + if (settings->r > 4) + return -1; + do_div(multiplier, settings->r); +@@ -184,14 +183,14 @@ static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, + return -1; + + /* Figure out the integer part, J, and the fractional part, D. */ +- settings->j = (u32) multiplier / 10000; +- settings->d = (u32) multiplier % 10000; ++ settings->j = (u32)multiplier / 10000; ++ settings->d = (u32)multiplier % 10000; + + return 0; + } + + static unsigned long clk_aic32x4_pll_recalc_rate(struct clk_hw *hw, +- unsigned long parent_rate) ++ unsigned long parent_rate) + { + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + struct clk_aic32x4_pll_muldiv settings; +@@ -220,8 +219,8 @@ static int clk_aic32x4_pll_determine_rate(struct clk_hw *hw, + } + + static int clk_aic32x4_pll_set_rate(struct clk_hw *hw, +- unsigned long rate, +- unsigned long parent_rate) ++ unsigned long rate, ++ unsigned long parent_rate) + { + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + struct clk_aic32x4_pll_muldiv settings; +@@ -236,7 +235,7 @@ static int clk_aic32x4_pll_set_rate(struct clk_hw *hw, + return ret; + + /* 10ms is the delay to wait before the clocks are stable */ +- msleep(10); ++ usleep_range(10000, 20000); + + return 0; + } +@@ -261,7 +260,6 @@ static u8 clk_aic32x4_pll_get_parent(struct clk_hw *hw) + return (val & AIC32X4_PLL_CLKIN_MASK) >> AIC32X4_PLL_CLKIN_SHIFT; + } + +- + static const struct clk_ops aic32x4_pll_ops = { + .prepare = clk_aic32x4_pll_prepare, + .unprepare = clk_aic32x4_pll_unprepare, +@@ -311,11 +309,11 @@ static void clk_aic32x4_div_unprepare(struct clk_hw *hw) + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + + regmap_update_bits(div->regmap, div->reg, +- AIC32X4_DIVEN, 0); ++ AIC32X4_DIVEN, 0); + } + + static int clk_aic32x4_div_set_rate(struct clk_hw *hw, unsigned long rate, +- unsigned long parent_rate) ++ unsigned long parent_rate) + { + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + u8 divisor; +@@ -342,7 +340,7 @@ static int clk_aic32x4_div_determine_rate(struct clk_hw *hw, + } + + static unsigned long clk_aic32x4_div_recalc_rate(struct clk_hw *hw, +- unsigned long parent_rate) ++ unsigned long parent_rate) + { + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + unsigned int val; +@@ -399,7 +397,7 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + { + .name = "pll", + .parent_names = +- (const char* []) { "mclk", "bclk", "gpio", "din" }, ++ (const char *[]) { "mclk", "bclk", "gpio", "din" }, + .num_parents = 4, + .ops = &aic32x4_pll_ops, + .reg = 0, +@@ -414,28 +412,28 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + }, + { + .name = "ndac", +- .parent_names = (const char * []) { "codec_clkin" }, ++ .parent_names = (const char *[]) { "codec_clkin" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_NDAC, + }, + { + .name = "mdac", +- .parent_names = (const char * []) { "ndac" }, ++ .parent_names = (const char *[]) { "ndac" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_MDAC, + }, + { + .name = "nadc", +- .parent_names = (const char * []) { "codec_clkin" }, ++ .parent_names = (const char *[]) { "codec_clkin" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_NADC, + }, + { + .name = "madc", +- .parent_names = (const char * []) { "nadc" }, ++ .parent_names = (const char *[]) { "nadc" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_MADC, +@@ -451,7 +449,7 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + }; + + static struct clk *aic32x4_register_clk(struct device *dev, +- struct aic32x4_clkdesc *desc) ++ struct aic32x4_clkdesc *desc) + { + struct clk_init_data init; + struct clk_aic32x4 *priv; +@@ -464,8 +462,8 @@ static struct clk *aic32x4_register_clk(struct device *dev, + init.flags = 0; + + priv = devm_kzalloc(dev, sizeof(struct clk_aic32x4), GFP_KERNEL); +- if (priv == NULL) +- return (struct clk *) -ENOMEM; ++ if (!priv) ++ return ERR_PTR(-ENOMEM); + + priv->dev = dev; + priv->hw.init = &init; +diff --git a/sound/soc/codecs/tlv320aic32x4-i2c.c b/sound/soc/codecs/tlv320aic32x4-i2c.c +--- a/sound/soc/codecs/tlv320aic32x4-i2c.c ++++ b/sound/soc/codecs/tlv320aic32x4-i2c.c +@@ -1,11 +1,10 @@ +-/* SPDX-License-Identifier: GPL-2.0 +- * ++// SPDX-License-Identifier: GPL-2.0 ++/* + * Copyright 2011-2019 NW Digital Radio + * + * Author: Annaliese McDermond + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. +- * + */ + + #include +@@ -16,17 +15,23 @@ + + #include "tlv320aic32x4.h" + ++static const struct regmap_config aic32x4_i2c_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = AIC32X4_REFPOWERUP, ++ .ranges = aic32x4_regmap_pages, ++ .num_ranges = 1, ++}; ++ + static int aic32x4_i2c_probe(struct i2c_client *i2c) + { + struct regmap *regmap; +- struct regmap_config config; + enum aic32x4_type type; + +- config = aic32x4_regmap_config; +- config.reg_bits = 8; +- config.val_bits = 8; ++ regmap = devm_regmap_init_i2c(i2c, &aic32x4_i2c_regmap_config); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); + +- regmap = devm_regmap_init_i2c(i2c, &config); + type = (uintptr_t)i2c_get_match_data(i2c); + + return aic32x4_probe(&i2c->dev, regmap, type); +diff --git a/sound/soc/codecs/tlv320aic32x4-spi.c b/sound/soc/codecs/tlv320aic32x4-spi.c +--- a/sound/soc/codecs/tlv320aic32x4-spi.c ++++ b/sound/soc/codecs/tlv320aic32x4-spi.c +@@ -1,11 +1,10 @@ +-/* SPDX-License-Identifier: GPL-2.0 +- * ++// SPDX-License-Identifier: GPL-2.0 ++/* + * Copyright 2011-2019 NW Digital Radio + * + * Author: Annaliese McDermond + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. +- * + */ + + #include +@@ -16,19 +15,25 @@ + + #include "tlv320aic32x4.h" + ++static const struct regmap_config aic32x4_spi_regmap_config = { ++ .reg_bits = 7, ++ .pad_bits = 1, ++ .val_bits = 8, ++ .read_flag_mask = 0x01, ++ .max_register = AIC32X4_REFPOWERUP, ++ .ranges = aic32x4_regmap_pages, ++ .num_ranges = 1, ++}; ++ + static int aic32x4_spi_probe(struct spi_device *spi) + { + struct regmap *regmap; +- struct regmap_config config; + enum aic32x4_type type; + +- config = aic32x4_regmap_config; +- config.reg_bits = 7; +- config.pad_bits = 1; +- config.val_bits = 8; +- config.read_flag_mask = 0x01; ++ regmap = devm_regmap_init_spi(spi, &aic32x4_spi_regmap_config); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); + +- regmap = devm_regmap_init_spi(spi, &config); + type = (uintptr_t)spi_get_device_match_data(spi); + + return aic32x4_probe(&spi->dev, regmap, type); +diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c +--- a/sound/soc/codecs/tlv320aic32x4.c ++++ b/sound/soc/codecs/tlv320aic32x4.c +@@ -1,7 +1,5 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + /* +- * linux/sound/soc/codecs/tlv320aic32x4.c +- * + * Copyright 2011 Vista Silicon S.L. + * + * Author: Javier Martin +@@ -28,7 +26,6 @@ + #include + #include + #include +-#include + + #include "tlv320aic32x4.h" + +@@ -45,7 +42,8 @@ struct aic32x4_priv { + struct regulator *supply_dv; + struct regulator *supply_av; + +- struct aic32x4_setup_data *setup; ++ unsigned int gpio_func[5]; ++ + struct device *dev; + enum aic32x4_type type; + +@@ -75,7 +73,7 @@ static int aic32x4_reset_adc(struct snd_soc_dapm_widget *w, + }; + + static int mic_bias_event(struct snd_soc_dapm_widget *w, +- struct snd_kcontrol *kcontrol, int event) ++ struct snd_kcontrol *kcontrol, int event) + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + +@@ -83,25 +81,23 @@ static int mic_bias_event(struct snd_soc_dapm_widget *w, + case SND_SOC_DAPM_POST_PMU: + /* Change Mic Bias Registor */ + snd_soc_component_update_bits(component, AIC32X4_MICBIAS, +- AIC32x4_MICBIAS_MASK, +- AIC32X4_MICBIAS_LDOIN | +- AIC32X4_MICBIAS_2075V); +- printk(KERN_DEBUG "%s: Mic Bias will be turned ON\n", __func__); ++ AIC32x4_MICBIAS_MASK, ++ AIC32X4_MICBIAS_LDOIN | ++ AIC32X4_MICBIAS_2075V); ++ dev_dbg(component->dev, "Mic Bias will be turned ON\n"); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, AIC32X4_MICBIAS, +- AIC32x4_MICBIAS_MASK, 0); +- printk(KERN_DEBUG "%s: Mic Bias will be turned OFF\n", +- __func__); ++ AIC32x4_MICBIAS_MASK, 0); ++ dev_dbg(component->dev, "Mic Bias will be turned OFF\n"); + break; + } + + return 0; + } + +- + static int aic32x4_get_mfp1_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -114,7 +110,7 @@ static int aic32x4_get_mfp1_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -123,8 +119,7 @@ static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, + val = snd_soc_component_read(component, AIC32X4_DOUTCTL); + gpio_check = (val & AIC32X4_MFP_GPIO_ENABLED); + if (gpio_check != AIC32X4_MFP_GPIO_ENABLED) { +- printk(KERN_ERR "%s: MFP2 is not configure as a GPIO output\n", +- __func__); ++ dev_err(component->dev, "MFP2 is not configure as a GPIO output\n"); + return -EINVAL; + } + +@@ -142,7 +137,7 @@ static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_get_mfp3_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -155,7 +150,7 @@ static int aic32x4_get_mfp3_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -164,8 +159,7 @@ static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, + val = snd_soc_component_read(component, AIC32X4_MISOCTL); + gpio_check = (val & AIC32X4_MFP_GPIO_ENABLED); + if (gpio_check != AIC32X4_MFP_GPIO_ENABLED) { +- printk(KERN_ERR "%s: MFP4 is not configure as a GPIO output\n", +- __func__); ++ dev_err(component->dev, "MFP4 is not configure as a GPIO output\n"); + return -EINVAL; + } + +@@ -183,7 +177,7 @@ static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_get_mfp5_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -195,7 +189,7 @@ static int aic32x4_get_mfp5_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -204,8 +198,7 @@ static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, + val = snd_soc_component_read(component, AIC32X4_GPIOCTL); + gpio_check = (val & AIC32X4_MFP5_GPIO_OUTPUT); + if (gpio_check != AIC32X4_MFP5_GPIO_OUTPUT) { +- printk(KERN_ERR "%s: MFP5 is not configure as a GPIO output\n", +- __func__); ++ dev_err(component->dev, "MFP5 is not configure as a GPIO output\n"); + return -EINVAL; + } + +@@ -222,25 +215,31 @@ static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, + return 0; + }; + +-static const struct snd_kcontrol_new aic32x4_mfp1[] = { +- SOC_SINGLE_BOOL_EXT("MFP1 GPIO", 0, aic32x4_get_mfp1_gpio, NULL), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp2[] = { +- SOC_SINGLE_BOOL_EXT("MFP2 GPIO", 0, NULL, aic32x4_set_mfp2_gpio), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp3[] = { +- SOC_SINGLE_BOOL_EXT("MFP3 GPIO", 0, aic32x4_get_mfp3_gpio, NULL), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp4[] = { +- SOC_SINGLE_BOOL_EXT("MFP4 GPIO", 0, NULL, aic32x4_set_mfp4_gpio), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp5[] = { +- SOC_SINGLE_BOOL_EXT("MFP5 GPIO", 0, aic32x4_get_mfp5_gpio, +- aic32x4_set_mfp5_gpio), ++static const struct { ++ unsigned int reg; ++ struct snd_kcontrol_new ctrl; ++} aic32x4_mfp_cfg[] = { ++ { ++ .reg = AIC32X4_DINCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP1 GPIO", 0, aic32x4_get_mfp1_gpio, NULL), ++ }, ++ { ++ .reg = AIC32X4_DOUTCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP2 GPIO", 0, NULL, aic32x4_set_mfp2_gpio), ++ }, ++ { ++ .reg = AIC32X4_SCLKCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP3 GPIO", 0, aic32x4_get_mfp3_gpio, NULL), ++ }, ++ { ++ .reg = AIC32X4_MISOCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP4 GPIO", 0, NULL, aic32x4_set_mfp4_gpio), ++ }, ++ { ++ .reg = AIC32X4_GPIOCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP5 GPIO", 0, aic32x4_get_mfp5_gpio, ++ aic32x4_set_mfp5_gpio), ++ }, + }; + + /* 0dB min, 0.5dB steps */ +@@ -270,53 +269,42 @@ static SOC_ENUM_SINGLE_DECL(r_ptm_enum, AIC32X4_RPLAYBACK, 2, ptm_text); + + static const struct snd_kcontrol_new aic32x4_snd_controls[] = { + SOC_DOUBLE_R_S_TLV("PCM Playback Volume", AIC32X4_LDACVOL, +- AIC32X4_RDACVOL, 0, -0x7f, 0x30, 7, 0, tlv_pcm), ++ AIC32X4_RDACVOL, 0, -0x7f, 0x30, 7, 0, tlv_pcm), + SOC_ENUM("DAC Left Playback PowerTune Switch", l_ptm_enum), + SOC_ENUM("DAC Right Playback PowerTune Switch", r_ptm_enum), + SOC_DOUBLE_R_S_TLV("HP Driver Gain Volume", AIC32X4_HPLGAIN, +- AIC32X4_HPRGAIN, 0, -0x6, 0x1d, 5, 0, +- tlv_driver_gain), ++ AIC32X4_HPRGAIN, 0, -0x6, 0x1d, 5, 0, tlv_driver_gain), + SOC_DOUBLE_R_S_TLV("LO Driver Gain Volume", AIC32X4_LOLGAIN, +- AIC32X4_LORGAIN, 0, -0x6, 0x1d, 5, 0, +- tlv_driver_gain), ++ AIC32X4_LORGAIN, 0, -0x6, 0x1d, 5, 0, tlv_driver_gain), + SOC_DOUBLE_R("HP DAC Playback Switch", AIC32X4_HPLGAIN, +- AIC32X4_HPRGAIN, 6, 0x01, 1), ++ AIC32X4_HPRGAIN, 6, 0x01, 1), + SOC_DOUBLE_R("LO DAC Playback Switch", AIC32X4_LOLGAIN, +- AIC32X4_LORGAIN, 6, 0x01, 1), ++ AIC32X4_LORGAIN, 6, 0x01, 1), + SOC_ENUM("LO Playback Common Mode Switch", lo_cm_enum), + SOC_DOUBLE_R("Mic PGA Switch", AIC32X4_LMICPGAVOL, +- AIC32X4_RMICPGAVOL, 7, 0x01, 1), ++ AIC32X4_RMICPGAVOL, 7, 0x01, 1), + + SOC_SINGLE("ADCFGA Left Mute Switch", AIC32X4_ADCFGA, 7, 1, 0), + SOC_SINGLE("ADCFGA Right Mute Switch", AIC32X4_ADCFGA, 3, 1, 0), + + SOC_DOUBLE_R_S_TLV("ADC Level Volume", AIC32X4_LADCVOL, +- AIC32X4_RADCVOL, 0, -0x18, 0x28, 6, 0, tlv_adc_vol), ++ AIC32X4_RADCVOL, 0, -0x18, 0x28, 6, 0, tlv_adc_vol), + SOC_DOUBLE_R_TLV("PGA Level Volume", AIC32X4_LMICPGAVOL, +- AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5), ++ AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5), + + SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0), + + SOC_SINGLE("AGC Left Switch", AIC32X4_LAGC1, 7, 1, 0), + SOC_SINGLE("AGC Right Switch", AIC32X4_RAGC1, 7, 1, 0), +- SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1, +- 4, 0x07, 0), +- SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1, +- 0, 0x03, 0), +- SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2, +- 6, 0x03, 0), +- SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2, +- 1, 0x1F, 0), +- SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3, +- 0, 0x7F, 0), +- SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4, +- 3, 0x1F, 0), +- SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5, +- 3, 0x1F, 0), +- SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6, +- 0, 0x1F, 0), +- SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7, +- 0, 0x0F, 0), ++ SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1, 4, 0x07, 0), ++ SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1, 0, 0x03, 0), ++ SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2, 6, 0x03, 0), ++ SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2, 1, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3, 0, 0x7F, 0), ++ SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4, 3, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5, 3, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6, 0, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7, 0, 0x0F, 0), + }; + + static const struct snd_kcontrol_new hpl_output_mixer_controls[] = { +@@ -354,21 +342,27 @@ static SOC_ENUM_SINGLE_DECL(in3r_lpga_n_enum, AIC32X4_LMICPGANIN, 2, resistor_te + static const struct snd_kcontrol_new in1l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L L+ Switch", in1l_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in2l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L L+ Switch", in2l_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in3l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L L+ Switch", in3l_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in1r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R L+ Switch", in1r_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new cml_to_lmixer_controls[] = { + SOC_DAPM_ENUM("CM_L L- Switch", cml_lpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in2r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R L- Switch", in2r_lpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in3r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R L- Switch", in3r_lpga_n_enum), + }; +@@ -385,21 +379,27 @@ static SOC_ENUM_SINGLE_DECL(in3l_rpga_n_enum, AIC32X4_RMICPGANIN, 2, resistor_te + static const struct snd_kcontrol_new in1r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R R+ Switch", in1r_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in2r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R R+ Switch", in2r_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in3r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R R+ Switch", in3r_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in2l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L R+ Switch", in2l_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new cmr_to_rmixer_controls[] = { + SOC_DAPM_ENUM("CM_R R- Switch", cmr_rpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in1l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L R- Switch", in1l_rpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in3l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L R- Switch", in3l_rpga_n_enum), + }; +@@ -428,38 +428,38 @@ static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { + + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", AIC32X4_ADCSETUP, 6, 0), + SND_SOC_DAPM_MUX("IN1_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in1r_to_rmixer_controls), ++ in1r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in2r_to_rmixer_controls), ++ in2r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in3r_to_rmixer_controls), ++ in3r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in2l_to_rmixer_controls), ++ in2l_to_rmixer_controls), + SND_SOC_DAPM_MUX("CM_R to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- cmr_to_rmixer_controls), ++ cmr_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN1_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in1l_to_rmixer_controls), ++ in1l_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in3l_to_rmixer_controls), ++ in3l_to_rmixer_controls), + + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0), + SND_SOC_DAPM_MUX("IN1_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in1l_to_lmixer_controls), ++ in1l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in2l_to_lmixer_controls), ++ in2l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in3l_to_lmixer_controls), ++ in3l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN1_R to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in1r_to_lmixer_controls), ++ in1r_to_lmixer_controls), + SND_SOC_DAPM_MUX("CM_L to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- cml_to_lmixer_controls), ++ cml_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in2r_to_lmixer_controls), ++ in2r_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in3r_to_lmixer_controls), ++ in3r_to_lmixer_controls), + + SND_SOC_DAPM_SUPPLY("Mic Bias", AIC32X4_MICBIAS, 6, 0, mic_bias_event, +- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_POST("ADC Reset", aic32x4_reset_adc), + +@@ -575,7 +575,7 @@ static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { + {"IN3_R to Left Mixer Negative Resistor", "40 kOhm", "IN3_R"}, + }; + +-static const struct regmap_range_cfg aic32x4_regmap_pages[] = { ++const struct regmap_range_cfg aic32x4_regmap_pages[] = { + { + .selector_reg = 0, + .selector_mask = 0xff, +@@ -585,13 +585,7 @@ static const struct regmap_range_cfg aic32x4_regmap_pages[] = { + .range_max = AIC32X4_REFPOWERUP, + }, + }; +- +-const struct regmap_config aic32x4_regmap_config = { +- .max_register = AIC32X4_REFPOWERUP, +- .ranges = aic32x4_regmap_pages, +- .num_ranges = ARRAY_SIZE(aic32x4_regmap_pages), +-}; +-EXPORT_SYMBOL(aic32x4_regmap_config); ++EXPORT_SYMBOL_GPL(aic32x4_regmap_pages); + + static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +@@ -624,7 +618,7 @@ static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) + case SND_SOC_DAIFMT_CBC_CFC: + break; + default: +- printk(KERN_ERR "aic32x4: invalid clock provider\n"); ++ dev_err(component->dev, "invalid clock provider\n"); + return -EINVAL; + } + +@@ -651,19 +645,20 @@ static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) + AIC32X4_IFACE1_DATATYPE_SHIFT); + break; + default: +- printk(KERN_ERR "aic32x4: invalid DAI interface format\n"); ++ dev_err(component->dev, "invalid DAI interface format\n"); + return -EINVAL; + } + + aic32x4->fmt = fmt; + + snd_soc_component_update_bits(component, AIC32X4_IFACE1, +- AIC32X4_IFACE1_DATATYPE_MASK | +- AIC32X4_IFACE1_MASTER_MASK, iface_reg_1); ++ AIC32X4_IFACE1_DATATYPE_MASK | ++ AIC32X4_IFACE1_MASTER_MASK, ++ iface_reg_1); + snd_soc_component_update_bits(component, AIC32X4_IFACE2, +- AIC32X4_DATA_OFFSET_MASK, iface_reg_2); ++ AIC32X4_DATA_OFFSET_MASK, iface_reg_2); + snd_soc_component_update_bits(component, AIC32X4_IFACE3, +- AIC32X4_BCLKINV_MASK, iface_reg_3); ++ AIC32X4_BCLKINV_MASK, iface_reg_3); + + return 0; + } +@@ -676,14 +671,13 @@ static int aic32x4_set_aosr(struct snd_soc_component *component, u8 aosr) + static int aic32x4_set_dosr(struct snd_soc_component *component, u16 dosr) + { + snd_soc_component_write(component, AIC32X4_DOSRMSB, dosr >> 8); +- snd_soc_component_write(component, AIC32X4_DOSRLSB, +- (dosr & 0xff)); ++ snd_soc_component_write(component, AIC32X4_DOSRLSB, dosr & 0xff); + + return 0; + } + + static int aic32x4_set_processing_blocks(struct snd_soc_component *component, +- u8 r_block, u8 p_block) ++ u8 r_block, u8 p_block) + { + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + +@@ -703,6 +697,45 @@ static int aic32x4_set_processing_blocks(struct snd_soc_component *component, + return 0; + } + ++static int aic32x4_configure_rate(struct snd_soc_component *component, ++ unsigned int rate, u8 *aosr, u8 *adc_rc, ++ u8 *dac_rc, u8 *dosr_inc) ++{ ++ struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); ++ u8 prb_rx, prb_tx; ++ ++ if (rate <= 48000) { ++ *aosr = 128; ++ *adc_rc = 6; ++ *dac_rc = 8; ++ *dosr_inc = 8; ++ prb_rx = 1; ++ prb_tx = 1; ++ } else if (rate <= 96000) { ++ *aosr = 64; ++ *adc_rc = 6; ++ *dac_rc = 8; ++ *dosr_inc = 4; ++ prb_rx = 1; ++ prb_tx = (aic32x4->type == AIC32X4_TYPE_TAS2505) ? 1 : 9; ++ } else if (rate == 192000) { ++ *aosr = 32; ++ *adc_rc = 3; ++ *dac_rc = 4; ++ *dosr_inc = 2; ++ prb_rx = 13; ++ prb_tx = (aic32x4->type == AIC32X4_TYPE_TAS2505) ? 1 : 19; ++ } else { ++ dev_err(component->dev, "Sampling rate %u not supported\n", rate); ++ return -EINVAL; ++ } ++ ++ if (aic32x4->type == AIC32X4_TYPE_TAS2505) ++ prb_rx = 0; ++ ++ return aic32x4_set_processing_blocks(component, prb_rx, prb_tx); ++} ++ + static int aic32x4_setup_clocks(struct snd_soc_component *component, + unsigned int sample_rate, unsigned int channels, + unsigned int bit_depth) +@@ -717,7 +750,7 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, + unsigned long adc_clock_rate, dac_clock_rate; + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "pll" }, + { .id = "nadc" }, + { .id = "madc" }, +@@ -729,37 +762,11 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, + if (ret) + return ret; + +- if (sample_rate <= 48000) { +- aosr = 128; +- adc_resource_class = 6; +- dac_resource_class = 8; +- dosr_increment = 8; +- if (aic32x4->type == AIC32X4_TYPE_TAS2505) +- aic32x4_set_processing_blocks(component, 0, 1); +- else +- aic32x4_set_processing_blocks(component, 1, 1); +- } else if (sample_rate <= 96000) { +- aosr = 64; +- adc_resource_class = 6; +- dac_resource_class = 8; +- dosr_increment = 4; +- if (aic32x4->type == AIC32X4_TYPE_TAS2505) +- aic32x4_set_processing_blocks(component, 0, 1); +- else +- aic32x4_set_processing_blocks(component, 1, 9); +- } else if (sample_rate == 192000) { +- aosr = 32; +- adc_resource_class = 3; +- dac_resource_class = 4; +- dosr_increment = 2; +- if (aic32x4->type == AIC32X4_TYPE_TAS2505) +- aic32x4_set_processing_blocks(component, 0, 1); +- else +- aic32x4_set_processing_blocks(component, 13, 19); +- } else { +- dev_err(component->dev, "Sampling rate not supported\n"); +- return -EINVAL; +- } ++ ret = aic32x4_configure_rate(component, sample_rate, &aosr, ++ &adc_resource_class, &dac_resource_class, ++ &dosr_increment); ++ if (ret) ++ return ret; + + /* PCM over I2S is always 2-channel */ + if ((aic32x4->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S) +@@ -781,50 +788,39 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, + (min_mdac * dosr * sample_rate); + for (mdac = min_mdac; mdac <= 128; ++mdac) { + for (ndac = max_ndac; ndac > 0; --ndac) { +- dac_clock_rate = ndac * mdac * dosr * +- sample_rate; +- if (dac_clock_rate == adc_clock_rate) { +- if (clk_round_rate(clocks[0].clk, dac_clock_rate) == 0) +- continue; ++ dac_clock_rate = ndac * mdac * dosr * sample_rate; ++ if (dac_clock_rate != adc_clock_rate) ++ continue; + +- clk_set_rate(clocks[0].clk, +- dac_clock_rate); ++ if (clk_round_rate(clocks[0].clk, dac_clock_rate) == 0) ++ continue; + +- clk_set_rate(clocks[1].clk, +- sample_rate * aosr * +- madc); +- clk_set_rate(clocks[2].clk, +- sample_rate * aosr); +- aic32x4_set_aosr(component, +- aosr); ++ clk_set_rate(clocks[0].clk, dac_clock_rate); + +- clk_set_rate(clocks[3].clk, +- sample_rate * dosr * +- mdac); +- clk_set_rate(clocks[4].clk, +- sample_rate * dosr); +- aic32x4_set_dosr(component, +- dosr); ++ clk_set_rate(clocks[1].clk, sample_rate * aosr * madc); ++ clk_set_rate(clocks[2].clk, sample_rate * aosr); ++ aic32x4_set_aosr(component, aosr); + +- clk_set_rate(clocks[5].clk, +- sample_rate * channels * +- bit_depth); ++ clk_set_rate(clocks[3].clk, sample_rate * dosr * mdac); ++ clk_set_rate(clocks[4].clk, sample_rate * dosr); ++ aic32x4_set_dosr(component, dosr); + +- return 0; +- } ++ clk_set_rate(clocks[5].clk, ++ sample_rate * channels * bit_depth); ++ ++ return 0; + } + } + } + } + +- dev_err(component->dev, +- "Could not set clocks to support sample rate.\n"); ++ dev_err(component->dev, "Could not set clocks to support sample rate.\n"); + return -EINVAL; + } + + static int aic32x4_hw_params(struct snd_pcm_substream *substream, +- struct snd_pcm_hw_params *params, +- struct snd_soc_dai *dai) ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) + { + struct snd_soc_component *component = dai->component; + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); +@@ -837,24 +833,20 @@ static int aic32x4_hw_params(struct snd_pcm_substream *substream, + + switch (params_physical_width(params)) { + case 16: +- iface1_reg |= (AIC32X4_WORD_LEN_16BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_16BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 20: +- iface1_reg |= (AIC32X4_WORD_LEN_20BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_20BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 24: +- iface1_reg |= (AIC32X4_WORD_LEN_24BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_24BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 32: +- iface1_reg |= (AIC32X4_WORD_LEN_32BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_32BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + } + snd_soc_component_update_bits(component, AIC32X4_IFACE1, +- AIC32X4_IFACE1_DATALEN_MASK, iface1_reg); ++ AIC32X4_IFACE1_DATALEN_MASK, iface1_reg); + + if (params_channels(params) == 1) { + dacsetup_reg = AIC32X4_RDAC2LCHN | AIC32X4_LDAC2LCHN; +@@ -865,7 +857,7 @@ static int aic32x4_hw_params(struct snd_pcm_substream *substream, + dacsetup_reg = AIC32X4_LDAC2LCHN | AIC32X4_RDAC2RCHN; + } + snd_soc_component_update_bits(component, AIC32X4_DACSETUP, +- AIC32X4_DAC_CHAN_MASK, dacsetup_reg); ++ AIC32X4_DAC_CHAN_MASK, dacsetup_reg); + + return 0; + } +@@ -875,7 +867,7 @@ static int aic32x4_mute(struct snd_soc_dai *dai, int mute, int direction) + struct snd_soc_component *component = dai->component; + + snd_soc_component_update_bits(component, AIC32X4_DACMUTE, +- AIC32X4_MUTEON, mute ? AIC32X4_MUTEON : 0); ++ AIC32X4_MUTEON, mute ? AIC32X4_MUTEON : 0); + + return 0; + } +@@ -886,7 +878,7 @@ static int aic32x4_set_bias_level(struct snd_soc_component *component, + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "madc" }, + { .id = "mdac" }, + { .id = "bdiv" }, +@@ -955,44 +947,14 @@ static void aic32x4_setup_gpios(struct snd_soc_component *component) + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + + /* setup GPIO functions */ +- /* MFP1 */ +- if (aic32x4->setup->gpio_func[0] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_DINCTL, +- aic32x4->setup->gpio_func[0]); +- snd_soc_add_component_controls(component, aic32x4_mfp1, +- ARRAY_SIZE(aic32x4_mfp1)); +- } ++ BUILD_BUG_ON(ARRAY_SIZE(aic32x4->gpio_func) != ARRAY_SIZE(aic32x4_mfp_cfg)); ++ for (int i = 0; i < ARRAY_SIZE(aic32x4->gpio_func); i++) { ++ if (aic32x4->gpio_func[i] == AIC32X4_MFPX_DEFAULT_VALUE) ++ continue; + +- /* MFP2 */ +- if (aic32x4->setup->gpio_func[1] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_DOUTCTL, +- aic32x4->setup->gpio_func[1]); +- snd_soc_add_component_controls(component, aic32x4_mfp2, +- ARRAY_SIZE(aic32x4_mfp2)); +- } +- +- /* MFP3 */ +- if (aic32x4->setup->gpio_func[2] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_SCLKCTL, +- aic32x4->setup->gpio_func[2]); +- snd_soc_add_component_controls(component, aic32x4_mfp3, +- ARRAY_SIZE(aic32x4_mfp3)); +- } +- +- /* MFP4 */ +- if (aic32x4->setup->gpio_func[3] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_MISOCTL, +- aic32x4->setup->gpio_func[3]); +- snd_soc_add_component_controls(component, aic32x4_mfp4, +- ARRAY_SIZE(aic32x4_mfp4)); +- } +- +- /* MFP5 */ +- if (aic32x4->setup->gpio_func[4] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_GPIOCTL, +- aic32x4->setup->gpio_func[4]); +- snd_soc_add_component_controls(component, aic32x4_mfp5, +- ARRAY_SIZE(aic32x4_mfp5)); ++ snd_soc_component_write(component, aic32x4_mfp_cfg[i].reg, ++ aic32x4->gpio_func[i]); ++ snd_soc_add_component_controls(component, &aic32x4_mfp_cfg[i].ctrl, 1); + } + } + +@@ -1002,7 +964,7 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + u32 tmp_reg; + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "codec_clkin" }, + { .id = "pll" }, + { .id = "bdiv" }, +@@ -1013,8 +975,7 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + if (ret) + return ret; + +- if (aic32x4->setup) +- aic32x4_setup_gpios(component); ++ aic32x4_setup_gpios(component); + + clk_set_parent(clocks[0].clk, clocks[1].clk); + clk_set_parent(clocks[2].clk, clocks[3].clk); +@@ -1022,7 +983,7 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + /* Power platform configuration */ + if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) { + snd_soc_component_write(component, AIC32X4_MICBIAS, +- AIC32X4_MICBIAS_LDOIN | AIC32X4_MICBIAS_2075V); ++ AIC32X4_MICBIAS_LDOIN | AIC32X4_MICBIAS_2075V); + } + if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) + snd_soc_component_write(component, AIC32X4_PWRCFG, AIC32X4_AVDDWEAKDISABLE); +@@ -1041,16 +1002,16 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + /* Mic PGA routing */ + if (aic32x4->micpga_routing & AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K) + snd_soc_component_write(component, AIC32X4_LMICPGANIN, +- AIC32X4_LMICPGANIN_IN2R_10K); ++ AIC32X4_LMICPGANIN_IN2R_10K); + else + snd_soc_component_write(component, AIC32X4_LMICPGANIN, +- AIC32X4_LMICPGANIN_CM1L_10K); ++ AIC32X4_LMICPGANIN_CM1L_10K); + if (aic32x4->micpga_routing & AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K) + snd_soc_component_write(component, AIC32X4_RMICPGANIN, +- AIC32X4_RMICPGANIN_IN1L_10K); ++ AIC32X4_RMICPGANIN_IN1L_10K); + else + snd_soc_component_write(component, AIC32X4_RMICPGANIN, +- AIC32X4_RMICPGANIN_CM1R_10K); ++ AIC32X4_RMICPGANIN_CM1R_10K); + + /* + * Workaround: for an unknown reason, the ADC needs to be powered up +@@ -1058,8 +1019,8 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + * a HW BUG or some kind of behavior not documented in the datasheet. + */ + tmp_reg = snd_soc_component_read(component, AIC32X4_ADCSETUP); +- snd_soc_component_write(component, AIC32X4_ADCSETUP, tmp_reg | +- AIC32X4_LADC_EN | AIC32X4_RADC_EN); ++ snd_soc_component_write(component, AIC32X4_ADCSETUP, ++ tmp_reg | AIC32X4_LADC_EN | AIC32X4_RADC_EN); + snd_soc_component_write(component, AIC32X4_ADCSETUP, tmp_reg); + + /* +@@ -1102,13 +1063,13 @@ static const struct snd_kcontrol_new aic32x4_tas2505_snd_controls[] = { + SOC_ENUM("DAC Playback PowerTune Switch", l_ptm_enum), + + SOC_SINGLE_TLV("HP Driver Gain Volume", +- AIC32X4_HPLGAIN, 0, 0x74, 1, tlv_tas_driver_gain), ++ AIC32X4_HPLGAIN, 0, 0x74, 1, tlv_tas_driver_gain), + SOC_SINGLE("HP DAC Playback Switch", AIC32X4_HPLGAIN, 6, 1, 1), + + SOC_SINGLE_TLV("Speaker Driver Playback Volume", +- TAS2505_SPKVOL1, 0, 0x74, 1, tlv_tas_driver_gain), ++ TAS2505_SPKVOL1, 0, 0x74, 1, tlv_tas_driver_gain), + SOC_SINGLE_TLV("Speaker Amplifier Playback Volume", +- TAS2505_SPKVOL2, 4, 5, 0, tlv_amp_vol), ++ TAS2505_SPKVOL2, 4, 5, 0, tlv_amp_vol), + + SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0), + }; +@@ -1144,11 +1105,12 @@ static const struct snd_soc_dapm_route aic32x4_tas2505_dapm_routes[] = { + static struct snd_soc_dai_driver aic32x4_tas2505_dai = { + .name = "tas2505-hifi", + .playback = { +- .stream_name = "Playback", +- .channels_min = 1, +- .channels_max = 2, +- .rates = SNDRV_PCM_RATE_8000_96000, +- .formats = AIC32X4_FORMATS,}, ++ .stream_name = "Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = AIC32X4_FORMATS, ++ }, + .ops = &aic32x4_ops, + .symmetric_rate = 1, + }; +@@ -1159,7 +1121,7 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) + u32 tmp_reg; + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "codec_clkin" }, + { .id = "pll" }, + { .id = "bdiv" }, +@@ -1170,8 +1132,7 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) + if (ret) + return ret; + +- if (aic32x4->setup) +- aic32x4_setup_gpios(component); ++ aic32x4_setup_gpios(component); + + clk_set_parent(clocks[0].clk, clocks[1].clk); + clk_set_parent(clocks[2].clk, clocks[3].clk); +@@ -1192,7 +1153,7 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) + snd_soc_component_write(component, AIC32X4_CMMODE, tmp_reg); + + /* +- * Enable the fast charging feature and ensure the needed 40ms ellapsed ++ * Enable the fast charging feature and ensure the needed 40ms elapsed + * before using the analog circuits. + */ + snd_soc_component_write(component, TAS2505_REFPOWERUP, +@@ -1218,17 +1179,10 @@ static const struct snd_soc_component_driver soc_component_dev_aic32x4_tas2505 = + .endianness = 1, + }; + +-static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, +- struct device_node *np) ++static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, struct device_node *np) + { +- struct aic32x4_setup_data *aic32x4_setup; + int ret; + +- aic32x4_setup = devm_kzalloc(aic32x4->dev, sizeof(*aic32x4_setup), +- GFP_KERNEL); +- if (!aic32x4_setup) +- return -ENOMEM; +- + ret = of_property_match_string(np, "clock-names", "mclk"); + if (ret < 0) + return -EINVAL; +@@ -1245,9 +1199,11 @@ static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, + gpiod_set_consumer_name(aic32x4->rstn_gpio, "tlv320aic32x4_rstn"); + } + +- if (of_property_read_u32_array(np, "aic32x4-gpio-func", +- aic32x4_setup->gpio_func, 5) >= 0) +- aic32x4->setup = aic32x4_setup; ++ for (int i = 0; i < ARRAY_SIZE(aic32x4->gpio_func); i++) ++ aic32x4->gpio_func[i] = AIC32X4_MFPX_DEFAULT_VALUE; ++ of_property_read_u32_array(np, "aic32x4-gpio-func", ++ aic32x4->gpio_func, ARRAY_SIZE(aic32x4->gpio_func)); ++ + return 0; + } + +@@ -1266,7 +1222,7 @@ static void aic32x4_disable_regulators(struct aic32x4_priv *aic32x4) + } + + static int aic32x4_setup_regulators(struct device *dev, +- struct aic32x4_priv *aic32x4) ++ struct aic32x4_priv *aic32x4) + { + int ret = 0; + +@@ -1356,12 +1312,8 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap, + struct device_node *np = dev->of_node; + int ret; + +- if (IS_ERR(regmap)) +- return PTR_ERR(regmap); +- +- aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv), +- GFP_KERNEL); +- if (aic32x4 == NULL) ++ aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv), GFP_KERNEL); ++ if (!aic32x4) + return -ENOMEM; + + aic32x4->dev = dev; +@@ -1405,11 +1357,13 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap, + switch (aic32x4->type) { + case AIC32X4_TYPE_TAS2505: + ret = devm_snd_soc_register_component(dev, +- &soc_component_dev_aic32x4_tas2505, &aic32x4_tas2505_dai, 1); ++ &soc_component_dev_aic32x4_tas2505, ++ &aic32x4_tas2505_dai, 1); + break; + default: + ret = devm_snd_soc_register_component(dev, +- &soc_component_dev_aic32x4, &aic32x4_dai, 1); ++ &soc_component_dev_aic32x4, ++ &aic32x4_dai, 1); + } + + if (ret) { +diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h +--- a/sound/soc/codecs/tlv320aic32x4.h ++++ b/sound/soc/codecs/tlv320aic32x4.h +@@ -3,7 +3,6 @@ + * tlv320aic32x4.h + */ + +- + #ifndef _TLV320AIC32X4_H + #define _TLV320AIC32X4_H + +@@ -16,7 +15,7 @@ enum aic32x4_type { + AIC32X4_TYPE_TAS2505, + }; + +-extern const struct regmap_config aic32x4_regmap_config; ++extern const struct regmap_range_cfg aic32x4_regmap_pages[]; + int aic32x4_probe(struct device *dev, struct regmap *regmap, + enum aic32x4_type type); + void aic32x4_remove(struct device *dev); +@@ -24,7 +23,7 @@ int aic32x4_register_clocks(struct device *dev, const char *mclk_name); + + /* tlv320aic32x4 register space (in decimal to match datasheet) */ + +-#define AIC32X4_REG(page, reg) ((page * 128) + reg) ++#define AIC32X4_REG(page, reg) (((page) * 128) + (reg)) + + #define AIC32X4_PSEL AIC32X4_REG(0, 0) + +@@ -234,4 +233,31 @@ int aic32x4_register_clocks(struct device *dev, const char *mclk_name); + #define AIC32X4_MAX_CODEC_CLKIN_FREQ 110000000 + #define AIC32X4_MAX_PLL_CLKIN 20000000 + ++#define AIC32X4_PWR_MICBIAS_2075_LDOIN 0x00000001 ++#define AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE 0x00000002 ++#define AIC32X4_PWR_AIC32X4_LDO_ENABLE 0x00000004 ++#define AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36 0x00000008 ++#define AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED 0x00000010 ++ ++#define AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K 0x00000001 ++#define AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K 0x00000002 ++ ++/* GPIO API */ ++#define AIC32X4_MFPX_DEFAULT_VALUE 0xff ++ ++#define AIC32X4_MFP1_DIN_DISABLED 0 ++#define AIC32X4_MFP1_DIN_ENABLED 0x2 ++#define AIC32X4_MFP1_GPIO_IN 0x4 ++ ++#define AIC32X4_MFP2_GPIO_OUT_LOW 0x0 ++#define AIC32X4_MFP2_GPIO_OUT_HIGH 0x1 ++ ++#define AIC32X4_MFP_GPIO_ENABLED 0x4 ++ ++#define AIC32X4_MFP5_GPIO_DISABLED 0x0 ++#define AIC32X4_MFP5_GPIO_INPUT 0x8 ++#define AIC32X4_MFP5_GPIO_OUTPUT 0xc ++#define AIC32X4_MFP5_GPIO_OUT_LOW 0x0 ++#define AIC32X4_MFP5_GPIO_OUT_HIGH 0x1 ++ + #endif /* _TLV320AIC32X4_H */ +diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c +--- a/sound/soc/codecs/tlv320dac33.c ++++ b/sound/soc/codecs/tlv320dac33.c +@@ -7,6 +7,7 @@ + * Copyright: (C) 2009 Nokia Corporation + */ + ++#include + #include + #include + #include +@@ -236,13 +237,10 @@ static int dac33_write_locked(struct snd_soc_component *component, unsigned int + unsigned int value) + { + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&dac33->mutex); +- ret = dac33_write(component, reg, value); +- mutex_unlock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + +- return ret; ++ return dac33_write(component, reg, value); + } + + #define DAC33_I2C_ADDR_AUTOINC 0x80 +@@ -365,13 +363,13 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + + /* Safety check */ + if (unlikely(power == dac33->chip_power)) { + dev_dbg(component->dev, "Trying to set the same power state: %s\n", + power ? "ON" : "OFF"); +- goto exit; ++ return ret; + } + + if (power) { +@@ -380,7 +378,7 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret != 0) { + dev_err(component->dev, + "Failed to enable supplies: %d\n", ret); +- goto exit; ++ return ret; + } + + if (dac33->reset_gpiod) { +@@ -388,7 +386,7 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret < 0) { + dev_err(&dac33->i2c->dev, + "Failed to set reset GPIO: %d\n", ret); +- goto exit; ++ return ret; + } + } + +@@ -400,7 +398,7 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret < 0) { + dev_err(&dac33->i2c->dev, + "Failed to set reset GPIO: %d\n", ret); +- goto exit; ++ return ret; + } + } + +@@ -409,14 +407,12 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret != 0) { + dev_err(component->dev, + "Failed to disable supplies: %d\n", ret); +- goto exit; ++ return ret; + } + + dac33->chip_power = 0; + } + +-exit: +- mutex_unlock(&dac33->mutex); + return ret; + } + +@@ -659,7 +655,6 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + { + struct snd_soc_component *component = dac33->component; + unsigned int delay; +- unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: +@@ -667,10 +662,10 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + DAC33_THRREG(dac33->nsample)); + + /* Take the timestamps */ +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp2 = ktime_to_us(ktime_get()); +- dac33->t_stamp1 = dac33->t_stamp2; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ dac33->t_stamp2 = ktime_to_us(ktime_get()); ++ dac33->t_stamp1 = dac33->t_stamp2; ++ } + + dac33_write16(component, DAC33_PREFILL_MSB, + DAC33_THRREG(dac33->alarm_threshold)); +@@ -682,11 +677,11 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + break; + case DAC33_FIFO_MODE7: + /* Take the timestamp */ +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp1 = ktime_to_us(ktime_get()); +- /* Move back the timestamp with drain time */ +- dac33->t_stamp1 -= dac33->mode7_us_to_lthr; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ dac33->t_stamp1 = ktime_to_us(ktime_get()); ++ /* Move back the timestamp with drain time */ ++ dac33->t_stamp1 -= dac33->mode7_us_to_lthr; ++ } + + dac33_write16(component, DAC33_PREFILL_MSB, + DAC33_THRREG(DAC33_MODE7_MARGIN)); +@@ -704,14 +699,12 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + static inline void dac33_playback_handler(struct tlv320dac33_priv *dac33) + { + struct snd_soc_component *component = dac33->component; +- unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: + /* Take the timestamp */ +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp2 = ktime_to_us(ktime_get()); +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) ++ dac33->t_stamp2 = ktime_to_us(ktime_get()); + + dac33_write16(component, DAC33_NSAMPLE_MSB, + DAC33_THRREG(dac33->nsample)); +@@ -735,7 +728,7 @@ static void dac33_work(struct work_struct *work) + dac33 = container_of(work, struct tlv320dac33_priv, work); + component = dac33->component; + +- mutex_lock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + switch (dac33->state) { + case DAC33_PREFILL: + dac33->state = DAC33_PLAYBACK; +@@ -757,18 +750,15 @@ static void dac33_work(struct work_struct *work) + dac33_write(component, DAC33_FIFO_CTRL_A, reg); + break; + } +- mutex_unlock(&dac33->mutex); + } + + static irqreturn_t dac33_interrupt_handler(int irq, void *dev) + { + struct snd_soc_component *component = dev; + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); +- unsigned long flags; + +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp1 = ktime_to_us(ktime_get()); +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) ++ dac33->t_stamp1 = ktime_to_us(ktime_get()); + + /* Do not schedule the workqueue in Mode7 */ + if (dac33->fifo_mode != DAC33_FIFO_MODE7) +@@ -902,14 +892,13 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream, + return -EINVAL; + } + +- mutex_lock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + + if (!dac33->chip_power) { + /* + * Chip is not powered yet. + * Do the init in the dac33_set_bias_level later. + */ +- mutex_unlock(&dac33->mutex); + return 0; + } + +@@ -1053,8 +1042,6 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream, + break; + } + +- mutex_unlock(&dac33->mutex); +- + return 0; + } + +@@ -1156,21 +1143,20 @@ static snd_pcm_sframes_t dac33_dai_delay( + unsigned int time_delta, uthr; + int samples_out, samples_in, samples; + snd_pcm_sframes_t delay = 0; +- unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_BYPASS: + break; + case DAC33_FIFO_MODE1: +- spin_lock_irqsave(&dac33->lock, flags); +- t0 = dac33->t_stamp1; +- t1 = dac33->t_stamp2; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ t0 = dac33->t_stamp1; ++ t1 = dac33->t_stamp2; ++ } + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t1) +- goto out; ++ return 0; + + if (t0 > t1) { + /* +@@ -1230,23 +1216,22 @@ static snd_pcm_sframes_t dac33_dai_delay( + } + break; + case DAC33_FIFO_MODE7: +- spin_lock_irqsave(&dac33->lock, flags); +- t0 = dac33->t_stamp1; +- uthr = dac33->uthr; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ t0 = dac33->t_stamp1; ++ uthr = dac33->uthr; ++ } + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t0) +- goto out; ++ return 0; + + if (t_now <= t0) { + /* + * Either the timestamps are messed or equal. Report + * maximum delay + */ +- delay = uthr; +- goto out; ++ return uthr; + } + + time_delta = t_now - t0; +@@ -1287,7 +1272,7 @@ static snd_pcm_sframes_t dac33_dai_delay( + dac33->fifo_mode); + break; + } +-out: ++ + return delay; + } + +diff --git a/sound/soc/codecs/tscs42xx.c b/sound/soc/codecs/tscs42xx.c +--- a/sound/soc/codecs/tscs42xx.c ++++ b/sound/soc/codecs/tscs42xx.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -210,25 +211,21 @@ static int power_up_audio_plls(struct snd_soc_component *component) + return ret; + } + +- mutex_lock(&tscs42xx->pll_lock); ++ guard(mutex)(&tscs42xx->pll_lock); + + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, mask, val); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL on (%d)\n", ret); +- goto exit; ++ return ret; + } + + if (!plls_locked(component)) { + dev_err(component->dev, "Failed to lock plls\n"); + ret = -ENOMSG; +- goto exit; ++ return ret; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->pll_lock); +- +- return ret; ++ return 0; + } + + static int power_down_audio_plls(struct snd_soc_component *component) +@@ -236,28 +233,24 @@ static int power_down_audio_plls(struct snd_soc_component *component) + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + int ret; + +- mutex_lock(&tscs42xx->pll_lock); ++ guard(mutex)(&tscs42xx->pll_lock); + + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, + RM_PLLCTL1C_PDB_PLL1, + RV_PLLCTL1C_PDB_PLL1_DISABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL off (%d)\n", ret); +- goto exit; ++ return ret; + } + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, + RM_PLLCTL1C_PDB_PLL2, + RV_PLLCTL1C_PDB_PLL2_DISABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL off (%d)\n", ret); +- goto exit; ++ return ret; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->pll_lock); +- +- return ret; ++ return 0; + } + + static int coeff_ram_get(struct snd_kcontrol *kcontrol, +@@ -269,13 +262,11 @@ static int coeff_ram_get(struct snd_kcontrol *kcontrol, + (struct coeff_ram_ctl *)kcontrol->private_value; + struct soc_bytes_ext *params = &ctl->bytes_ext; + +- mutex_lock(&tscs42xx->coeff_ram_lock); ++ guard(mutex)(&tscs42xx->coeff_ram_lock); + + memcpy(ucontrol->value.bytes.data, + &tscs42xx->coeff_ram[ctl->addr * COEFF_SIZE], params->max); + +- mutex_unlock(&tscs42xx->coeff_ram_lock); +- + return 0; + } + +@@ -290,14 +281,14 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + unsigned int coeff_cnt = params->max / COEFF_SIZE; + int ret; + +- mutex_lock(&tscs42xx->coeff_ram_lock); ++ guard(mutex)(&tscs42xx->coeff_ram_lock); + + tscs42xx->coeff_ram_synced = false; + + memcpy(&tscs42xx->coeff_ram[ctl->addr * COEFF_SIZE], + ucontrol->value.bytes.data, params->max); + +- mutex_lock(&tscs42xx->pll_lock); ++ guard(mutex)(&tscs42xx->pll_lock); + + if (plls_locked(component)) { + ret = write_coeff_ram(component, tscs42xx->coeff_ram, +@@ -305,18 +296,12 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + if (ret < 0) { + dev_err(component->dev, + "Failed to flush coeff ram cache (%d)\n", ret); +- goto exit; ++ return ret; + } + tscs42xx->coeff_ram_synced = true; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->pll_lock); +- +- mutex_unlock(&tscs42xx->coeff_ram_lock); +- +- return ret; ++ return 0; + } + + /* Input L Capture Route */ +@@ -385,21 +370,17 @@ static int dac_event(struct snd_soc_dapm_widget *w, + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + int ret; + +- mutex_lock(&tscs42xx->coeff_ram_lock); ++ guard(mutex)(&tscs42xx->coeff_ram_lock); + + if (!tscs42xx->coeff_ram_synced) { + ret = write_coeff_ram(component, tscs42xx->coeff_ram, 0x00, + COEFF_RAM_COEFF_COUNT); + if (ret < 0) +- goto exit; ++ return ret; + tscs42xx->coeff_ram_synced = true; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->coeff_ram_lock); +- +- return ret; ++ return 0; + } + + static const struct snd_soc_dapm_widget tscs42xx_dapm_widgets[] = { +@@ -926,12 +907,10 @@ static int setup_sample_rate(struct snd_soc_component *component, + return ret; + } + +- mutex_lock(&tscs42xx->audio_params_lock); ++ guard(mutex)(&tscs42xx->audio_params_lock); + + tscs42xx->samplerate = rate; + +- mutex_unlock(&tscs42xx->audio_params_lock); +- + return 0; + } + +@@ -1253,12 +1232,10 @@ static int tscs42xx_set_dai_bclk_ratio(struct snd_soc_dai *codec_dai, + return ret; + } + +- mutex_lock(&tscs42xx->audio_params_lock); ++ guard(mutex)(&tscs42xx->audio_params_lock); + + tscs42xx->bclk_ratio = ratio; + +- mutex_unlock(&tscs42xx->audio_params_lock); +- + return 0; + } + +diff --git a/sound/soc/codecs/tscs454.c b/sound/soc/codecs/tscs454.c +--- a/sound/soc/codecs/tscs454.c ++++ b/sound/soc/codecs/tscs454.c +@@ -4,6 +4,7 @@ + // Author: Steven Eckhoff + + #include ++#include + #include + #include + #include +@@ -329,12 +330,10 @@ static int coeff_ram_get(struct snd_kcontrol *kcontrol, + return -EINVAL; + } + +- mutex_lock(coeff_ram_lock); +- +- memcpy(ucontrol->value.bytes.data, +- &coeff_ram[ctl->addr * COEFF_SIZE], params->max); +- +- mutex_unlock(coeff_ram_lock); ++ scoped_guard(mutex, coeff_ram_lock) { ++ memcpy(ucontrol->value.bytes.data, ++ &coeff_ram[ctl->addr * COEFF_SIZE], params->max); ++ } + + return 0; + } +@@ -428,15 +427,15 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + return -EINVAL; + } + +- mutex_lock(coeff_ram_lock); ++ guard(mutex)(coeff_ram_lock); + + *coeff_ram_synced = false; + + memcpy(&coeff_ram[ctl->addr * COEFF_SIZE], + ucontrol->value.bytes.data, params->max); + +- mutex_lock(&tscs454->pll1.lock); +- mutex_lock(&tscs454->pll2.lock); ++ guard(mutex)(&tscs454->pll1.lock); ++ guard(mutex)(&tscs454->pll2.lock); + + val = snd_soc_component_read(component, R_PLLSTAT); + if (val) { /* PLLs locked */ +@@ -446,18 +445,12 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + if (ret < 0) { + dev_err(component->dev, + "Failed to flush coeff ram cache (%d)\n", ret); +- goto exit; ++ return ret; + } + *coeff_ram_synced = true; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs454->pll2.lock); +- mutex_unlock(&tscs454->pll1.lock); +- mutex_unlock(coeff_ram_lock); +- +- return ret; ++ return 0; + } + + static inline int coeff_ram_sync(struct snd_soc_component *component, +@@ -465,41 +458,35 @@ static inline int coeff_ram_sync(struct snd_soc_component *component, + { + int ret; + +- mutex_lock(&tscs454->dac_ram.lock); +- if (!tscs454->dac_ram.synced) { +- ret = write_coeff_ram(component, tscs454->dac_ram.cache, +- R_DACCRS, R_DACCRADD, R_DACCRWDL, +- 0x00, COEFF_RAM_COEFF_COUNT); +- if (ret < 0) { +- mutex_unlock(&tscs454->dac_ram.lock); +- return ret; ++ scoped_guard(mutex, &tscs454->dac_ram.lock) { ++ if (!tscs454->dac_ram.synced) { ++ ret = write_coeff_ram(component, tscs454->dac_ram.cache, ++ R_DACCRS, R_DACCRADD, R_DACCRWDL, ++ 0x00, COEFF_RAM_COEFF_COUNT); ++ if (ret < 0) ++ return ret; + } + } +- mutex_unlock(&tscs454->dac_ram.lock); + +- mutex_lock(&tscs454->spk_ram.lock); +- if (!tscs454->spk_ram.synced) { +- ret = write_coeff_ram(component, tscs454->spk_ram.cache, +- R_SPKCRS, R_SPKCRADD, R_SPKCRWDL, +- 0x00, COEFF_RAM_COEFF_COUNT); +- if (ret < 0) { +- mutex_unlock(&tscs454->spk_ram.lock); +- return ret; ++ scoped_guard(mutex, &tscs454->spk_ram.lock) { ++ if (!tscs454->spk_ram.synced) { ++ ret = write_coeff_ram(component, tscs454->spk_ram.cache, ++ R_SPKCRS, R_SPKCRADD, R_SPKCRWDL, ++ 0x00, COEFF_RAM_COEFF_COUNT); ++ if (ret < 0) ++ return ret; + } + } +- mutex_unlock(&tscs454->spk_ram.lock); + +- mutex_lock(&tscs454->sub_ram.lock); +- if (!tscs454->sub_ram.synced) { +- ret = write_coeff_ram(component, tscs454->sub_ram.cache, +- R_SUBCRS, R_SUBCRADD, R_SUBCRWDL, +- 0x00, COEFF_RAM_COEFF_COUNT); +- if (ret < 0) { +- mutex_unlock(&tscs454->sub_ram.lock); +- return ret; ++ scoped_guard(mutex, &tscs454->sub_ram.lock) { ++ if (!tscs454->sub_ram.synced) { ++ ret = write_coeff_ram(component, tscs454->sub_ram.cache, ++ R_SUBCRS, R_SUBCRADD, R_SUBCRWDL, ++ 0x00, COEFF_RAM_COEFF_COUNT); ++ if (ret < 0) ++ return ret; + } + } +- mutex_unlock(&tscs454->sub_ram.lock); + + return 0; + } +@@ -658,16 +645,14 @@ static int set_sysclk(struct snd_soc_component *component) + + static inline void reserve_pll(struct pll *pll) + { +- mutex_lock(&pll->lock); ++ guard(mutex)(&pll->lock); + pll->users++; +- mutex_unlock(&pll->lock); + } + + static inline void free_pll(struct pll *pll) + { +- mutex_lock(&pll->lock); ++ guard(mutex)(&pll->lock); + pll->users--; +- mutex_unlock(&pll->lock); + } + + static int pll_connected(struct snd_soc_dapm_widget *source, +@@ -679,15 +664,13 @@ static int pll_connected(struct snd_soc_dapm_widget *source, + int users; + + if (strstr(source->name, "PLL 1")) { +- mutex_lock(&tscs454->pll1.lock); +- users = tscs454->pll1.users; +- mutex_unlock(&tscs454->pll1.lock); ++ scoped_guard(mutex, &tscs454->pll1.lock) ++ users = tscs454->pll1.users; + dev_dbg(component->dev, "%s(): PLL 1 users = %d\n", __func__, + users); + } else { +- mutex_lock(&tscs454->pll2.lock); +- users = tscs454->pll2.users; +- mutex_unlock(&tscs454->pll2.lock); ++ scoped_guard(mutex, &tscs454->pll2.lock) ++ users = tscs454->pll2.users; + dev_dbg(component->dev, "%s(): PLL 2 users = %d\n", __func__, + users); + } +@@ -806,7 +789,7 @@ static inline int aif_free(struct snd_soc_component *component, + { + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&tscs454->aifs_status_lock); ++ guard(mutex)(&tscs454->aifs_status_lock); + + dev_dbg(component->dev, "%s(): aif %d\n", __func__, aif->id); + +@@ -829,8 +812,6 @@ static inline int aif_free(struct snd_soc_component *component, + free_pll(tscs454->internal_rate.pll); + } + +- mutex_unlock(&tscs454->aifs_status_lock); +- + return 0; + } + +@@ -3174,7 +3155,7 @@ static int tscs454_hw_params(struct snd_pcm_substream *substream, + unsigned int val; + int ret; + +- mutex_lock(&tscs454->aifs_status_lock); ++ guard(mutex)(&tscs454->aifs_status_lock); + + dev_dbg(component->dev, "%s(): aif %d fs = %u\n", __func__, + aif->id, fs); +@@ -3207,14 +3188,14 @@ static int tscs454_hw_params(struct snd_pcm_substream *substream, + ret = set_aif_fs(component, aif->id, fs); + if (ret < 0) { + dev_err(component->dev, "Failed to set aif fs (%d)\n", ret); +- goto exit; ++ return ret; + } + + ret = set_aif_sample_format(component, params_format(params), aif->id); + if (ret < 0) { + dev_err(component->dev, + "Failed to set aif sample format (%d)\n", ret); +- goto exit; ++ return ret; + } + + set_aif_status_active(&tscs454->aifs_status, aif->id, +@@ -3223,11 +3204,7 @@ static int tscs454_hw_params(struct snd_pcm_substream *substream, + dev_dbg(component->dev, "Set aif %d active. Streams status is 0x%x\n", + aif->id, tscs454->aifs_status.streams); + +- ret = 0; +-exit: +- mutex_unlock(&tscs454->aifs_status_lock); +- +- return ret; ++ return 0; + } + + static int tscs454_hw_free(struct snd_pcm_substream *substream, +diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c +--- a/sound/soc/codecs/twl6040.c ++++ b/sound/soc/codecs/twl6040.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -273,7 +274,7 @@ static void twl6040_hs_jack_report(struct snd_soc_component *component, + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + int status; + +- mutex_lock(&priv->mutex); ++ guard(mutex)(&priv->mutex); + + /* Sync status */ + status = twl6040_read(component, TWL6040_REG_STATUS); +@@ -281,8 +282,6 @@ static void twl6040_hs_jack_report(struct snd_soc_component *component, + snd_soc_jack_report(jack, report, report); + else + snd_soc_jack_report(jack, 0, report); +- +- mutex_unlock(&priv->mutex); + } + + void twl6040_hs_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/uda1342.c b/sound/soc/codecs/uda1342.c +--- a/sound/soc/codecs/uda1342.c ++++ b/sound/soc/codecs/uda1342.c +@@ -308,9 +308,16 @@ static int uda1342_suspend(struct device *dev) + static int uda1342_resume(struct device *dev) + { + struct uda1342_priv *uda1342 = dev_get_drvdata(dev); ++ int ret; + ++ regcache_cache_only(uda1342->regmap, false); + regcache_mark_dirty(uda1342->regmap); +- regcache_sync(uda1342->regmap); ++ ret = regcache_sync(uda1342->regmap); ++ if (ret) { ++ regcache_cache_only(uda1342->regmap, true); ++ regcache_mark_dirty(uda1342->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c +--- a/sound/soc/codecs/wcd9335.c ++++ b/sound/soc/codecs/wcd9335.c +@@ -5010,9 +5010,9 @@ static int wcd9335_power_on_reset(struct wcd9335_codec *wcd) + */ + usleep_range(600, 650); + +- gpiod_set_value(wcd->reset_gpio, 1); ++ gpiod_set_value_cansleep(wcd->reset_gpio, 1); + msleep(20); +- gpiod_set_value(wcd->reset_gpio, 0); ++ gpiod_set_value_cansleep(wcd->reset_gpio, 0); + msleep(20); + + return 0; +diff --git a/sound/soc/codecs/wcd934x.c b/sound/soc/codecs/wcd934x.c +--- a/sound/soc/codecs/wcd934x.c ++++ b/sound/soc/codecs/wcd934x.c +@@ -1265,13 +1265,10 @@ static int wcd934x_set_sido_input_src(struct wcd934x_codec *wcd, int sido_src) + + static int wcd934x_enable_ana_bias_and_sysclk(struct wcd934x_codec *wcd) + { +- mutex_lock(&wcd->sysclk_mutex); +- +- if (++wcd->sysclk_users != 1) { +- mutex_unlock(&wcd->sysclk_mutex); +- return 0; ++ scoped_guard(mutex, &wcd->sysclk_mutex) { ++ if (++wcd->sysclk_users != 1) ++ return 0; + } +- mutex_unlock(&wcd->sysclk_mutex); + + regmap_update_bits(wcd->regmap, WCD934X_ANA_BIAS, + WCD934X_ANA_BIAS_EN_MASK, +@@ -1328,12 +1325,10 @@ static int wcd934x_enable_ana_bias_and_sysclk(struct wcd934x_codec *wcd) + + static int wcd934x_disable_ana_bias_and_syclk(struct wcd934x_codec *wcd) + { +- mutex_lock(&wcd->sysclk_mutex); +- if (--wcd->sysclk_users != 0) { +- mutex_unlock(&wcd->sysclk_mutex); +- return 0; ++ scoped_guard(mutex, &wcd->sysclk_mutex) { ++ if (--wcd->sysclk_users != 0) ++ return 0; + } +- mutex_unlock(&wcd->sysclk_mutex); + + regmap_update_bits(wcd->regmap, WCD934X_CLK_SYS_MCLK_PRG, + WCD934X_EXT_CLK_BUF_EN_MASK | +@@ -2384,7 +2379,7 @@ static int wcd934x_micbias_control(struct snd_soc_component *component, + __func__, micb_num); + return -EINVAL; + } +- mutex_lock(&wcd934x->micb_lock); ++ guard(mutex)(&wcd934x->micb_lock); + + switch (req) { + case MICB_PULLUP_ENABLE: +@@ -2446,8 +2441,6 @@ static int wcd934x_micbias_control(struct snd_soc_component *component, + break; + } + +- mutex_unlock(&wcd934x->micb_lock); +- + return 0; + } + +@@ -2488,7 +2481,7 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) + { + struct wcd934x_codec *wcd934x = snd_soc_component_get_drvdata(component); +- int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; ++ int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -2506,7 +2499,7 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd934x->micb_lock); ++ guard(mutex)(&wcd934x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as +@@ -2521,15 +2514,11 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD934X_MICB_VAL_MASK); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = -EINVAL; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return -EINVAL; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + if (micb_en == WCD934X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, +@@ -2550,9 +2539,8 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + */ + usleep_range(2000, 2100); + } +-exit: +- mutex_unlock(&wcd934x->micb_lock); +- return ret; ++ ++ return 0; + } + + static int wcd934x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +@@ -5899,10 +5887,8 @@ static int wcd934x_codec_probe(struct platform_device *pdev) + } + + static const struct platform_device_id wcd934x_driver_id[] = { +- { +- .name = "wcd934x-codec", +- }, +- {}, ++ { .name = "wcd934x-codec" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, wcd934x_driver_id); + +diff --git a/sound/soc/codecs/wcd937x-sdw.c b/sound/soc/codecs/wcd937x-sdw.c +--- a/sound/soc/codecs/wcd937x-sdw.c ++++ b/sound/soc/codecs/wcd937x-sdw.c +@@ -1084,10 +1084,16 @@ static int wcd937x_sdw_runtime_suspend(struct device *dev) + static int wcd937x_sdw_runtime_resume(struct device *dev) + { + struct wcd937x_sdw_priv *wcd = dev_get_drvdata(dev); ++ int ret; + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); +- regcache_sync(wcd->regmap); ++ ret = regcache_sync(wcd->regmap); ++ if (ret) { ++ regcache_cache_only(wcd->regmap, true); ++ regcache_mark_dirty(wcd->regmap); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/wcd937x.c b/sound/soc/codecs/wcd937x.c +--- a/sound/soc/codecs/wcd937x.c ++++ b/sound/soc/codecs/wcd937x.c +@@ -2,6 +2,7 @@ + // Copyright (c) 2023-2024 Qualcomm Innovation Center, Inc. All rights reserved. + + #include ++#include + #include + #include + #include +@@ -1056,7 +1057,7 @@ static int wcd937x_micbias_control(struct snd_soc_component *component, + return -EINVAL; + } + +- mutex_lock(&wcd937x->micb_lock); ++ guard(mutex)(&wcd937x->micb_lock); + switch (req) { + case MICB_PULLUP_ENABLE: + wcd937x->pullup_ref[micb_index]++; +@@ -1136,7 +1137,6 @@ static int wcd937x_micbias_control(struct snd_soc_component *component, + } + break; + } +- mutex_unlock(&wcd937x->micb_lock); + + return 0; + } +@@ -1460,7 +1460,7 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) + { + struct wcd937x_priv *wcd937x = snd_soc_component_get_drvdata(component); +- int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; ++ int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -1475,7 +1475,7 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd937x->micb_lock); ++ guard(mutex)(&wcd937x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as +@@ -1490,15 +1490,11 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD937X_MICB_VOUT_MASK); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = -EINVAL; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return -EINVAL; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + if (micb_en == WCD937X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, +@@ -1519,9 +1515,8 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + */ + usleep_range(2000, 2100); + } +-exit: +- mutex_unlock(&wcd937x->micb_lock); +- return ret; ++ ++ return 0; + } + + static int wcd937x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/wcd938x-sdw.c b/sound/soc/codecs/wcd938x-sdw.c +--- a/sound/soc/codecs/wcd938x-sdw.c ++++ b/sound/soc/codecs/wcd938x-sdw.c +@@ -1245,10 +1245,16 @@ static int wcd938x_sdw_runtime_suspend(struct device *dev) + static int wcd938x_sdw_runtime_resume(struct device *dev) + { + struct wcd938x_sdw_priv *wcd = dev_get_drvdata(dev); ++ int ret; + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); +- regcache_sync(wcd->regmap); ++ ret = regcache_sync(wcd->regmap); ++ if (ret) { ++ regcache_cache_only(wcd->regmap, true); ++ regcache_mark_dirty(wcd->regmap); ++ return ret; ++ } + } + + pm_runtime_mark_last_busy(dev); +diff --git a/sound/soc/codecs/wcd938x.c b/sound/soc/codecs/wcd938x.c +--- a/sound/soc/codecs/wcd938x.c ++++ b/sound/soc/codecs/wcd938x.c +@@ -1,6 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-only + // Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + ++#include + #include + #include + #include +@@ -1976,7 +1977,7 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) + { + struct wcd938x_priv *wcd938x = snd_soc_component_get_drvdata(component); +- int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; ++ int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -1994,7 +1995,7 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd938x->micb_lock); ++ guard(mutex)(&wcd938x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as +@@ -2009,15 +2010,11 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD938X_MICB_VOUT_MASK); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = -EINVAL; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return -EINVAL; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + if (micb_en == WCD938X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, +@@ -2038,9 +2035,8 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + */ + usleep_range(2000, 2100); + } +-exit: +- mutex_unlock(&wcd938x->micb_lock); +- return ret; ++ ++ return 0; + } + + static int wcd938x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/wcd939x-sdw.c b/sound/soc/codecs/wcd939x-sdw.c +--- a/sound/soc/codecs/wcd939x-sdw.c ++++ b/sound/soc/codecs/wcd939x-sdw.c +@@ -1431,10 +1431,16 @@ static int wcd939x_sdw_runtime_suspend(struct device *dev) + static int wcd939x_sdw_runtime_resume(struct device *dev) + { + struct wcd939x_sdw_priv *wcd = dev_get_drvdata(dev); ++ int ret; + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); +- regcache_sync(wcd->regmap); ++ ret = regcache_sync(wcd->regmap); ++ if (ret) { ++ regcache_cache_only(wcd->regmap, true); ++ regcache_mark_dirty(wcd->regmap); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/wcd939x.c b/sound/soc/codecs/wcd939x.c +--- a/sound/soc/codecs/wcd939x.c ++++ b/sound/soc/codecs/wcd939x.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1923,7 +1924,6 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + struct wcd939x_priv *wcd939x = snd_soc_component_get_drvdata(component); + unsigned int micb_reg, cur_vout_ctl, micb_en; + int req_vout_ctl; +- int ret = 0; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -1941,7 +1941,7 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd939x->micb_lock); ++ guard(mutex)(&wcd939x->micb_lock); + + /* + * If requested micbias voltage is same as current micbias +@@ -1957,15 +1957,11 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD939X_MICB_VOUT_CTL); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = req_vout_ctl; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return req_vout_ctl; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + dev_dbg(component->dev, "%s: micb_num: %d, cur_mv: %d, req_mv: %d, micb_en: %d\n", + __func__, micb_num, WCD_VOUT_CTL_TO_MICB(cur_vout_ctl), +@@ -1990,9 +1986,7 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + usleep_range(2000, 2100); + } + +-exit: +- mutex_unlock(&wcd939x->micb_lock); +- return ret; ++ return 0; + } + + static int wcd939x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/wm0010.c b/sound/soc/codecs/wm0010.c +--- a/sound/soc/codecs/wm0010.c ++++ b/sound/soc/codecs/wm0010.c +@@ -9,6 +9,7 @@ + * Scott Ling + */ + ++#include + #include + #include + #include +@@ -148,13 +149,11 @@ static const char *wm0010_state_to_str(enum wm0010_state state) + static void wm0010_halt(struct snd_soc_component *component) + { + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); +- unsigned long flags; + enum wm0010_state state; + + /* Fetch the wm0010 state */ +- spin_lock_irqsave(&wm0010->irq_lock, flags); +- state = wm0010->state; +- spin_unlock_irqrestore(&wm0010->irq_lock, flags); ++ scoped_guard(spinlock_irqsave, &wm0010->irq_lock) ++ state = wm0010->state; + + switch (state) { + case WM0010_POWER_OFF: +@@ -173,9 +172,8 @@ static void wm0010_halt(struct snd_soc_component *component) + break; + } + +- spin_lock_irqsave(&wm0010->irq_lock, flags); +- wm0010->state = WM0010_POWER_OFF; +- spin_unlock_irqrestore(&wm0010->irq_lock, flags); ++ scoped_guard(spinlock_irqsave, &wm0010->irq_lock) ++ wm0010->state = WM0010_POWER_OFF; + } + + struct wm0010_boot_xfer { +@@ -190,11 +188,9 @@ struct wm0010_boot_xfer { + static void wm0010_mark_boot_failure(struct wm0010_priv *wm0010) + { + enum wm0010_state state; +- unsigned long flags; + +- spin_lock_irqsave(&wm0010->irq_lock, flags); +- state = wm0010->state; +- spin_unlock_irqrestore(&wm0010->irq_lock, flags); ++ scoped_guard(spinlock_irqsave, &wm0010->irq_lock) ++ state = wm0010->state; + + dev_err(wm0010->dev, "Failed to transition from `%s' state to `%s' state\n", + wm0010_state_to_str(state), wm0010_state_to_str(state + 1)); +@@ -337,7 +333,6 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + struct wm0010_boot_xfer *xfer; + int ret; + DECLARE_COMPLETION_ONSTACK(done); +- const struct firmware *fw; + const struct dfw_binrec *rec; + const struct dfw_inforec *inforec; + u64 *img; +@@ -346,6 +341,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + + INIT_LIST_HEAD(&xfer_list); + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, name, component->dev); + if (ret != 0) { + dev_err(component->dev, "Failed to request application(%s): %d\n", +@@ -364,16 +360,14 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + /* First record should be INFO */ + if (rec->command != DFW_CMD_INFO) { + dev_err(component->dev, "First record not INFO\r\n"); +- ret = -EINVAL; +- goto abort; ++ return -EINVAL; + } + + if (inforec->info_version != INFO_VERSION) { + dev_err(component->dev, + "Unsupported version (%02d) of INFO record\r\n", + inforec->info_version); +- ret = -EINVAL; +- goto abort; ++ return -EINVAL; + } + + dev_dbg(component->dev, "Version v%02d INFO record found\r\n", +@@ -382,8 +376,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + /* Check it's a DSP file */ + if (dsp != DEVICE_ID_WM0010) { + dev_err(component->dev, "Not a WM0010 firmware file.\r\n"); +- ret = -EINVAL; +- goto abort; ++ return -EINVAL; + } + + /* Skip the info record as we don't need to send it */ +@@ -408,14 +401,14 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + out = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!out) { + ret = -ENOMEM; +- goto abort1; ++ goto abort; + } + xfer->t.rx_buf = out; + + img = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!img) { + ret = -ENOMEM; +- goto abort1; ++ goto abort; + } + xfer->t.tx_buf = img; + +@@ -453,13 +446,13 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + ret = spi_async(spi, &xfer->m); + if (ret != 0) { + dev_err(component->dev, "Write failed: %d\n", ret); +- goto abort1; ++ goto abort; + } + + if (wm0010->boot_failed) { + dev_dbg(component->dev, "Boot fail!\n"); + ret = -EINVAL; +- goto abort1; ++ goto abort; + } + } + +@@ -467,7 +460,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + + ret = 0; + +-abort1: ++abort: + while (!list_empty(&xfer_list)) { + xfer = list_first_entry(&xfer_list, struct wm0010_boot_xfer, + list); +@@ -477,8 +470,6 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + kfree(xfer); + } + +-abort: +- release_firmware(fw); + return ret; + } + +@@ -486,14 +477,12 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + { + struct spi_device *spi = to_spi_device(component->dev); + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); +- const struct firmware *fw; + struct spi_message m; + struct spi_transfer t; +- u32 *img; +- u8 *out; + int i; + int ret = 0; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, "wm0010_stage2.bin", component->dev); + if (ret != 0) { + dev_err(component->dev, "Failed to request stage2 loader: %d\n", +@@ -504,17 +493,15 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + dev_dbg(component->dev, "Downloading %zu byte stage 2 loader\n", fw->size); + + /* Copy to local buffer first as vmalloc causes problems for dma */ +- img = kmemdup(&fw->data[0], fw->size, GFP_KERNEL | GFP_DMA); +- if (!img) { +- ret = -ENOMEM; +- goto abort2; +- } ++ u32 *img __free(kfree) = ++ kmemdup(&fw->data[0], fw->size, GFP_KERNEL | GFP_DMA); ++ if (!img) ++ return -ENOMEM; + +- out = kzalloc(fw->size, GFP_KERNEL | GFP_DMA); +- if (!out) { +- ret = -ENOMEM; +- goto abort1; +- } ++ u8 *out __free(kfree) = ++ kzalloc(fw->size, GFP_KERNEL | GFP_DMA); ++ if (!out) ++ return -ENOMEM; + + spi_message_init(&m); + memset(&t, 0, sizeof(t)); +@@ -531,7 +518,7 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + ret = spi_sync(spi, &m); + if (ret != 0) { + dev_err(component->dev, "Initial download failed: %d\n", ret); +- goto abort; ++ return ret; + } + + /* Look for errors from the boot ROM */ +@@ -540,18 +527,11 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + dev_err(component->dev, "Boot ROM error: %x in %d\n", + out[i], i); + wm0010_mark_boot_failure(wm0010); +- ret = -EBUSY; +- goto abort; ++ return -EBUSY; + } + } +-abort: +- kfree(out); +-abort1: +- kfree(img); +-abort2: +- release_firmware(fw); + +- return ret; ++ return 0; + } + + static int wm0010_boot(struct snd_soc_component *component) +@@ -734,9 +714,8 @@ static int wm0010_set_bias_level(struct snd_soc_component *component, + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_dapm_get_bias_level(dapm) == SND_SOC_BIAS_PREPARE) { +- mutex_lock(&wm0010->lock); +- wm0010_halt(component); +- mutex_unlock(&wm0010->lock); ++ scoped_guard(mutex, &wm0010->lock) ++ wm0010_halt(component); + } + break; + case SND_SOC_BIAS_OFF: +@@ -832,9 +811,8 @@ static irqreturn_t wm0010_irq(int irq, void *data) + case WM0010_OUT_OF_RESET: + case WM0010_BOOTROM: + case WM0010_STAGE2: +- spin_lock(&wm0010->irq_lock); +- complete(&wm0010->boot_completion); +- spin_unlock(&wm0010->irq_lock); ++ scoped_guard(spinlock, &wm0010->irq_lock) ++ complete(&wm0010->boot_completion); + return IRQ_HANDLED; + default: + return IRQ_NONE; +diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c +--- a/sound/soc/codecs/wm2000.c ++++ b/sound/soc/codecs/wm2000.c +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -612,20 +613,15 @@ static int wm2000_anc_mode_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + unsigned int anc_active = ucontrol->value.integer.value[0]; +- int ret; + + if (anc_active > 1) + return -EINVAL; + +- mutex_lock(&wm2000->lock); ++ guard(mutex)(&wm2000->lock); + + wm2000->anc_active = anc_active; + +- ret = wm2000_anc_set_mode(wm2000); +- +- mutex_unlock(&wm2000->lock); +- +- return ret; ++ return wm2000_anc_set_mode(wm2000); + } + + static int wm2000_speaker_get(struct snd_kcontrol *kcontrol, +@@ -645,20 +641,15 @@ static int wm2000_speaker_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + unsigned int val = ucontrol->value.integer.value[0]; +- int ret; + + if (val > 1) + return -EINVAL; + +- mutex_lock(&wm2000->lock); ++ guard(mutex)(&wm2000->lock); + + wm2000->spk_ena = val; + +- ret = wm2000_anc_set_mode(wm2000); +- +- mutex_unlock(&wm2000->lock); +- +- return ret; ++ return wm2000_anc_set_mode(wm2000); + } + + static const struct snd_kcontrol_new wm2000_controls[] = { +@@ -676,9 +667,8 @@ static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w, + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); +- int ret; + +- mutex_lock(&wm2000->lock); ++ guard(mutex)(&wm2000->lock); + + if (SND_SOC_DAPM_EVENT_ON(event)) + wm2000->anc_eng_ena = 1; +@@ -686,11 +676,7 @@ static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w, + if (SND_SOC_DAPM_EVENT_OFF(event)) + wm2000->anc_eng_ena = 0; + +- ret = wm2000_anc_set_mode(wm2000); +- +- mutex_unlock(&wm2000->lock); +- +- return ret; ++ return wm2000_anc_set_mode(wm2000); + } + + static const struct snd_soc_dapm_widget wm2000_dapm_widgets[] = { +@@ -810,7 +796,7 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) + struct wm2000_priv *wm2000; + struct wm2000_platform_data *pdata; + const char *filename; +- const struct firmware *fw = NULL; ++ const struct firmware *fw __free(firmware) = NULL; + int ret, i; + unsigned int reg; + u16 id; +@@ -828,7 +814,7 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) + ret = PTR_ERR(wm2000->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); +- goto out; ++ return ret; + } + + for (i = 0; i < WM2000_NUM_SUPPLIES; i++) +@@ -922,9 +908,6 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) + + err_supplies: + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); +- +-out: +- release_firmware(fw); + return ret; + } + +diff --git a/sound/soc/codecs/wm2200.c b/sound/soc/codecs/wm2200.c +--- a/sound/soc/codecs/wm2200.c ++++ b/sound/soc/codecs/wm2200.c +@@ -2461,7 +2461,15 @@ static int wm2200_runtime_resume(struct device *dev) + } + + regcache_cache_only(wm2200->regmap, false); +- regcache_sync(wm2200->regmap); ++ ret = regcache_sync(wm2200->regmap); ++ if (ret) { ++ regcache_cache_only(wm2200->regmap, true); ++ regcache_mark_dirty(wm2200->regmap); ++ gpiod_set_value_cansleep(wm2200->ldo_ena, 0); ++ regulator_bulk_disable(ARRAY_SIZE(wm2200->core_supplies), ++ wm2200->core_supplies); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c +--- a/sound/soc/codecs/wm5100.c ++++ b/sound/soc/codecs/wm5100.c +@@ -2659,7 +2659,15 @@ static int wm5100_runtime_resume(struct device *dev) + } + + regcache_cache_only(wm5100->regmap, false); +- regcache_sync(wm5100->regmap); ++ ret = regcache_sync(wm5100->regmap); ++ if (ret) { ++ regcache_cache_only(wm5100->regmap, true); ++ regcache_mark_dirty(wm5100->regmap); ++ gpiod_set_value_cansleep(wm5100->ldo_ena, 0); ++ regulator_bulk_disable(ARRAY_SIZE(wm5100->core_supplies), ++ wm5100->core_supplies); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c +--- a/sound/soc/codecs/wm5102.c ++++ b/sound/soc/codecs/wm5102.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -667,10 +668,9 @@ static int wm5102_out_comp_coeff_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + put_unaligned_be16(arizona->dac_comp_coeff, + ucontrol->value.bytes.data); +- mutex_unlock(&arizona->dac_comp_lock); + + return 0; + } +@@ -681,16 +681,14 @@ static int wm5102_out_comp_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + uint16_t dac_comp_coeff = get_unaligned_be16(ucontrol->value.bytes.data); +- int ret = 0; + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + if (arizona->dac_comp_coeff != dac_comp_coeff) { + arizona->dac_comp_coeff = dac_comp_coeff; +- ret = 1; ++ return 1; + } +- mutex_unlock(&arizona->dac_comp_lock); + +- return ret; ++ return 0; + } + + static int wm5102_out_comp_switch_get(struct snd_kcontrol *kcontrol, +@@ -699,9 +697,8 @@ static int wm5102_out_comp_switch_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + ucontrol->value.integer.value[0] = arizona->dac_comp_enabled; +- mutex_unlock(&arizona->dac_comp_lock); + + return 0; + } +@@ -712,19 +709,17 @@ static int wm5102_out_comp_switch_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; +- int ret = 0; + + if (ucontrol->value.integer.value[0] > mc->max) + return -EINVAL; + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + if (arizona->dac_comp_enabled != ucontrol->value.integer.value[0]) { + arizona->dac_comp_enabled = ucontrol->value.integer.value[0]; +- ret = 1; ++ return 1; + } +- mutex_unlock(&arizona->dac_comp_lock); + +- return ret; ++ return 0; + } + + static const char * const wm5102_osr_text[] = { +diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c +--- a/sound/soc/codecs/wm8731.c ++++ b/sound/soc/codecs/wm8731.c +@@ -10,6 +10,7 @@ + * Based on wm8753.c by Liam Girdwood + */ + ++#include + #include + #include + #include +@@ -110,22 +111,20 @@ static int wm8731_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8731_priv *wm8731 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; +- int ret = 0; + + if (deemph > 1) + return -EINVAL; + +- mutex_lock(&wm8731->lock); ++ guard(mutex)(&wm8731->lock); + if (wm8731->deemph != deemph) { + wm8731->deemph = deemph; + + wm8731_set_deemph(component); + +- ret = 1; ++ return 1; + } +- mutex_unlock(&wm8731->lock); + +- return ret; ++ return 0; + } + + static const DECLARE_TLV_DB_SCALE(in_tlv, -3450, 150, 0); +diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c +--- a/sound/soc/codecs/wm8903.c ++++ b/sound/soc/codecs/wm8903.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -458,22 +459,20 @@ static int wm8903_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8903_priv *wm8903 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; +- int ret = 0; + + if (deemph > 1) + return -EINVAL; + +- mutex_lock(&wm8903->lock); ++ guard(mutex)(&wm8903->lock); + if (wm8903->deemph != deemph) { + wm8903->deemph = deemph; + + wm8903_set_deemph(component); + +- ret = 1; ++ return 1; + } +- mutex_unlock(&wm8903->lock); + +- return ret; ++ return 0; + } + + /* ALSA can only do steps of .01dB */ +diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c +--- a/sound/soc/codecs/wm8940.c ++++ b/sound/soc/codecs/wm8940.c +@@ -333,7 +333,7 @@ static const struct snd_soc_dapm_route wm8940_dapm_routes[] = { + {"ADC", NULL, "Boost Mixer"}, + }; + +-#define wm8940_reset(c) snd_soc_component_write(c, WM8940_SOFTRESET, 0); ++#define wm8940_reset(c) snd_soc_component_write(c, WM8940_SOFTRESET, 0) + + static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c +--- a/sound/soc/codecs/wm8958-dsp2.c ++++ b/sound/soc/codecs/wm8958-dsp2.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -864,9 +865,8 @@ static void wm8958_enh_eq_loaded(const struct firmware *fw, void *context) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "ENH_EQ", fw, true) == 0)) { +- mutex_lock(&wm8994->fw_lock); ++ guard(mutex)(&wm8994->fw_lock); + wm8994->enh_eq = fw; +- mutex_unlock(&wm8994->fw_lock); + } + } + +@@ -876,9 +876,8 @@ static void wm8958_mbc_vss_loaded(const struct firmware *fw, void *context) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "MBC+VSS", fw, true) == 0)) { +- mutex_lock(&wm8994->fw_lock); ++ guard(mutex)(&wm8994->fw_lock); + wm8994->mbc_vss = fw; +- mutex_unlock(&wm8994->fw_lock); + } + } + +@@ -888,9 +887,8 @@ static void wm8958_mbc_loaded(const struct firmware *fw, void *context) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "MBC", fw, true) == 0)) { +- mutex_lock(&wm8994->fw_lock); ++ guard(mutex)(&wm8994->fw_lock); + wm8994->mbc = fw; +- mutex_unlock(&wm8994->fw_lock); + } + } + +diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c +--- a/sound/soc/codecs/wm8962.c ++++ b/sound/soc/codecs/wm8962.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1564,11 +1565,10 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + int old = wm8962->dsp2_ena; +- int ret = 0; + int dsp2_running = snd_soc_component_read(component, WM8962_DSP2_POWER_MANAGEMENT) & + WM8962_DSP2_ENA; + +- mutex_lock(&wm8962->dsp2_ena_lock); ++ guard(mutex)(&wm8962->dsp2_ena_lock); + + if (ucontrol->value.integer.value[0]) + wm8962->dsp2_ena |= 1 << shift; +@@ -1576,9 +1576,7 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + wm8962->dsp2_ena &= ~(1 << shift); + + if (wm8962->dsp2_ena == old) +- goto out; +- +- ret = 1; ++ return 0; + + if (dsp2_running) { + if (wm8962->dsp2_ena) +@@ -1587,10 +1585,7 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + wm8962_dsp2_stop(component); + } + +-out: +- mutex_unlock(&wm8962->dsp2_ena_lock); +- +- return ret; ++ return 1; + } + + /* The VU bits for the headphones are in a different register to the mute +@@ -3937,7 +3932,9 @@ static int wm8962_runtime_resume(struct device *dev) + WM8962_OSC_ENA | WM8962_PLL2_ENA | WM8962_PLL3_ENA, + 0); + +- regcache_sync(wm8962->regmap); ++ ret = regcache_sync(wm8962->regmap); ++ if (ret) ++ goto cache_sync_err; + + regmap_update_bits(wm8962->regmap, WM8962_ANTI_POP, + WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA, +@@ -3955,6 +3952,11 @@ static int wm8962_runtime_resume(struct device *dev) + + return 0; + ++cache_sync_err: ++ regcache_cache_only(wm8962->regmap, true); ++ regcache_mark_dirty(wm8962->regmap); ++ regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies), ++ wm8962->supplies); + disable_clock: + clk_disable_unprepare(wm8962->pdata.mclk); + return ret; +diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c +--- a/sound/soc/codecs/wm8994.c ++++ b/sound/soc/codecs/wm8994.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -766,7 +767,7 @@ static void active_reference(struct snd_soc_component *component) + { + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + wm8994->active_refcount++; + +@@ -775,8 +776,6 @@ static void active_reference(struct snd_soc_component *component) + + /* If we're using jack detection go into audio mode */ + wm1811_jackdet_set_mode(component, WM1811_JACKDET_MODE_AUDIO); +- +- mutex_unlock(&wm8994->accdet_lock); + } + + static void active_dereference(struct snd_soc_component *component) +@@ -784,7 +783,7 @@ static void active_dereference(struct snd_soc_component *component) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + u16 mode; + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + wm8994->active_refcount--; + +@@ -800,8 +799,6 @@ static void active_dereference(struct snd_soc_component *component) + + wm1811_jackdet_set_mode(component, mode); + } +- +- mutex_unlock(&wm8994->accdet_lock); + } + + static int clk_sys_event(struct snd_soc_dapm_widget *w, +@@ -3704,7 +3701,7 @@ static void wm8958_open_circuit_work(struct work_struct *work) + open_circuit_work.work); + struct device *dev = wm8994->wm8994->dev; + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + wm1811_micd_stop(wm8994->hubs.component); + +@@ -3718,8 +3715,6 @@ static void wm8958_open_circuit_work(struct work_struct *work) + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + wm8994->btn_mask | + SND_JACK_HEADSET); +- +- mutex_unlock(&wm8994->accdet_lock); + } + + static void wm8958_mic_id(void *data, u16 status) +@@ -3777,7 +3772,7 @@ static void wm1811_mic_work(struct work_struct *work) + struct snd_soc_component *component = wm8994->hubs.component; + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + +- pm_runtime_get_sync(component->dev); ++ guard(pm_runtime_active)(component->dev); + + /* If required for an external cap force MICBIAS on */ + if (control->pdata.jd_ext_cap) { +@@ -3785,7 +3780,7 @@ static void wm1811_mic_work(struct work_struct *work) + snd_soc_dapm_sync(dapm); + } + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + dev_dbg(component->dev, "Starting mic detection\n"); + +@@ -3803,10 +3798,6 @@ static void wm1811_mic_work(struct work_struct *work) + snd_soc_component_update_bits(component, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, WM8958_MICD_ENA); + } +- +- mutex_unlock(&wm8994->accdet_lock); +- +- pm_runtime_put(component->dev); + } + + static irqreturn_t wm1811_jackdet_irq(int irq, void *data) +@@ -4026,15 +4017,10 @@ static void wm8958_mic_work(struct work_struct *work) + mic_complete_work.work); + struct snd_soc_component *component = wm8994->hubs.component; + +- pm_runtime_get_sync(component->dev); +- +- mutex_lock(&wm8994->accdet_lock); ++ guard(pm_runtime_active)(component->dev); ++ guard(mutex)(&wm8994->accdet_lock); + + wm8994->mic_id_cb(wm8994->mic_id_cb_data, wm8994->mic_status); +- +- mutex_unlock(&wm8994->accdet_lock); +- +- pm_runtime_put(component->dev); + } + + static irqreturn_t wm8958_mic_irq(int irq, void *data) +diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c +--- a/sound/soc/codecs/wm9712.c ++++ b/sound/soc/codecs/wm9712.c +@@ -6,6 +6,7 @@ + * Author: Liam Girdwood + */ + ++#include + #include + #include + #include +@@ -229,7 +230,7 @@ static int wm9712_hp_mixer_put(struct snd_kcontrol *kcontrol, + shift = mc->shift & 0xff; + mask = 1 << shift; + +- mutex_lock(&wm9712->lock); ++ guard(mutex)(&wm9712->lock); + old = wm9712->hp_mixer[mixer]; + if (ucontrol->value.integer.value[0]) + wm9712->hp_mixer[mixer] |= mask; +@@ -251,8 +252,6 @@ static int wm9712_hp_mixer_put(struct snd_kcontrol *kcontrol, + &update); + } + +- mutex_unlock(&wm9712->lock); +- + return change; + } + +diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c +--- a/sound/soc/codecs/wm9713.c ++++ b/sound/soc/codecs/wm9713.c +@@ -11,6 +11,7 @@ + * o Support for DAPM + */ + ++#include + #include + #include + #include +@@ -238,7 +239,7 @@ static int wm9713_hp_mixer_put(struct snd_kcontrol *kcontrol, + shift = mc->shift & 0xff; + mask = (1 << shift); + +- mutex_lock(&wm9713->lock); ++ guard(mutex)(&wm9713->lock); + old = wm9713->hp_mixer[mixer]; + if (ucontrol->value.integer.value[0]) + wm9713->hp_mixer[mixer] |= mask; +@@ -260,8 +261,6 @@ static int wm9713_hp_mixer_put(struct snd_kcontrol *kcontrol, + &update); + } + +- mutex_unlock(&wm9713->lock); +- + return change; + } + +diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c +--- a/sound/soc/codecs/wm_adsp.c ++++ b/sound/soc/codecs/wm_adsp.c +@@ -348,7 +348,6 @@ int wm_adsp_fw_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct wm_adsp *dsp = snd_soc_component_get_drvdata(component); +- int ret = 1; + + if (ucontrol->value.enumerated.item[0] == dsp[e->shift_l].fw) + return 0; +@@ -356,16 +355,14 @@ int wm_adsp_fw_put(struct snd_kcontrol *kcontrol, + if (ucontrol->value.enumerated.item[0] >= WM_ADSP_NUM_FW) + return -EINVAL; + +- mutex_lock(&dsp[e->shift_l].cs_dsp.pwr_lock); ++ guard(mutex)(&dsp[e->shift_l].cs_dsp.pwr_lock); + + if (dsp[e->shift_l].cs_dsp.booted || !list_empty(&dsp[e->shift_l].compr_list)) +- ret = -EBUSY; ++ return -EBUSY; + else + dsp[e->shift_l].fw = ucontrol->value.enumerated.item[0]; + +- mutex_unlock(&dsp[e->shift_l].cs_dsp.pwr_lock); +- +- return ret; ++ return 1; + } + EXPORT_SYMBOL_GPL(wm_adsp_fw_put); + +@@ -450,15 +447,13 @@ static int wm_coeff_put_acked(struct snd_kcontrol *kctl, + if (val == 0) + return 0; /* 0 means no event */ + +- mutex_lock(&cs_ctl->dsp->pwr_lock); ++ guard(mutex)(&cs_ctl->dsp->pwr_lock); + + if (cs_ctl->enabled) + ret = cs_dsp_coeff_write_acked_control(cs_ctl, val); + else + ret = -EPERM; + +- mutex_unlock(&cs_ctl->dsp->pwr_lock); +- + if (ret < 0) + return ret; + +@@ -486,15 +481,13 @@ static int wm_coeff_tlv_get(struct snd_kcontrol *kctl, + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + int ret = 0; + +- mutex_lock(&cs_ctl->dsp->pwr_lock); ++ guard(mutex)(&cs_ctl->dsp->pwr_lock); + + ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, cs_ctl->cache, size); + + if (!ret && copy_to_user(bytes, cs_ctl->cache, size)) + ret = -EFAULT; + +- mutex_unlock(&cs_ctl->dsp->pwr_lock); +- + return ret; + } + +@@ -694,10 +687,9 @@ int wm_adsp_write_ctl(struct wm_adsp *dsp, const char *name, int type, + struct cs_dsp_coeff_ctl *cs_ctl; + int ret; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + cs_ctl = cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg); + ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, buf, len); +- mutex_unlock(&dsp->cs_dsp.pwr_lock); + + if (ret < 0) + return ret; +@@ -709,14 +701,10 @@ EXPORT_SYMBOL_GPL(wm_adsp_write_ctl); + int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type, + unsigned int alg, void *buf, size_t len) + { +- int ret; ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + +- mutex_lock(&dsp->cs_dsp.pwr_lock); +- ret = cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg), ++ return cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg), + 0, buf, len); +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- +- return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_read_ctl); + +@@ -1270,38 +1258,32 @@ int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream) + { + struct wm_adsp_compr *compr, *tmp; + struct snd_soc_pcm_runtime *rtd = stream->private_data; +- int ret = 0; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + if (wm_adsp_fw[dsp->fw].num_caps == 0) { + adsp_err(dsp, "%s: Firmware does not support compressed API\n", + snd_soc_rtd_to_codec(rtd, 0)->name); +- ret = -ENXIO; +- goto out; ++ return -ENXIO; + } + + if (wm_adsp_fw[dsp->fw].compr_direction != stream->direction) { + adsp_err(dsp, "%s: Firmware does not support stream direction\n", + snd_soc_rtd_to_codec(rtd, 0)->name); +- ret = -EINVAL; +- goto out; ++ return -EINVAL; + } + + list_for_each_entry(tmp, &dsp->compr_list, list) { + if (!strcmp(tmp->name, snd_soc_rtd_to_codec(rtd, 0)->name)) { + adsp_err(dsp, "%s: Only a single stream supported per dai\n", + snd_soc_rtd_to_codec(rtd, 0)->name); +- ret = -EBUSY; +- goto out; ++ return -EBUSY; + } + } + + compr = kzalloc_obj(*compr); +- if (!compr) { +- ret = -ENOMEM; +- goto out; +- } ++ if (!compr) ++ return -ENOMEM; + + compr->dsp = dsp; + compr->stream = stream; +@@ -1311,10 +1293,7 @@ int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream) + + stream->runtime->private_data = compr; + +-out: +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- +- return ret; ++ return 0; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_open); + +@@ -1324,7 +1303,7 @@ int wm_adsp_compr_free(struct snd_soc_component *component, + struct wm_adsp_compr *compr = stream->runtime->private_data; + struct wm_adsp *dsp = compr->dsp; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + wm_adsp_compr_detach(compr); + list_del(&compr->list); +@@ -1332,8 +1311,6 @@ int wm_adsp_compr_free(struct snd_soc_component *component, + kfree(compr->raw_buf); + kfree(compr); + +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return 0; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_free); +@@ -1741,7 +1718,7 @@ int wm_adsp_compr_trigger(struct snd_soc_component *component, + + compr_dbg(compr, "Trigger: %d\n", cmd); + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: +@@ -1777,8 +1754,6 @@ int wm_adsp_compr_trigger(struct snd_soc_component *component, + break; + } + +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_trigger); +@@ -1907,21 +1882,20 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, + + compr_dbg(compr, "Pointer request\n"); + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + buf = compr->buf; + + if (dsp->fatal_error || !buf || buf->error) { + snd_compr_stop_error(stream, SNDRV_PCM_STATE_XRUN); +- ret = -EIO; +- goto out; ++ return -EIO; + } + + if (buf->avail < wm_adsp_compr_frag_words(compr)) { + ret = wm_adsp_buffer_update_avail(buf); + if (ret < 0) { + compr_err(compr, "Error reading avail: %d\n", ret); +- goto out; ++ return ret; + } + + /* +@@ -1934,14 +1908,14 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, + if (buf->error) + snd_compr_stop_error(stream, + SNDRV_PCM_STATE_XRUN); +- goto out; ++ return ret; + } + + ret = wm_adsp_buffer_reenable_irq(buf); + if (ret < 0) { + compr_err(compr, "Failed to re-enable buffer IRQ: %d\n", + ret); +- goto out; ++ return ret; + } + } + } +@@ -1950,9 +1924,6 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, + tstamp->copied_total += buf->avail * CS_DSP_DATA_WORD_SIZE; + tstamp->sampling_rate = compr->sample_rate; + +-out: +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_pointer); +@@ -2063,15 +2034,13 @@ int wm_adsp_compr_copy(struct snd_soc_component *component, + struct wm_adsp *dsp = compr->dsp; + int ret; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + if (stream->direction == SND_COMPRESS_CAPTURE) + ret = wm_adsp_compr_read(compr, buf, count); + else + ret = -ENOTSUPP; + +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_copy); +diff --git a/sound/soc/codecs/wsa881x.c b/sound/soc/codecs/wsa881x.c +--- a/sound/soc/codecs/wsa881x.c ++++ b/sound/soc/codecs/wsa881x.c +@@ -1178,7 +1178,13 @@ static int wsa881x_runtime_resume(struct device *dev) + } + + regcache_cache_only(regmap, false); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) { ++ regcache_cache_only(regmap, true); ++ regcache_mark_dirty(regmap); ++ gpiod_direction_output(wsa881x->sd_n, 1); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wsa883x.c b/sound/soc/codecs/wsa883x.c +--- a/sound/soc/codecs/wsa883x.c ++++ b/sound/soc/codecs/wsa883x.c +@@ -4,6 +4,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -1237,9 +1238,8 @@ static int wsa883x_spkr_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +- mutex_lock(&wsa883x->sp_lock); +- wsa883x->pa_on = true; +- mutex_unlock(&wsa883x->sp_lock); ++ scoped_guard(mutex, &wsa883x->sp_lock) ++ wsa883x->pa_on = true; + + switch (wsa883x->dev_mode) { + case RECEIVER: +@@ -1290,9 +1290,8 @@ static int wsa883x_spkr_event(struct snd_soc_dapm_widget *w, + WSA883X_GLOBAL_PA_EN_MASK, 0); + snd_soc_component_write_field(component, WSA883X_PDM_WD_CTL, + WSA883X_PDM_EN_MASK, 0); +- mutex_lock(&wsa883x->sp_lock); +- wsa883x->pa_on = false; +- mutex_unlock(&wsa883x->sp_lock); ++ scoped_guard(mutex, &wsa883x->sp_lock) ++ wsa883x->pa_on = false; + break; + } + return 0; +@@ -1696,9 +1695,15 @@ static int wsa883x_runtime_suspend(struct device *dev) + static int wsa883x_runtime_resume(struct device *dev) + { + struct regmap *regmap = dev_get_regmap(dev, NULL); ++ int ret; + + regcache_cache_only(regmap, false); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) { ++ regcache_cache_only(regmap, true); ++ regcache_mark_dirty(regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wsa884x.c b/sound/soc/codecs/wsa884x.c +--- a/sound/soc/codecs/wsa884x.c ++++ b/sound/soc/codecs/wsa884x.c +@@ -1701,9 +1701,8 @@ static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +- mutex_lock(&wsa884x->sp_lock); +- wsa884x->pa_on = true; +- mutex_unlock(&wsa884x->sp_lock); ++ scoped_guard(mutex, &wsa884x->sp_lock) ++ wsa884x->pa_on = true; + + wsa884x_spkr_post_pmu(component, wsa884x); + +@@ -1717,9 +1716,8 @@ static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w, + WSA884X_PDM_WD_CTL_PDM_WD_EN_MASK, + 0x0); + +- mutex_lock(&wsa884x->sp_lock); +- wsa884x->pa_on = false; +- mutex_unlock(&wsa884x->sp_lock); ++ scoped_guard(mutex, &wsa884x->sp_lock) ++ wsa884x->pa_on = false; + break; + } + +@@ -2147,9 +2145,15 @@ static int wsa884x_runtime_suspend(struct device *dev) + static int wsa884x_runtime_resume(struct device *dev) + { + struct regmap *regmap = dev_get_regmap(dev, NULL); ++ int ret; + + regcache_cache_only(regmap, false); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) { ++ regcache_cache_only(regmap, true); ++ regcache_mark_dirty(regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wsa885x.c b/sound/soc/codecs/wsa885x.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/wsa885x.c +@@ -0,0 +1,1533 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. ++ */ ++ ++/* WSA885X codec driver */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Control Registers - Audio Processing */ ++#define WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S 0x0000 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK 0x0004 ++#define WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX 0x0140 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID 0x0208 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX 0x0240 ++#define WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER 0x0340 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0 0x4405 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1 0x4406 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB 0x4409 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB 0x6409 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB 0x440a ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB 0x640a ++#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS 0x0a04 ++#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS 0x0a40 ++#define WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE 0x0b10 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX 0x0e40 ++ ++/* Analog Top Registers - Power and Clock Control */ ++#define WSA885X_ANA_TOP_PON_CKSK_CTL_0 0x800d ++#define WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG 0x8024 ++#define WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG 0x8025 ++#define WSA885X_ANA_TOP_BG_TVP_OVRD_CTL 0x8034 ++ ++/* Analog PLL Registers */ ++#define WSA885X_ANA_PLL_DIV_CTL_0 0x8090 ++#define WSA885X_ANA_PLL_DIV_CTL_1 0x8091 ++#define WSA885X_ANA_TOP_PLL_VCO_CTL 0x8092 ++#define WSA885X_ANA_TOP_PLL_LOOPFILT_0 0x8093 ++#define WSA885X_ANA_TOP_PLL_OVRD_CTL 0x8098 ++#define WSA885X_ANA_TOP_PLL_STATUS_0 0x809a ++#define WSA885X_ANA_TOP_PLL_STATUS_1 0x809b ++ ++/* Analog Boost Control Registers */ ++#define WSA885X_ANA_TOP_BOOST_STB_CTRL2 0x805b ++#define WSA885X_ANA_TOP_BOOST_STB_CTRL3 0x805c ++#define WSA885X_ANA_TOP_BOOST_BYP_CTRL2 0x805e ++#define WSA885X_ANA_TOP_BOOST_BYP_CTRL3 0x805f ++#define WSA885X_ANA_TOP_BOOST_MISC 0x8063 ++#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2 0x8065 ++#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4 0x8067 ++ ++/* Analog IV Sense ADC Registers */ ++#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2 0x80ca ++#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3 0x80cb ++#define WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL 0x80cc ++#define WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2 0x80d0 ++ ++/* Analog Speaker Power Stage Registers */ ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3 0x8108 ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3 0x810b ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3 0x810e ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3 0x8111 ++#define WSA885X_ANA_TOP_SPK_TOP_SPARE3 0x813c ++#define WSA885X_SPK_TOP_LF_CH1_CTRL11 0x811c ++#define WSA885X_SPK_TOP_LF_CH1_TUNE1 0x811d ++#define WSA885X_SPK_TOP_LF_CH2_TUNE1 0x8129 ++#define WSA885X_SPK_TOP_LF_CH1_CTRL9 0x811a ++#define WSA885X_SPK_TOP_LF_CH2_CTRL9 0x8126 ++#define WSA885X_SPK_TOP_LF_CH2_CTRL11 0x8128 ++#define WSA885X_SPK_TOP_COMMON_CTRL2 0x8102 ++#define WSA885X_SPK_TOP_COMMON_TUNE1 0x8103 ++#define WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1 0x80ba ++#define WSA885X_DIG_CTRL0_TOP_CLK_CFG 0x8418 ++#define WSA885X_DIG_CTRL0_SDCA_COMMIT 0x8419 ++#define WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE 0x841a ++#define WSA885X_DIG_CTRL0_SYS_CLK_SEL 0x841b ++#define WSA885X_DIG_CTRL0_CDC_CLK_CTL 0x841c ++#define WSA885X_DIG_CTRL0_PA_FSM_CTL 0x8420 ++#define WSA885X_DIG_CTRL0_POWER_FSM_CTL0 0x8423 ++#define WSA885X_DIG_CTRL0_POWER_FSM_CTL1 0x8424 ++#define WSA885X_DIG_CTRL0_PA0_FSM_CTL1 0x842b ++#define WSA885X_DIG_CTRL0_PA1_FSM_CTL1 0x8435 ++#define WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL 0x8458 ++#define WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL 0x8470 ++#define WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1 0x84b4 ++#define WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1 0x84b7 ++#define WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1 0x84A0 ++#define WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1 0x84A4 ++ ++/* Digital Control 1 Registers - I2S/TDM Interface */ ++#define WSA885X_DIG_CTRL1_I2S_CTL0 0x85A0 ++#define WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX 0x85A2 ++#define WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX 0x85A3 ++#define WSA885X_DIG_CTRL1_I2S_TDM_CTL0 0x85A7 ++#define WSA885X_DIG_CTRL1_I2S_TDM_CTL1 0x85A9 ++#define WSA885X_DIG_CTRL1_I2S_TDM_CH_RX 0x85AA ++#define WSA885X_DIG_CTRL1_I2S_TDM_CH_TX 0x85AB ++#define WSA885X_DIG_CTRL1_I2S_RESET_CTL 0x85AE ++ ++/* CDC RX Path Registers - Audio Data Path */ ++#define WSA885X_CDC_RX0_RX_PATH_CFG0 0x8601 ++#define WSA885X_CDC_RX0_RX_PATH_CFG1 0x8602 ++#define WSA885X_CDC_RX0_RX_PATH_CTL 0x8606 ++#define WSA885X_RX0_RX_PATH_DSMDEM_CTL 0x8613 ++#define WSA885X_CDC_RX1_RX_PATH_CFG0 0x8621 ++#define WSA885X_CDC_RX1_RX_PATH_CFG1 0x8622 ++#define WSA885X_CDC_RX1_RX_PATH_CTL 0x8626 ++#define WSA885X_RX1_RX_PATH_DSMDEM_CTL 0x8633 ++ ++/* CDC Compander Registers - Dynamic Range Control */ ++#define WSA885X_CDC_COMPANDER0_CTL0 0x8640 ++#define WSA885X_CDC_COMPANDER0_CTL7 0x8647 ++#define WSA885X_CDC_COMPANDER1_CTL0 0x8660 ++#define WSA885X_CDC_COMPANDER1_CTL7 0x8667 ++ ++/* CDC Speaker Protection Registers - IV Sense */ ++#define WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL 0x86A1 ++#define WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL 0x86B1 ++#define WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL 0x86A9 ++#define WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL 0x86B9 ++ ++/* CDC Class-H Registers - Headroom Control */ ++#define WSA885X_CDC_CLSH_V1P8_BP_CTL1 0x86CD ++#define WSA885X_CDC_CLSH_V1P8_BP_CTL0 0x86CC ++#define WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0 0x86C7 ++#define WSA885X_CDC_CLSH_CLSH_V_HD_PA 0x86C3 ++#define WSA885X_CDC_CLSH_V1P8_BP_CTL2 0x86CE ++ ++/* Driver Constants */ ++#define WSA885X_CLK_RATE_FIXED 73728000 ++#define WSA885X_NUM_REGS 0x03 ++ ++/* Interrupt Registers */ ++#define WSA885X_INTR_STATUS0 0x8584 ++#define WSA885X_INTR_MASK0 0x8581 ++#define WSA885X_INTR_CLEAR0 0x8587 ++ ++/* Power and PA FSM Control Registers */ ++#define WSA885X_PA0_FSM_CTL0 0x842A ++#define WSA885X_PA1_FSM_CTL0 0x8434 ++ ++/* Digital Control GPIO and Interrupt Registers */ ++#define WSA885X_DIG_CTRL1_PIN_CT 0x8510 ++#define WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL 0x8518 ++#define WSA885X_DIG_CTRL1_INTR_MODE 0x8580 ++ ++#define WSA885X_I2S_CTL0_PCM_RATE_MASK GENMASK(4, 1) ++#define WSA885X_I2S_CTL0_ENABLE_MASK BIT(0) ++#define WSA885X_I2S_CTL0_PCM_RATE(v) \ ++ FIELD_PREP(WSA885X_I2S_CTL0_PCM_RATE_MASK, (v)) ++#define WSA885X_I2S_CTL0_PCM_RATE_8KHZ 0x0 ++#define WSA885X_I2S_CTL0_PCM_RATE_16KHZ 0x1 ++#define WSA885X_I2S_CTL0_PCM_RATE_32KHZ 0x2 ++#define WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ 0x3 ++#define WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ 0x4 ++#define WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ 0x5 ++#define WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ 0x6 ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK GENMASK(2, 0) ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK GENMASK(6, 4) ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT0(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, (v)) ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT1(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, (v)) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK GENMASK(2, 0) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK GENMASK(6, 4) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT2(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK, (v)) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT3(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK, (v)) ++#define WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK BIT(0) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK GENMASK(3, 2) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2 \ ++ FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 0) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4 \ ++ FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 1) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8 \ ++ FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 3) ++#define WSA885X_I2S_TDM_CH_TX_CH0_EN BIT(0) ++#define WSA885X_I2S_TDM_CH_TX_CH1_EN BIT(1) ++#define WSA885X_I2S_TDM_CH_TX_CH2_EN BIT(2) ++#define WSA885X_I2S_TDM_CH_TX_CH3_EN BIT(3) ++#define WSA885X_I2S_TDM_CH_RX_CH0_EN BIT(0) ++#define WSA885X_I2S_TDM_CH_RX_CH3_EN BIT(3) ++#define WSA885X_I2S_RESET_CTL_RESET_MASK BIT(0) ++#define WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK BIT(2) ++#define WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK BIT(3) ++#define WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK BIT(2) ++ ++#define WSA885X_I2S_TX_SLOT_ISENSE0 0x1 ++#define WSA885X_I2S_TX_SLOT_ISENSE1 0x2 ++#define WSA885X_I2S_TX_SLOT_CUR_SENSE0 0x5 ++#define WSA885X_I2S_TX_SLOT_CUR_SENSE1 0x6 ++ ++/* RX Sample Rate Index Values - Audio Playback Path */ ++#define WSA885X_RX_RATE_8000HZ 0x00 ++#define WSA885X_RX_RATE_16000HZ 0x01 ++#define WSA885X_RX_RATE_32000HZ 0x02 ++#define WSA885X_RX_RATE_44100HZ 0x03 ++#define WSA885X_RX_RATE_48000HZ 0x04 ++#define WSA885X_RX_RATE_96000HZ 0x05 ++#define WSA885X_RX_RATE_192000HZ 0x06 ++#define WSA885X_RX_RATE_384000HZ 0x07 ++ ++/* VI Sample Rate Index Values - Voltage/Current Sensing Path */ ++#define WSA885X_VI_RATE_8000HZ 0x00 ++#define WSA885X_VI_RATE_16000HZ 0x01 ++#define WSA885X_VI_RATE_44100HZ 0x02 ++#define WSA885X_VI_RATE_48000HZ 0x03 ++#define WSA885X_VI_RATE_96000HZ 0x04 ++#define WSA885X_VI_RATE_22050HZ 0x05 ++#define WSA885X_VI_RATE_24000HZ 0x06 ++#define WSA885X_VI_RATE_192000HZ 0x07 ++#define WSA885X_VI_RATE_384000HZ 0x08 ++ ++/* Channel Configuration Masks */ ++#define WSA885X_CHANNEL_STEREO 0x03 ++#define WSA885X_CHANNEL_MONO_LEFT 0x01 ++#define WSA885X_CHANNEL_MONO_RIGHT 0x02 ++ ++#define WSA885X_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ ++ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 | \ ++ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) ++ ++#define WSA885X_PLL_LOCK_BIT BIT(0) ++ ++#define WSA885X_FU21_VOL_STEPS 124 ++#define WSA885X_USAGE_MODE_MAX 8 ++static const DECLARE_TLV_DB_SCALE(wsa885x_fu21_digital_gain, -8400, 100, 0); ++ ++static bool wsa885x_is_valid_rx_slot_mask(u32 mask) ++{ ++ return mask == WSA885X_CHANNEL_MONO_LEFT || ++ mask == WSA885X_CHANNEL_MONO_RIGHT || ++ mask == WSA885X_CHANNEL_STEREO; ++} ++ ++static const char *const wsa885x_supply_name[] = { ++ "vdd-1p8", ++ "vdd-io", ++}; ++ ++enum { ++ WSA885X_BATT_1S = 1, ++ WSA885X_BATT_2S, ++}; ++ ++enum { ++ WSA885X_IRQ_INT_SAF2WAR = 0, ++ WSA885X_IRQ_INT_WAR2SAF, ++ WSA885X_IRQ_INT_DISABLE, ++ WSA885X_IRQ_INT_PA0_OCP, ++ WSA885X_IRQ_INT_PA1_OCP, ++ WSA885X_IRQ_INT_CLIP0, ++ WSA885X_IRQ_INT_CLIP1, ++ WSA885X_IRQ_INT_CLK_WD, ++ WSA885X_IRQ_INT_INTR_GPIO1_PIN, ++ WSA885X_IRQ_INT_INTR_GPIO2_PIN, ++ WSA885X_IRQ_INT_UVLO, ++ WSA885X_IRQ_INT_BOP, ++ WSA885X_IRQ_INT_PA0_FSM_ERR, ++ WSA885X_IRQ_INT_PA1_FSM_ERR, ++ WSA885X_IRQ_INT_MAIN_FSM_ERR, ++ WSA885X_IRQ_INT_PCM_DATA0_WD, ++ WSA885X_IRQ_INT_PCM_DATA1_WD, ++ WSA885X_IRQ_INT_PCM_DATA0_DC, ++ WSA885X_IRQ_INT_PCM_DATA1_DC, ++ WSA885X_IRQ_INT_PLL_UNLOCKED, ++ WSA885X_IRQ_INT_PROT_MODE_CHANGE, ++ WSA885X_IRQ_INT_PB_CLOCK_VALID, ++ WSA885X_IRQ_INT_SENSE_CLOCK_VALID, ++ WSA885X_IRQ_MAX, ++}; ++ ++struct wsa885x_priv { ++ struct i2c_client *client; ++ struct regmap *regmap; ++ struct device *dev; ++ struct snd_soc_component *component; ++ struct gpio_desc *sd_n; ++ struct reset_control *sd_reset; ++ u32 usage_mode; ++ u32 rx_slot_mask; ++ u32 batt_conf; ++ int stereo_vol_db; ++ struct mutex state_lock; /* protects mutable control state */ ++}; ++ ++struct wsa885x_reg_update { ++ unsigned int reg; ++ unsigned int mask; ++ unsigned int val; ++}; ++ ++static const struct regmap_range_cfg wsa885x_regmap_ranges[] = { ++ { ++ .range_min = 0, ++ .range_max = 0x88ff, ++ .selector_reg = 0x0, ++ .selector_mask = 0xFF, ++ .selector_shift = 0, ++ .window_start = 0, ++ .window_len = 0x100, ++ }, ++}; ++ ++static const struct reg_default wsa885x_codec_reg_defaults[] = { ++ {WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX, 0x04}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB, 0xac}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB, 0xac}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS, 0x03}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX, 0x03}, ++ {WSA885X_ANA_TOP_PON_CKSK_CTL_0, 0x00}, ++ {WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG, 0x19}, ++ {WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG, 0x22}, ++ {WSA885X_ANA_PLL_DIV_CTL_0, 0x0c}, ++ {WSA885X_ANA_PLL_DIV_CTL_1, 0x50}, ++ {WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00}, ++ {WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xb4}, ++ {WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00}, ++ {WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00}, ++ {WSA885X_ANA_TOP_BOOST_STB_CTRL2, 0x03}, ++ {WSA885X_ANA_TOP_BOOST_STB_CTRL3, 0x3c}, ++ {WSA885X_ANA_TOP_BOOST_BYP_CTRL2, 0xc5}, ++ {WSA885X_ANA_TOP_BOOST_BYP_CTRL3, 0x13}, ++ {WSA885X_ANA_TOP_BOOST_MISC, 0x79}, ++ {WSA885X_ANA_TOP_SPK_TOP_SPARE3, 0x00}, ++ {WSA885X_SPK_TOP_COMMON_CTRL2, 0x08}, ++ {WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x09}, ++ {WSA885X_SPK_TOP_LF_CH1_TUNE1, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH2_TUNE1, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH1_CTRL9, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH2_CTRL9, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x09}, ++ {WSA885X_SPK_TOP_COMMON_TUNE1, 0x03}, ++ {WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1, 0x00}, ++ {WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x0e}, ++ {WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2, 0x40}, ++ {WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4, 0xff}, ++ {WSA885X_ANA_TOP_PLL_STATUS_0, 0x00}, ++ {WSA885X_ANA_TOP_PLL_STATUS_1, 0x00}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2, 0x84}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x02}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL, 0x00}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2, 0xe0}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3, 0xa4}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3, 0xc9}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3, 0xa4}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3, 0xc9}, ++ {WSA885X_DIG_CTRL0_TOP_CLK_CFG, 0x00}, ++ {WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x00}, ++ {WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00}, ++ {WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00}, ++ {WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00}, ++ {WSA885X_DIG_CTRL0_POWER_FSM_CTL0, 0x05}, ++ {WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x00}, ++ {WSA885X_DIG_CTRL0_PA0_FSM_CTL1, 0x45}, ++ {WSA885X_DIG_CTRL0_PA1_FSM_CTL1, 0x45}, ++ {WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL, 0x7f}, ++ {WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x00}, ++ {WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1, 0x01}, ++ {WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1, 0x01}, ++ {WSA885X_DIG_CTRL1_I2S_CTL0, 0x06}, ++ {WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CTL0, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x05}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, WSA885X_I2S_TDM_CH_RX_CH3_EN}, ++ {WSA885X_CDC_RX0_RX_PATH_CFG0, 0x89}, ++ {WSA885X_CDC_RX0_RX_PATH_CFG1, 0x64}, ++ {WSA885X_CDC_RX0_RX_PATH_CTL, 0x24}, ++ {WSA885X_RX0_RX_PATH_DSMDEM_CTL, 0x01}, ++ {WSA885X_CDC_RX1_RX_PATH_CFG0, 0x89}, ++ {WSA885X_CDC_RX1_RX_PATH_CFG1, 0x64}, ++ {WSA885X_CDC_RX1_RX_PATH_CTL, 0x04}, ++ {WSA885X_RX1_RX_PATH_DSMDEM_CTL, 0x01}, ++ {WSA885X_CDC_COMPANDER0_CTL0, 0x01}, ++ {WSA885X_CDC_COMPANDER0_CTL7, 0x2a}, ++ {WSA885X_CDC_COMPANDER1_CTL0, 0x01}, ++ {WSA885X_CDC_COMPANDER1_CTL7, 0x2a}, ++ {WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x50}, ++ {WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0x6c}, ++ {WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0, 0x0d}, ++ {WSA885X_CDC_CLSH_CLSH_V_HD_PA, 0x03}, ++ {WSA885X_CDC_CLSH_V1P8_BP_CTL2, 0x05}, ++}; ++ ++static void wsa885x_multi_update_bits(struct regmap *regmap, ++ const struct wsa885x_reg_update *updates, ++ size_t num_updates) ++{ ++ size_t i; ++ ++ for (i = 0; i < num_updates; i++) ++ regmap_update_bits(regmap, updates[i].reg, ++ updates[i].mask, updates[i].val); ++} ++ ++static void wsa885x_toggle_irq_bit(struct wsa885x_priv *wsa885x, ++ unsigned int reg, unsigned int mask) ++{ ++ regmap_update_bits(wsa885x->regmap, reg, mask, 0); ++ regmap_update_bits(wsa885x->regmap, reg, mask, mask); ++} ++ ++static void wsa885x_pulse_irq_bit(struct wsa885x_priv *wsa885x, ++ unsigned int reg, unsigned int mask) ++{ ++ regmap_update_bits(wsa885x->regmap, reg, mask, 0); ++ regmap_update_bits(wsa885x->regmap, reg, mask, mask); ++ regmap_update_bits(wsa885x->regmap, reg, mask, 0); ++} ++ ++static int wsa885x_tdm_ctl0_slot_num_val(int slots, unsigned int *slot_num_val) ++{ ++ if (!slot_num_val) ++ return -EINVAL; ++ ++ switch (slots) { ++ case 2: ++ *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2; ++ return 0; ++ case 4: ++ *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4; ++ return 0; ++ case 8: ++ *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int wsa885x_reg_update_sequence(struct regmap *regmap, int slots) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x15 }, ++ { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x11 }, ++ }; ++ unsigned int slot_num_val; ++ int ret; ++ ++ if (!regmap) ++ return -EINVAL; ++ ++ ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val); ++ if (ret) ++ return ret; ++ ++ regmap_multi_reg_write(regmap, regs, ARRAY_SIZE(regs)); ++ ++ regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0, ++ WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, ++ slot_num_val); ++ regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0, ++ WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK, ++ WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK); ++ regmap_write(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH0_EN); ++ regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH1_EN, ++ WSA885X_I2S_TDM_CH_TX_CH1_EN); ++ ++ return 0; ++} ++ ++static int wsa885x_wait_for_pll_lock(struct wsa885x_priv *wsa885x) ++{ ++ unsigned int status = 0; ++ int cnt = 0, ret = 0; ++ ++ do { ++ usleep_range(1000, 1100); ++ ret = regmap_read(wsa885x->regmap, WSA885X_ANA_TOP_PLL_STATUS_0, &status); ++ if (ret) { ++ dev_err(wsa885x->dev, "PLL status read failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (status & WSA885X_PLL_LOCK_BIT) ++ return 0; ++ } while (++cnt < 20); ++ ++ dev_warn(wsa885x->dev, "PLL lock timeout after 20ms, status=0x%x\n", status); ++ return -ETIMEDOUT; ++} ++ ++static int wsa885x_2s_conf(struct wsa885x_priv *wsa885x) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_SPK_TOP_COMMON_TUNE1, 0x26 }, ++ { WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x0d }, ++ { WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x0d }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x71 }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0xAA }, ++ }; ++ ++ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++} ++ ++static const struct reg_sequence wsa885x_reg_init[] = { ++ { WSA885X_CDC_RX0_RX_PATH_CTL, 0x24 }, ++ { WSA885X_CDC_RX1_RX_PATH_CTL, 0x24 }, ++ { WSA885X_RX0_RX_PATH_DSMDEM_CTL, 0x01 }, ++ { WSA885X_RX1_RX_PATH_DSMDEM_CTL, 0x01 }, ++ { WSA885X_CDC_COMPANDER0_CTL0, 0x01 }, ++ { WSA885X_CDC_COMPANDER1_CTL0, 0x01 }, ++ { WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x0f }, ++ { WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x4f }, ++ { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x02 }, ++ { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x00 }, ++ { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK, 0x01 }, ++ { WSA885X_CDC_RX0_RX_PATH_CFG1, 0x60 }, ++ { WSA885X_CDC_RX1_RX_PATH_CFG1, 0x60 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3, 0xa5 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3, 0xa5 }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2, 0x85 }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x0c }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x0e }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL, 0x0c }, ++ { WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1, 0x01 }, ++ { WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1, 0x01 }, ++ { WSA885X_CDC_RX0_RX_PATH_CFG0, 0x88 }, ++ { WSA885X_CDC_RX0_RX_PATH_CFG0, 0x89 }, ++ { WSA885X_CDC_RX1_RX_PATH_CFG0, 0x88 }, ++ { WSA885X_CDC_RX1_RX_PATH_CFG0, 0x89 }, ++ { WSA885X_ANA_TOP_BOOST_STB_CTRL2, 0x82 }, ++ { WSA885X_ANA_TOP_BOOST_STB_CTRL3, 0x34 }, ++ { WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2, 0x41 }, ++ { WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4, 0x7f }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x50 }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0x6c }, ++ { WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0, 0x0d }, ++ { WSA885X_CDC_CLSH_CLSH_V_HD_PA, 0x03 }, ++ { WSA885X_DIG_CTRL0_POWER_FSM_CTL0, 0x05 }, ++ { WSA885X_ANA_TOP_PON_CKSK_CTL_0, 0x20 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3, 0x45 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3, 0x45 }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL2, 0x05 }, ++ { WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG, 0x35 }, ++ { WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG, 0x21 }, ++ { WSA885X_ANA_TOP_BOOST_BYP_CTRL2, 0xc7 }, ++ { WSA885X_ANA_TOP_BOOST_BYP_CTRL3, 0x11 }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2, 0x80 }, ++ { WSA885X_ANA_TOP_SPK_TOP_SPARE3, 0x08 }, ++ { WSA885X_DIG_CTRL0_PA0_FSM_CTL1, 0x47 }, ++ { WSA885X_DIG_CTRL0_PA1_FSM_CTL1, 0x47 }, ++ { WSA885X_CDC_COMPANDER0_CTL7, 0x34 }, ++ { WSA885X_CDC_COMPANDER1_CTL7, 0x34 }, ++ { WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL, 0x79 }, ++}; ++ ++static int wsa885x_hw_init(struct wsa885x_priv *wsa885x) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL, 0x2e }, ++ { WSA885X_DIG_CTRL1_INTR_MODE, 0x01 }, ++ { WSA885X_DIG_CTRL1_PIN_CT, 0x04 }, ++ }; ++ int ret; ++ ++ ret = regmap_multi_reg_write(wsa885x->regmap, wsa885x_reg_init, ++ ARRAY_SIZE(wsa885x_reg_init)); ++ if (ret) ++ return ret; ++ ++ if (wsa885x->batt_conf == WSA885X_BATT_2S) { ++ ret = wsa885x_2s_conf(wsa885x); ++ if (ret) ++ return ret; ++ } ++ ++ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++} ++ ++static int wsa885x_unmask_interrupts(struct wsa885x_priv *wsa885x) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_INTR_MASK0, 0x00 }, ++ { WSA885X_INTR_MASK0 + 1, 0x00 }, ++ { WSA885X_INTR_MASK0 + 2, 0xf8 }, ++ }; ++ ++ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++} ++ ++static int wsa885x_wait_for_pde_state(struct wsa885x_priv *wsa885x, int ps) ++{ ++ unsigned int act_ps = 0, clock_valid = 0; ++ int rc = 0, cnt = 0; ++ ++ if (ps < 0 || ps > 3) ++ return -EINVAL; ++ ++ do { ++ usleep_range(1000, 1500); ++ rc = regmap_read(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS, ++ &act_ps); ++ if (rc) { ++ dev_err(wsa885x->dev, "PDE state read failed: %d\n", rc); ++ return rc; ++ } ++ if (act_ps == ps) ++ return 0; ++ } while (++cnt < 5); ++ ++ if (regmap_read(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID, ++ &clock_valid)) ++ dev_err(wsa885x->dev, ++ "PDE power state %d request failed, actual_ps %d, clock_valid read failed\n", ++ ps, act_ps); ++ else ++ dev_err(wsa885x->dev, ++ "PDE power state %d request failed, actual_ps %d, clock_valid:%d\n", ++ ps, act_ps, clock_valid); ++ ++ return -ETIMEDOUT; ++} ++ ++static void wsa885x_program_stereo_volume(struct wsa885x_priv *wsa885x, ++ int stereo_vol_db, bool commit) ++{ ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB, ++ (u8)(s8)stereo_vol_db); ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00); ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB, ++ (u8)(s8)stereo_vol_db); ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00); ++ ++ if (commit) ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01); ++} ++ ++static int wsa885x_codec_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct wsa885x_priv *wsa885x; ++ u8 pcm_rate, cs21_sample_rate_idx, cs24_sample_rate_idx; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ switch (params_rate(params)) { ++ case 8000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_8KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_8000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_8000HZ; ++ break; ++ case 16000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_16KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_16000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_16000HZ; ++ break; ++ case 32000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_32KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_32000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ; ++ break; ++ case 44100: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_44100HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_44100HZ; ++ break; ++ case 48000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_48000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ; ++ break; ++ case 88200: ++ case 96000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_96000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_96000HZ; ++ break; ++ case 176400: ++ case 192000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_192000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_192000HZ; ++ break; ++ case 352800: ++ case 384000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_384000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_384000HZ; ++ break; ++ default: ++ dev_err(wsa885x->dev, "sampling rate %d is not supported\n", params_rate(params)); ++ return -EINVAL; ++ } ++ ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0, ++ WSA885X_I2S_CTL0_PCM_RATE_MASK | ++ WSA885X_I2S_CTL0_ENABLE_MASK, ++ WSA885X_I2S_CTL0_PCM_RATE(pcm_rate) | ++ WSA885X_I2S_CTL0_ENABLE_MASK); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00); ++ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX, ++ cs21_sample_rate_idx); ++ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX, ++ cs24_sample_rate_idx); ++ ++ mutex_lock(&wsa885x->state_lock); ++ wsa885x_program_stereo_volume(wsa885x, wsa885x->stereo_vol_db, false); ++ mutex_unlock(&wsa885x->state_lock); ++ ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01); ++ ++ return 0; ++} ++ ++static int wsa885x_codec_set_tdm_slot(struct snd_soc_dai *dai, ++ unsigned int tx_slot_mask, ++ unsigned int rx_slot_mask, int slots, ++ int slot_width) ++{ ++ static const struct wsa885x_reg_update stereo_updates[] = { ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_ISENSE1) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK, ++ WSA885X_I2S_CFG1_TDM_TX_SLOT2(WSA885X_I2S_TX_SLOT_CUR_SENSE0) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK, ++ WSA885X_I2S_CFG1_TDM_TX_SLOT3(WSA885X_I2S_TX_SLOT_CUR_SENSE1) }, ++ }; ++ static const struct wsa885x_reg_update mono_left_updates[] = { ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE0) }, ++ }; ++ static const struct wsa885x_reg_update mono_right_updates[] = { ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE1) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE1) }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ unsigned int slot_num_val; ++ u32 mask; ++ int ret; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val); ++ if (ret) { ++ dev_err(wsa885x->dev, "%s: unsupported slot count %d\n", ++ __func__, slots); ++ return ret; ++ } ++ ++ if (rx_slot_mask && !wsa885x_is_valid_rx_slot_mask(rx_slot_mask)) { ++ dev_err(wsa885x->dev, ++ "%s: unsupported rx_slot_mask 0x%x\n", ++ __func__, rx_slot_mask); ++ return -EINVAL; ++ } ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (rx_slot_mask) ++ wsa885x->rx_slot_mask = rx_slot_mask; ++ else if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask)) ++ wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; ++ mask = wsa885x->rx_slot_mask; ++ ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, ++ WSA885X_I2S_RESET_CTL_RESET_MASK, ++ WSA885X_I2S_RESET_CTL_RESET_MASK); ++ ++ if (mask == WSA885X_CHANNEL_STEREO) { ++ wsa885x_multi_update_bits(wsa885x->regmap, stereo_updates, ++ ARRAY_SIZE(stereo_updates)); ++ ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); ++ if (ret) ++ goto exit_unlock; ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH2_EN, ++ WSA885X_I2S_TDM_CH_TX_CH2_EN); ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH3_EN, ++ WSA885X_I2S_TDM_CH_TX_CH3_EN); ++ } else if (mask == WSA885X_CHANNEL_MONO_LEFT) { ++ wsa885x_multi_update_bits(wsa885x->regmap, mono_left_updates, ++ ARRAY_SIZE(mono_left_updates)); ++ ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); ++ if (ret) ++ goto exit_unlock; ++ } else if (mask == WSA885X_CHANNEL_MONO_RIGHT) { ++ wsa885x_multi_update_bits(wsa885x->regmap, mono_right_updates, ++ ARRAY_SIZE(mono_right_updates)); ++ ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); ++ if (ret) ++ goto exit_unlock; ++ } ++ ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0, ++ WSA885X_I2S_CTL0_ENABLE_MASK, ++ WSA885X_I2S_CTL0_ENABLE_MASK); ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, ++ WSA885X_I2S_RESET_CTL_RESET_MASK, 0); ++ ++ ret = 0; ++ ++exit_unlock: ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return ret; ++} ++ ++static int wsa885x_codec_set_sysclk(struct snd_soc_dai *dai, int clk_id, ++ unsigned int freq, int dir) ++{ ++ static const struct reg_sequence pll_prep[] = { ++ { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x03 }, ++ { WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x04 }, ++ { WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xB4 }, ++ { WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00 }, ++ { WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00 }, ++ }; ++ static const struct reg_sequence pll_cleanup[] = { ++ { WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00 }, ++ { WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00 }, ++ { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00 }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ u32 pll_div; ++ int ret = 0; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ if (!freq) ++ return -EINVAL; ++ if (WSA885X_CLK_RATE_FIXED % freq) ++ return -EINVAL; ++ pll_div = WSA885X_CLK_RATE_FIXED / freq; ++ if (pll_div > 0xff) ++ return -EINVAL; ++ ++ regmap_multi_reg_write(wsa885x->regmap, pll_prep, ARRAY_SIZE(pll_prep)); ++ regmap_write(wsa885x->regmap, WSA885X_ANA_PLL_DIV_CTL_0, pll_div); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x02); ++ ++ ret = wsa885x_wait_for_pll_lock(wsa885x); ++ if (ret) { ++ dev_err(wsa885x->dev, "PLL lock failed, aborting sysclk configuration\n"); ++ regmap_multi_reg_write(wsa885x->regmap, pll_cleanup, ++ ARRAY_SIZE(pll_cleanup)); ++ return ret; ++ } ++ ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x01); ++ regmap_write(wsa885x->regmap, WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00); ++ ++ return 0; ++} ++ ++static int wsa885x_codec_mute_stream(struct snd_soc_dai *dai, int mute, int stream) ++{ ++ static const struct reg_sequence mute_regs[] = { ++ { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03 }, ++ }; ++ static const struct reg_sequence mute_commit_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01 }, ++ { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, ++ }; ++ static const struct reg_sequence unmute_prep_head_regs[] = { ++ { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, ++ }; ++ static const struct reg_sequence unmute_prep_tail_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01 }, ++ }; ++ static const struct reg_sequence unmute_volume_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00 }, ++ }; ++ static const struct reg_sequence unmute_commit_regs[] = { ++ { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x00 }, ++ }; ++ static const struct reg_sequence unmute_finish_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x00 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x00 }, ++ { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ int ret = 0, ps0 = 0, ps3 = 3; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ if (stream != SNDRV_PCM_STREAM_PLAYBACK) ++ return 0; ++ ++ mutex_lock(&wsa885x->state_lock); ++ ++ if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX) { ++ ret = -EINVAL; ++ goto exit_unlock; ++ } ++ ++ if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask)) ++ wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; ++ ++ if (mute) { ++ regmap_multi_reg_write(wsa885x->regmap, mute_regs, ++ ARRAY_SIZE(mute_regs)); ++ ret = wsa885x_wait_for_pde_state(wsa885x, ps3); ++ if (ret) { ++ dev_err(wsa885x->dev, ++ "PS3 transition failed: %d\n", ret); ++ } else { ++ regmap_multi_reg_write(wsa885x->regmap, mute_commit_regs, ++ ARRAY_SIZE(mute_commit_regs)); ++ } ++ } else { ++ regmap_multi_reg_write(wsa885x->regmap, unmute_prep_head_regs, ++ ARRAY_SIZE(unmute_prep_head_regs)); ++ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE, ++ wsa885x->usage_mode); ++ regmap_multi_reg_write(wsa885x->regmap, unmute_prep_tail_regs, ++ ARRAY_SIZE(unmute_prep_tail_regs)); ++ wsa885x_program_stereo_volume(wsa885x, wsa885x->stereo_vol_db, false); ++ regmap_multi_reg_write(wsa885x->regmap, unmute_volume_regs, ++ ARRAY_SIZE(unmute_volume_regs)); ++ regmap_multi_reg_write(wsa885x->regmap, unmute_commit_regs, ++ ARRAY_SIZE(unmute_commit_regs)); ++ ret = wsa885x_wait_for_pde_state(wsa885x, ps0); ++ if (ret) ++ goto exit_unlock; ++ ++ if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_STEREO) { ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, ++ WSA885X_I2S_TDM_CH_RX_CH0_EN | ++ WSA885X_I2S_TDM_CH_RX_CH3_EN); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x03); ++ } else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_LEFT) { ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, ++ WSA885X_I2S_TDM_CH_RX_CH3_EN); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x01); ++ } else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_RIGHT) { ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, ++ WSA885X_I2S_TDM_CH_RX_CH0_EN); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x02); ++ } ++ ++ regmap_multi_reg_write(wsa885x->regmap, unmute_finish_regs, ++ ARRAY_SIZE(unmute_finish_regs)); ++ } ++ ++exit_unlock: ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return ret; ++} ++ ++static int wsa885x_codec_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) ++ return 0; ++ ++ mutex_lock(&wsa885x->state_lock); ++ regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 0; ++} ++ ++static const struct snd_soc_dai_ops wsa885x_dai_ops = { ++ .hw_params = wsa885x_codec_hw_params, ++ .set_tdm_slot = wsa885x_codec_set_tdm_slot, ++ .set_sysclk = wsa885x_codec_set_sysclk, ++ .mute_stream = wsa885x_codec_mute_stream, ++ .hw_free = wsa885x_codec_hw_free, ++}; ++ ++static struct snd_soc_dai_driver wsa885x_dai[] = { ++ { ++ .name = "wsa885x_dai_drv", ++ .playback = { ++ .stream_name = "WSA885X TDM Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = WSA885X_RATES, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | ++ SNDRV_PCM_FMTBIT_S32_LE, ++ }, ++ .ops = &wsa885x_dai_ops, ++ }, ++}; ++ ++static void wsa885x_reset_assert(void *data) ++{ ++ struct wsa885x_priv *wsa885x = data; ++ ++ if (wsa885x->sd_reset) ++ reset_control_assert(wsa885x->sd_reset); ++ else ++ gpiod_direction_output(wsa885x->sd_n, 1); ++} ++ ++static void wsa885x_reset_deassert(struct wsa885x_priv *wsa885x) ++{ ++ if (wsa885x->sd_reset) ++ reset_control_deassert(wsa885x->sd_reset); ++ else ++ gpiod_direction_output(wsa885x->sd_n, 0); ++} ++ ++static int wsa885x_get_reset(struct device *dev, struct wsa885x_priv *wsa885x) ++{ ++ wsa885x->sd_reset = devm_reset_control_get_optional_shared(dev, NULL); ++ if (IS_ERR(wsa885x->sd_reset)) ++ return dev_err_probe(dev, PTR_ERR(wsa885x->sd_reset), ++ "Failed to get reset\n"); ++ else if (wsa885x->sd_reset) ++ return 0; ++ ++ wsa885x->sd_n = devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH); ++ if (IS_ERR(wsa885x->sd_n)) ++ return dev_err_probe(dev, PTR_ERR(wsa885x->sd_n), ++ "Shutdown Control GPIO not found\n"); ++ ++ return 0; ++} ++ ++static bool wsa885x_volatile_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case WSA885X_ANA_TOP_PLL_STATUS_0: ++ case WSA885X_ANA_TOP_PLL_STATUS_1: ++ case WSA885X_DIG_CTRL0_SDCA_COMMIT: ++ case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS: ++ case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID: ++ case WSA885X_INTR_STATUS0: ++ case WSA885X_INTR_STATUS0 + 1: ++ case WSA885X_INTR_STATUS0 + 2: ++ case WSA885X_INTR_CLEAR0: ++ case WSA885X_INTR_CLEAR0 + 1: ++ case WSA885X_INTR_CLEAR0 + 2: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static bool wsa885x_readable_register(struct device *dev, unsigned int reg) ++{ ++ if (reg == WSA885X_INTR_CLEAR0 || ++ reg == WSA885X_INTR_CLEAR0 + 1 || ++ reg == WSA885X_INTR_CLEAR0 + 2) ++ return false; ++ return reg <= 0x88ff; ++} ++ ++static bool wsa885x_writeable_register(struct device *dev, unsigned int reg) ++{ ++ if (reg > 0x88ff) ++ return false; ++ ++ switch (reg) { ++ case WSA885X_ANA_TOP_PLL_STATUS_0: ++ case WSA885X_ANA_TOP_PLL_STATUS_1: ++ case WSA885X_INTR_STATUS0: ++ case WSA885X_INTR_STATUS0 + 1: ++ case WSA885X_INTR_STATUS0 + 2: ++ case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS: ++ case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID: ++ return false; ++ default: ++ return true; ++ } ++} ++ ++static const struct regmap_config wsa885x_regmap_cfg = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = 0x88FF, ++ .ranges = wsa885x_regmap_ranges, ++ .num_ranges = ARRAY_SIZE(wsa885x_regmap_ranges), ++ .reg_defaults = wsa885x_codec_reg_defaults, ++ .num_reg_defaults = ARRAY_SIZE(wsa885x_codec_reg_defaults), ++ .volatile_reg = wsa885x_volatile_register, ++ .writeable_reg = wsa885x_writeable_register, ++ .readable_reg = wsa885x_readable_register, ++ .cache_type = REGCACHE_MAPLE, ++ .use_single_read = true, ++ .use_single_write = true, ++}; ++ ++static int wsa885x_component_probe(struct snd_soc_component *component) ++{ ++ struct wsa885x_priv *wsa885x = ++ snd_soc_component_get_drvdata(component); ++ int ret; ++ ++ wsa885x->component = component; ++ snd_soc_component_init_regmap(component, wsa885x->regmap); ++ ++ ret = wsa885x_hw_init(wsa885x); ++ if (ret) ++ return ret; ++ ++ return wsa885x_unmask_interrupts(wsa885x); ++} ++ ++static int wsa885x_stereo_gain_offset_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ int val; ++ ++ mutex_lock(&wsa885x->state_lock); ++ val = wsa885x->stereo_vol_db + 84; ++ mutex_unlock(&wsa885x->state_lock); ++ if (val < 0 || val > WSA885X_FU21_VOL_STEPS) ++ return -ERANGE; ++ ++ ucontrol->value.integer.value[0] = val; ++ return 0; ++} ++ ++static int wsa885x_stereo_gain_offset_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ long val; ++ int stereo_vol_db; ++ ++ val = ucontrol->value.integer.value[0]; ++ ++ if (val < 0 || val > WSA885X_FU21_VOL_STEPS) { ++ dev_err(component->dev, "%s: Invalid range, Val: %ld\n", __func__, val); ++ return -EINVAL; ++ } ++ ++ stereo_vol_db = (int)val - 84; ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->stereo_vol_db == stereo_vol_db) { ++ mutex_unlock(&wsa885x->state_lock); ++ return 0; ++ } ++ ++ wsa885x_program_stereo_volume(wsa885x, stereo_vol_db, true); ++ wsa885x->stereo_vol_db = stereo_vol_db; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 1; ++} ++ ++static int wsa885x_usage_modes_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX) { ++ mutex_unlock(&wsa885x->state_lock); ++ return -ERANGE; ++ } ++ ++ ucontrol->value.integer.value[0] = wsa885x->usage_mode; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 0; ++} ++ ++static int wsa885x_usage_modes_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ u32 val = ucontrol->value.integer.value[0]; ++ ++ if (val > WSA885X_USAGE_MODE_MAX) ++ return -EINVAL; ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->usage_mode == val) { ++ mutex_unlock(&wsa885x->state_lock); ++ return 0; ++ } ++ ++ wsa885x->usage_mode = val; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 1; ++} ++ ++static int wsa885x_rx_slot_mask_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ u32 mask; ++ ++ mutex_lock(&wsa885x->state_lock); ++ mask = wsa885x->rx_slot_mask; ++ mutex_unlock(&wsa885x->state_lock); ++ if (!wsa885x_is_valid_rx_slot_mask(mask)) ++ return -ERANGE; ++ ++ ucontrol->value.integer.value[0] = mask; ++ ++ return 0; ++} ++ ++static int wsa885x_rx_slot_mask_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ u32 mask = ucontrol->value.integer.value[0]; ++ ++ if (!wsa885x_is_valid_rx_slot_mask(mask)) ++ return -EINVAL; ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->rx_slot_mask == mask) { ++ mutex_unlock(&wsa885x->state_lock); ++ return 0; ++ } ++ ++ wsa885x->rx_slot_mask = mask; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 1; ++} ++ ++static const struct snd_kcontrol_new wsa885x_snd_controls[] = { ++ SOC_SINGLE_EXT("Usage Mode", SND_SOC_NOPM, 0, WSA885X_USAGE_MODE_MAX, 0, ++ wsa885x_usage_modes_get, ++ wsa885x_usage_modes_put), ++ ++ SOC_SINGLE_EXT_TLV("Speaker Volume", SND_SOC_NOPM, ++ 0, WSA885X_FU21_VOL_STEPS, 0, ++ wsa885x_stereo_gain_offset_get, ++ wsa885x_stereo_gain_offset_put, ++ wsa885x_fu21_digital_gain), ++ ++ SOC_SINGLE_EXT("Rx Slot Mask", SND_SOC_NOPM, 0, 3, 0, ++ wsa885x_rx_slot_mask_get, ++ wsa885x_rx_slot_mask_put), ++}; ++ ++static const struct snd_soc_component_driver wsa885x_component = { ++ .name = "wsa885x", ++ .probe = wsa885x_component_probe, ++ .controls = wsa885x_snd_controls, ++ .num_controls = ARRAY_SIZE(wsa885x_snd_controls), ++}; ++ ++static irqreturn_t wsa885x_handle_irq(int irq_idx, void *data) ++{ ++ struct wsa885x_priv *wsa885x = data; ++ ++ if (irq_idx < 0 || irq_idx >= WSA885X_IRQ_MAX) ++ return IRQ_NONE; ++ ++ switch (irq_idx) { ++ case WSA885X_IRQ_INT_SAF2WAR: ++ case WSA885X_IRQ_INT_WAR2SAF: ++ case WSA885X_IRQ_INT_DISABLE: ++ case WSA885X_IRQ_INT_INTR_GPIO1_PIN: ++ case WSA885X_IRQ_INT_INTR_GPIO2_PIN: ++ case WSA885X_IRQ_INT_PA0_OCP: ++ case WSA885X_IRQ_INT_PA1_OCP: ++ case WSA885X_IRQ_INT_CLIP0: ++ case WSA885X_IRQ_INT_CLIP1: ++ case WSA885X_IRQ_INT_CLK_WD: ++ case WSA885X_IRQ_INT_BOP: ++ case WSA885X_IRQ_INT_UVLO: ++ case WSA885X_IRQ_INT_PCM_DATA0_DC: ++ case WSA885X_IRQ_INT_PCM_DATA1_DC: ++ case WSA885X_IRQ_INT_PLL_UNLOCKED: ++ case WSA885X_IRQ_INT_PROT_MODE_CHANGE: ++ case WSA885X_IRQ_INT_PB_CLOCK_VALID: ++ case WSA885X_IRQ_INT_SENSE_CLOCK_VALID: ++ break; ++ case WSA885X_IRQ_INT_PCM_DATA0_WD: ++ case WSA885X_IRQ_INT_PCM_DATA1_WD: ++ if (irq_idx == WSA885X_IRQ_INT_PCM_DATA0_WD) ++ wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1, ++ WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK); ++ else ++ wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1, ++ WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK); ++ break; ++ case WSA885X_IRQ_INT_PA0_FSM_ERR: ++ case WSA885X_IRQ_INT_PA1_FSM_ERR: ++ case WSA885X_IRQ_INT_MAIN_FSM_ERR: ++ if (irq_idx == WSA885X_IRQ_INT_MAIN_FSM_ERR) { ++ wsa885x_pulse_irq_bit(wsa885x, WSA885X_DIG_CTRL0_POWER_FSM_CTL0, ++ WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK); ++ } else if (irq_idx == WSA885X_IRQ_INT_PA0_FSM_ERR) { ++ wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA0_FSM_CTL0, ++ WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK); ++ } else if (irq_idx == WSA885X_IRQ_INT_PA1_FSM_ERR) { ++ wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA1_FSM_CTL0, ++ WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK); ++ } ++ break; ++ default: ++ break; ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t wsa885x_interrupt_handler(int irq, void *data) ++{ ++ static const unsigned int status_reg[WSA885X_NUM_REGS] = { ++ WSA885X_INTR_STATUS0, ++ WSA885X_INTR_STATUS0 + 1, ++ WSA885X_INTR_STATUS0 + 2, ++ }; ++ static const unsigned int clear_reg[WSA885X_NUM_REGS] = { ++ WSA885X_INTR_CLEAR0, ++ WSA885X_INTR_CLEAR0 + 1, ++ WSA885X_INTR_CLEAR0 + 2, ++ }; ++ unsigned int status[WSA885X_NUM_REGS] = { 0 }; ++ struct wsa885x_priv *wsa885x = data; ++ irqreturn_t handled = IRQ_NONE; ++ irqreturn_t irq_ret; ++ int i, bit, ret, irq_num; ++ ++ for (i = 0; i < WSA885X_NUM_REGS; i++) { ++ ret = regmap_read(wsa885x->regmap, status_reg[i], &status[i]); ++ if (ret) { ++ dev_err(wsa885x->dev, ++ "Failed to read status_reg[%d] (0x%x): %d\n", ++ i, status_reg[i], ret); ++ status[i] = 0; ++ continue; ++ } ++ } ++ ++ for (i = 0; i < WSA885X_NUM_REGS; i++) { ++ for (bit = 0; bit < 8; bit++) { ++ if (status[i] & BIT(bit)) { ++ irq_num = i * 8 + bit; ++ regmap_write(wsa885x->regmap, clear_reg[i], BIT(bit)); ++ regmap_write(wsa885x->regmap, clear_reg[i], 0); ++ if (irq_num >= WSA885X_IRQ_MAX) { ++ dev_warn_ratelimited(wsa885x->dev, ++ "Unexpected IRQ bit %d (reg %d)\n", ++ bit, i); ++ handled = IRQ_HANDLED; ++ continue; ++ } ++ irq_ret = wsa885x_handle_irq(irq_num, wsa885x); ++ if (irq_ret == IRQ_HANDLED) ++ handled = IRQ_HANDLED; ++ } ++ } ++ } ++ return handled; ++} ++ ++static int wsa885x_register_irq(struct wsa885x_priv *wsa885x) ++{ ++ if (!wsa885x->client->irq) ++ return dev_err_probe(wsa885x->dev, -EINVAL, ++ "IRQ is not configured\n"); ++ ++ return devm_request_threaded_irq(wsa885x->dev, wsa885x->client->irq, NULL, ++ wsa885x_interrupt_handler, ++ IRQF_ONESHOT, ++ dev_name(wsa885x->dev), wsa885x); ++} ++ ++static int wsa885x_probe(struct i2c_client *client) ++{ ++ struct wsa885x_priv *wsa885x; ++ const struct snd_soc_component_driver *component_driver = &wsa885x_component; ++ const char *battery_config; ++ unsigned int i; ++ int ret; ++ struct device *dev = &client->dev; ++ ++ wsa885x = devm_kzalloc(dev, sizeof(*wsa885x), GFP_KERNEL); ++ if (!wsa885x) ++ return -ENOMEM; ++ ++ wsa885x->client = client; ++ wsa885x->dev = dev; ++ wsa885x->stereo_vol_db = -84; ++ wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; ++ mutex_init(&wsa885x->state_lock); ++ wsa885x->regmap = devm_regmap_init_i2c(client, &wsa885x_regmap_cfg); ++ ++ if (IS_ERR(wsa885x->regmap)) ++ return PTR_ERR(wsa885x->regmap); ++ ++ ret = device_property_read_string(dev, "qcom,battery-config", ++ &battery_config); ++ if (ret) { ++ wsa885x->batt_conf = WSA885X_BATT_1S; ++ } else if (!strcmp(battery_config, "1s")) { ++ wsa885x->batt_conf = WSA885X_BATT_1S; ++ } else if (!strcmp(battery_config, "2s")) { ++ wsa885x->batt_conf = WSA885X_BATT_2S; ++ } else { ++ return dev_err_probe(dev, -EINVAL, ++ "Invalid battery config %s (expected 1s or 2s)\n", ++ battery_config); ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(wsa885x_supply_name); i++) { ++ ret = devm_regulator_get_enable(dev, wsa885x_supply_name[i]); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "Failed to enable regulator %s\n", ++ wsa885x_supply_name[i]); ++ } ++ ++ ret = wsa885x_get_reset(dev, wsa885x); ++ if (ret) ++ return ret; ++ ++ wsa885x_reset_deassert(wsa885x); ++ usleep_range(5000, 5500); ++ ++ ret = devm_add_action_or_reset(dev, wsa885x_reset_assert, wsa885x); ++ if (ret) ++ return dev_err_probe(dev, ret, "devm_add_action_or_reset failed\n"); ++ ++ i2c_set_clientdata(client, wsa885x); ++ ++ ret = wsa885x_register_irq(wsa885x); ++ if (ret) ++ return dev_err_probe(dev, ret, "wsa885x irq registration failed\n"); ++ ++ ret = devm_snd_soc_register_component(dev, component_driver, ++ wsa885x_dai, ++ ARRAY_SIZE(wsa885x_dai)); ++ if (ret) ++ return dev_err_probe(dev, ret, "Codec component registration failed\n"); ++ ++ return 0; ++} ++ ++static const struct of_device_id wsa885x_dt_match[] = { ++ { ++ .compatible = "qcom,wsa8855", ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, wsa885x_dt_match); ++ ++static const struct i2c_device_id wsa885x_id[] = { ++ { ++ .name = "wsa885x", ++ .driver_data = 0, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, wsa885x_id); ++ ++static struct i2c_driver wsa885x_driver = { ++ .driver = { ++ .name = "wsa885x", ++ .of_match_table = wsa885x_dt_match, ++ }, ++ .probe = wsa885x_probe, ++ .id_table = wsa885x_id, ++}; ++ ++module_i2c_driver(wsa885x_driver); ++ ++MODULE_DESCRIPTION("ASoC WSA885X Stereo Smart PA Codec Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/sound/soc/codecs/zl38060.c b/sound/soc/codecs/zl38060.c +--- a/sound/soc/codecs/zl38060.c ++++ b/sound/soc/codecs/zl38060.c +@@ -162,7 +162,7 @@ static int zl38_fw_send_xaddr(struct regmap *regmap, const void *data) + static int zl38_load_firmware(struct device *dev, struct regmap *regmap) + { + const struct ihex_binrec *rec; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + u32 addr; + u16 len; + int err; +@@ -180,7 +180,7 @@ static int zl38_load_firmware(struct device *dev, struct regmap *regmap) + return err; + err = zl38_fw_enter_boot_mode(regmap); + if (err) +- goto out; ++ return err; + rec = (const struct ihex_binrec *)fw->data; + while (rec) { + addr = be32_to_cpu(rec->addr); +@@ -195,15 +195,12 @@ static int zl38_load_firmware(struct device *dev, struct regmap *regmap) + err = -EINVAL; + } + if (err) +- goto out; ++ return err; + /* next ! */ + rec = ihex_next_binrec(rec); + } +- err = zl38_fw_go(regmap); + +-out: +- release_firmware(fw); +- return err; ++ return zl38_fw_go(regmap); + } + + +diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c +--- a/sound/soc/dwc/dwc-i2s.c ++++ b/sound/soc/dwc/dwc-i2s.c +@@ -955,7 +955,9 @@ static int dw_i2s_probe(struct platform_device *pdev) + } + + irq = platform_get_irq_optional(pdev, 0); +- if (irq >= 0) { ++ if (irq == -EPROBE_DEFER) ++ return irq; ++ if (irq > 0) { + ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0, + pdev->name, dev); + if (ret < 0) { +diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c +--- a/sound/soc/fsl/fsl-asoc-card.c ++++ b/sound/soc/fsl/fsl-asoc-card.c +@@ -69,7 +69,6 @@ static const struct snd_pcm_hw_constraint_list cs42888_channel_constraints = { + + /** + * struct codec_priv - CODEC private data +- * @mclk: Main clock of the CODEC + * @mclk_freq: Clock rate of MCLK + * @free_freq: Clock rate of MCLK for hw_free() + * @mclk_id: MCLK (or main clock) id for set_sysclk() +@@ -80,7 +79,6 @@ static const struct snd_pcm_hw_constraint_list cs42888_channel_constraints = { + * to stay within PLL frequency limits + */ + struct codec_priv { +- struct clk *mclk; + unsigned long mclk_freq; + unsigned long free_freq; + u32 mclk_id; +@@ -109,12 +107,58 @@ struct cpu_priv { + u32 slot_num; + }; + ++struct fsl_asoc_card_priv; ++ ++/* ++ * struct fsl_asoc_card_pdata - per-compatible static card description ++ * @sysclk_dir: initial CPU SYSCLK direction override (0 = leave default IN) ++ * @sysclk_ratio: SYSCLK ratio on sample rate (0 = not used) ++ * @slot_width: TDM slot width (0 = not TDM) ++ * @codec_dai_name: name of the codec DAI ++ * @codec_mclk_id: MCLK id passed to set_sysclk() for the codec ++ * @codec_fll_id: FLL id; only valid when has_pll is true ++ * @codec_pll_id: PLL id; only valid when has_pll is true ++ * @codec_pll_ratio_s24: PLL output ratio for S24_LE ++ * @has_pll: codec uses PLL/FLL; codec_fll_id and codec_pll_id are valid ++ * @dai_fmt: DAI format flags ++ * @playback_only: restrict card to playback direction ++ * @capture_only: restrict card to capture direction ++ * @dapm_routes: DAPM route table override ++ * @num_dapm_routes: number of entries in dapm_routes ++ * @exclude_format: PCM format bitmask excluded (for SAI + WM8960/WM8962) ++ * @codec_init: codec-specific init run after mclk_freq is populated ++ * @probe_init: optional DT-driven init run at end of probe() (e.g. SPDIF codec discovery) ++ */ ++struct fsl_asoc_card_pdata { ++ u32 sysclk_dir[2]; ++ u32 sysclk_ratio[2]; ++ u32 slot_width; ++ const char *codec_dai_name; ++ u32 codec_mclk_id; ++ int codec_fll_id; ++ int codec_pll_id; ++ int codec_pll_ratio_s24; ++ bool has_pll; ++ bool playback_only; ++ bool capture_only; ++ u32 dai_fmt; ++ const struct snd_soc_dapm_route *dapm_routes; ++ int num_dapm_routes; ++ u64 exclude_format; ++ int (*codec_init)(struct fsl_asoc_card_priv *priv); ++ int (*probe_init)(struct device_node *codec_np[], ++ struct device_node *cpu_np, ++ const char *codec_dai_name[], ++ struct fsl_asoc_card_priv *priv); ++}; ++ + /** + * struct fsl_asoc_card_priv - Freescale Generic ASOC card private data + * @dai_link: DAI link structure including normal one and DPCM link + * @hp_jack: Headphone Jack structure + * @mic_jack: Microphone Jack structure + * @pdev: platform device pointer ++ * @pdata: pointer to the per-compatible card platform data + * @codec_priv: CODEC private data + * @cpu_priv: CPU private data + * @card: ASoC card structure +@@ -135,6 +179,7 @@ struct fsl_asoc_card_priv { + struct simple_util_jack hp_jack; + struct simple_util_jack mic_jack; + struct platform_device *pdev; ++ const struct fsl_asoc_card_pdata *pdata; + struct codec_priv codec_priv[2]; + struct cpu_priv cpu_priv; + struct snd_soc_card card; +@@ -609,6 +654,192 @@ static int fsl_asoc_card_spdif_init(struct device_node *codec_np[], + return 0; + } + ++static int fsl_asoc_card_cs42888_codec_init(struct fsl_asoc_card_priv *priv) ++{ ++ unsigned long mclk_freq = priv->codec_priv[0].mclk_freq; ++ ++ /* ++ * Set CPU sysclk frequency from codec MCLK only if not already ++ * set by the CPU DAI init (e.g. ESAI extal clock takes precedence). ++ */ ++ if (!priv->cpu_priv.sysclk_freq[TX]) ++ priv->cpu_priv.sysclk_freq[TX] = mclk_freq; ++ if (!priv->cpu_priv.sysclk_freq[RX]) ++ priv->cpu_priv.sysclk_freq[RX] = mclk_freq; ++ ++ priv->constraint_channels = &cs42888_channel_constraints; ++ if (mclk_freq % 12288000 == 0) ++ priv->constraint_rates = &cs42888_rate_48k_constraints; ++ else if (mclk_freq % 11289600 == 0) ++ priv->constraint_rates = &cs42888_rate_44k_constraints; ++ else ++ dev_warn(&priv->pdev->dev, ++ "Unknown MCLK frequency %lu, no rate constraints\n", ++ mclk_freq); ++ ++ return 0; ++} ++ ++static int fsl_asoc_card_wm8958_codec_init(struct fsl_asoc_card_priv *priv) ++{ ++ priv->codec_priv[0].free_freq = priv->codec_priv[0].mclk_freq; ++ return 0; ++} ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_cs42888_pdata = { ++ .codec_dai_name = "cs42888", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ .sysclk_dir = { SND_SOC_CLOCK_OUT, SND_SOC_CLOCK_OUT }, ++ .slot_width = 32, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++ .codec_init = fsl_asoc_card_cs42888_codec_init, ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_cs427x_pdata = { ++ .codec_dai_name = "cs4271-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = CS427x_SYSCLK_MCLK, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_sgtl5000_pdata = { ++ .codec_dai_name = "sgtl5000", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = SGTL5000_SYSCLK, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_tlv320aic32x4_pdata = { ++ .codec_dai_name = "tlv320aic32x4-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_tlv320aic31xx_pdata = { ++ .codec_dai_name = "tlv320dac31xx-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ .sysclk_dir = { SND_SOC_CLOCK_OUT, SND_SOC_CLOCK_OUT }, ++ .playback_only = true, ++ .dapm_routes = audio_map_tx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_tx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8962_pdata = { ++ .codec_dai_name = "wm8962", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = WM8962_SYSCLK_MCLK, ++ .has_pll = true, ++ .codec_fll_id = WM8962_SYSCLK_FLL, ++ .codec_pll_id = WM8962_FLL, ++ /* ++ * WM8962 has same BCLK generation limitations as WM8960. ++ * See WM8960 section for detailed explanation. ++ */ ++ .exclude_format = SNDRV_PCM_FMTBIT_S20_3LE, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8960_pdata = { ++ .codec_dai_name = "wm8960-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .has_pll = true, ++ .codec_fll_id = WM8960_SYSCLK_AUTO, ++ .codec_pll_id = WM8960_SYSCLK_AUTO, ++ /* ++ * WM8960 in master mode cannot generate exact 1.92 MHz BCLK ++ * required for S20_3LE (48kHz x 2ch x 20bit). Closest available ++ * is 2.048 MHz (SYSCLK/6), which causes right channel corruption. ++ * ++ * In SAI master mode, SAI derive BCLK from MCLK using integer ++ * dividers only. S20_3LE requires non-integer divider ratios ++ * with standard MCLK frequencies. For example, 48kHz stereo ++ * needs 1.920 MHz BCLK, which requires a divider of 6.4 from ++ * 12.288 MHz MCLK (not an integer). ++ */ ++ .exclude_format = SNDRV_PCM_FMTBIT_S20_3LE, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_ac97_pdata = { ++ .codec_dai_name = "ac97-hifi", ++ .dai_fmt = SND_SOC_DAIFMT_AC97, ++ .dapm_routes = audio_map_ac97, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_ac97), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_mqs_pdata = { ++ .codec_dai_name = "fsl-mqs-dai", ++ .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBC_CFC | ++ SND_SOC_DAIFMT_NB_NF, ++ .playback_only = true, ++ .dapm_routes = audio_map_tx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_tx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8524_pdata = { ++ .codec_dai_name = "wm8524-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ /* RX=0, TX=1: set TX (index 1) to CLOCK_OUT, RX stays at default IN */ ++ .sysclk_dir = { 0, SND_SOC_CLOCK_OUT }, ++ .sysclk_ratio = { 0, 256 }, ++ .slot_width = 32, ++ .playback_only = true, ++ .dapm_routes = audio_map_tx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_tx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_si476x_pdata = { ++ .codec_dai_name = "si476x-codec", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ .dapm_routes = audio_map_rx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_rx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8958_pdata = { ++ .codec_dai_name = "wm8994-aif1", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = WM8994_FLL_SRC_MCLK1, ++ .has_pll = true, ++ .codec_fll_id = WM8994_SYSCLK_FLL1, ++ .codec_pll_id = WM8994_FLL1, ++ .codec_init = fsl_asoc_card_wm8958_codec_init, ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_nau8822_pdata = { ++ .codec_dai_name = "nau8822-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = NAU8822_CLK_MCLK, ++ .has_pll = true, ++ .codec_fll_id = NAU8822_CLK_PLL, ++ .codec_pll_id = NAU8822_CLK_PLL, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8904_pdata = { ++ .codec_dai_name = "wm8904-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = WM8904_FLL_MCLK, ++ .has_pll = true, ++ .codec_fll_id = WM8904_CLK_FLL, ++ .codec_pll_id = WM8904_FLL_MCLK, ++ .codec_pll_ratio_s24 = 192, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_spdif_pdata = { ++ .codec_dai_name = "spdif", ++ .dai_fmt = DAI_FMT_BASE, ++ .probe_init = fsl_asoc_card_spdif_init, ++}; ++ + static int hp_jack_event(struct notifier_block *nb, unsigned long event, + void *data) + { +@@ -643,17 +874,173 @@ static struct notifier_block mic_jack_nb = { + .notifier_call = mic_jack_event, + }; + +-static int fsl_asoc_card_late_probe(struct snd_soc_card *card) ++/* ++ * fsl_asoc_card_init_cpu - configure CPU DAI-specific settings. ++ * ++ * Called from late_probe() when the CPU DAI component is guaranteed bound. ++ */ ++static int fsl_asoc_card_init_cpu(struct snd_soc_card *card, ++ struct snd_soc_pcm_runtime *rtd) + { + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); +- struct snd_soc_pcm_runtime *rtd = list_first_entry( +- &card->rtd_list, struct snd_soc_pcm_runtime, list); ++ struct device_node *np = priv->pdev->dev.of_node; ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ const char *comp_drv_name = cpu_dai->component->driver->name; ++ struct device *dev = card->dev; ++ int ret; ++ ++ if (!strcmp(comp_drv_name, "fsl-ssi")) { ++ /* Only SSI needs to configure AUDMUX */ ++ ret = fsl_asoc_card_audmux_init(np, priv); ++ if (ret) { ++ dev_err(dev, "failed to init audmux\n"); ++ return ret; ++ } ++ } else if (!strcmp(comp_drv_name, "fsl-esai")) { ++ struct clk *esai_clk = clk_get(cpu_dai->dev, "extal"); ++ ++ if (!IS_ERR(esai_clk)) { ++ priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(esai_clk); ++ priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(esai_clk); ++ clk_put(esai_clk); ++ } else { ++ dev_warn(dev, "failed to get ESAI extal clock: %ld\n", PTR_ERR(esai_clk)); ++ } ++ ++ priv->cpu_priv.sysclk_id[TX] = ESAI_HCKT_EXTAL; ++ priv->cpu_priv.sysclk_id[RX] = ESAI_HCKR_EXTAL; ++ } else if (!strcmp(comp_drv_name, "fsl-sai")) { ++ priv->cpu_priv.sysclk_id[TX] = FSL_SAI_CLK_MAST1; ++ priv->cpu_priv.sysclk_id[RX] = FSL_SAI_CLK_MAST1; ++ ++ if (priv->pdata->exclude_format) ++ priv->exclude_format = priv->pdata->exclude_format; ++ } ++ ++ return 0; ++} ++ ++/* ++ * fsl_asoc_card_init_codecs - read codec MCLK rates and set codec sysclk. ++ * ++ * Called from late_probe() after all components are bound. ++ */ ++static int fsl_asoc_card_init_codecs(struct snd_soc_card *card, ++ struct snd_soc_pcm_runtime *rtd) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ const struct fsl_asoc_card_pdata *pdata = priv->pdata; + struct snd_soc_dai *codec_dai; + struct codec_priv *codec_priv; + struct device *dev = card->dev; + int codec_idx; + int ret; + ++ /* Read MCLK rate from each bound codec component */ ++ for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { ++ struct clk *codec_clk = clk_get(codec_dai->component->dev, NULL); ++ ++ codec_priv = &priv->codec_priv[codec_idx]; ++ if (!IS_ERR(codec_clk)) { ++ codec_priv->mclk_freq = clk_get_rate(codec_clk); ++ clk_put(codec_clk); ++ } ++ } ++ ++ if (pdata->codec_init) { ++ ret = pdata->codec_init(priv); ++ if (ret) ++ return ret; ++ } ++ ++ for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { ++ codec_priv = &priv->codec_priv[codec_idx]; ++ ++ ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, ++ codec_priv->mclk_freq, SND_SOC_CLOCK_IN); ++ if (ret && ret != -ENOTSUPP) { ++ dev_err(dev, "failed to set sysclk in %s\n", __func__); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++ ++static void fsl_asoc_card_free_jack(struct snd_soc_card *card) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ ++ if (priv->hp_jack.gpio.desc) { ++ snd_soc_jack_notifier_unregister(&priv->hp_jack.jack, &hp_jack_nb); ++ snd_soc_jack_free_gpios(&priv->hp_jack.jack, 1, &priv->hp_jack.gpio); ++ priv->hp_jack.gpio.desc = NULL; ++ } ++ ++ if (priv->mic_jack.gpio.desc) { ++ snd_soc_jack_notifier_unregister(&priv->mic_jack.jack, &mic_jack_nb); ++ snd_soc_jack_free_gpios(&priv->mic_jack.jack, 1, &priv->mic_jack.gpio); ++ priv->mic_jack.gpio.desc = NULL; ++ } ++} ++ ++/* ++ * fsl_asoc_card_init_jack - register optional headphone and mic jacks. ++ * ++ * Called from late_probe() once per card bind cycle. ++ */ ++static int fsl_asoc_card_init_jack(struct snd_soc_card *card) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ struct device_node *np = priv->pdev->dev.of_node; ++ int ret; ++ ++ /* ++ * Properties "hp-det-gpios" and "mic-det-gpios" are optional. ++ * simple_util_init_jack() checks for the GPIO property and ++ * does nothing if it is absent. ++ */ ++ if (of_property_present(np, "hp-det-gpios") || ++ of_property_present(np, "hp-det-gpio") /* deprecated */) { ++ ret = simple_util_init_jack(card, &priv->hp_jack, ++ 1, NULL, "Headphone Jack"); ++ if (ret) ++ return ret; ++ ++ snd_soc_jack_notifier_register(&priv->hp_jack.jack, &hp_jack_nb); ++ } ++ ++ if (of_property_present(np, "mic-det-gpios") || ++ of_property_present(np, "mic-det-gpio") /* deprecated */) { ++ ret = simple_util_init_jack(card, &priv->mic_jack, ++ 0, NULL, "Mic Jack"); ++ if (ret) ++ return ret; ++ ++ snd_soc_jack_notifier_register(&priv->mic_jack.jack, &mic_jack_nb); ++ } ++ ++ return 0; ++} ++ ++static int fsl_asoc_card_late_probe(struct snd_soc_card *card) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ struct snd_soc_pcm_runtime *rtd; ++ int ret; ++ ++ /* Use the first rtd which carries the CPU+codec DAIs */ ++ rtd = list_first_entry(&card->rtd_list, ++ struct snd_soc_pcm_runtime, list); ++ ++ ret = fsl_asoc_card_init_jack(card); ++ if (ret) ++ goto jack_fail; ++ ++ ret = fsl_asoc_card_init_cpu(card, rtd); ++ if (ret) ++ goto jack_fail; ++ + if (fsl_asoc_card_is_ac97(priv)) { + #if IS_ENABLED(CONFIG_SND_AC97_CODEC) + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; +@@ -671,19 +1058,20 @@ static int fsl_asoc_card_late_probe(struct snd_soc_card *card) + return 0; + } + +- for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { +- codec_priv = &priv->codec_priv[codec_idx]; ++ ret = fsl_asoc_card_init_codecs(card, rtd); ++ if (ret) ++ goto jack_fail; + +- ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, +- codec_priv->mclk_freq, SND_SOC_CLOCK_IN); +- if (ret && ret != -ENOTSUPP) { +- dev_err(dev, "failed to set sysclk in %s\n", __func__); +- return ret; +- } ++ return 0; + +- if (!IS_ERR_OR_NULL(codec_priv->mclk)) +- clk_prepare_enable(codec_priv->mclk); +- } ++jack_fail: ++ fsl_asoc_card_free_jack(card); ++ return ret; ++} ++ ++static int fsl_asoc_card_card_remove(struct snd_soc_card *card) ++{ ++ fsl_asoc_card_free_jack(card); + + return 0; + } +@@ -697,12 +1085,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + struct platform_device *asrc_pdev = NULL; + struct device_node *bitclkprovider = NULL; + struct device_node *frameprovider = NULL; +- struct platform_device *cpu_pdev; + struct fsl_asoc_card_priv *priv; +- struct device *codec_dev[2] = { NULL, NULL }; ++ const struct fsl_asoc_card_pdata *pdata; + struct snd_soc_dai_link_component *dlc; +- const char *codec_dai_name[2]; +- const char *codec_dev_name[2]; ++ const char *codec_dai_name[2] = { NULL, NULL }; + u32 asrc_fmt = 0; + int codec_idx; + u32 width; +@@ -714,6 +1100,13 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + + priv->pdev = pdev; + ++ pdata = of_device_get_match_data(&pdev->dev); ++ if (!pdata) { ++ dev_err(&pdev->dev, "unknown Device Tree compatible\n"); ++ return -EINVAL; ++ } ++ priv->pdata = pdata; ++ + cpu_np = of_parse_phandle(np, "audio-cpu", 0); + /* Give a chance to old DT bindings */ + if (!cpu_np) +@@ -726,52 +1119,13 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + goto fail; + } + +- cpu_pdev = of_find_device_by_node(cpu_np); +- if (!cpu_pdev) { +- dev_err(&pdev->dev, "failed to find CPU DAI device\n"); +- ret = -EINVAL; +- goto fail; +- } +- + codec_np[0] = of_parse_phandle(np, "audio-codec", 0); + codec_np[1] = of_parse_phandle(np, "audio-codec", 1); + +- for (codec_idx = 0; codec_idx < 2; codec_idx++) { +- if (codec_np[codec_idx]) { +- struct platform_device *codec_pdev; +- struct i2c_client *codec_i2c; +- +- codec_i2c = of_find_i2c_device_by_node(codec_np[codec_idx]); +- if (codec_i2c) { +- codec_dev[codec_idx] = &codec_i2c->dev; +- codec_dev_name[codec_idx] = codec_i2c->name; +- } +- if (!codec_dev[codec_idx]) { +- codec_pdev = of_find_device_by_node(codec_np[codec_idx]); +- if (codec_pdev) { +- codec_dev[codec_idx] = &codec_pdev->dev; +- codec_dev_name[codec_idx] = codec_pdev->name; +- } +- } +- } +- } +- + asrc_np = of_parse_phandle(np, "audio-asrc", 0); + if (asrc_np) + asrc_pdev = of_find_device_by_node(asrc_np); + +- /* Get the MCLK rate only, and leave it controlled by CODEC drivers */ +- for (codec_idx = 0; codec_idx < 2; codec_idx++) { +- if (codec_dev[codec_idx]) { +- struct clk *codec_clk = clk_get(codec_dev[codec_idx], NULL); +- +- if (!IS_ERR(codec_clk)) { +- priv->codec_priv[codec_idx].mclk_freq = clk_get_rate(codec_clk); +- clk_put(codec_clk); +- } +- } +- } +- + /* Default sample rate and format, will be updated in hw_params() */ + priv->sample_rate = 44100; + priv->sample_format = SNDRV_PCM_FORMAT_S16_LE; +@@ -823,133 +1177,40 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + } + + /* Diversify the card configurations */ +- if (of_device_is_compatible(np, "fsl,imx-audio-cs42888")) { +- codec_dai_name[0] = "cs42888"; +- priv->cpu_priv.sysclk_freq[TX] = priv->codec_priv[0].mclk_freq; +- priv->cpu_priv.sysclk_freq[RX] = priv->codec_priv[0].mclk_freq; +- priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.slot_width = 32; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; +- priv->constraint_channels = &cs42888_channel_constraints; +- if (priv->codec_priv[0].mclk_freq % 12288000 == 0) +- priv->constraint_rates = &cs42888_rate_48k_constraints; +- else if (priv->codec_priv[0].mclk_freq % 11289600 == 0) +- priv->constraint_rates = &cs42888_rate_44k_constraints; +- else +- dev_warn(&pdev->dev, "Unknown MCLK frequency %lu, no rate constraints\n", +- priv->codec_priv[0].mclk_freq); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-cs427x")) { +- codec_dai_name[0] = "cs4271-hifi"; +- priv->codec_priv[0].mclk_id = CS427x_SYSCLK_MCLK; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-sgtl5000")) { +- codec_dai_name[0] = "sgtl5000"; +- priv->codec_priv[0].mclk_id = SGTL5000_SYSCLK; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-tlv320aic32x4")) { +- codec_dai_name[0] = "tlv320aic32x4-hifi"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-tlv320aic31xx")) { +- codec_dai_name[0] = "tlv320dac31xx-hifi"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; ++ priv->cpu_priv.sysclk_dir[TX] = pdata->sysclk_dir[TX]; ++ priv->cpu_priv.sysclk_dir[RX] = pdata->sysclk_dir[RX]; ++ priv->cpu_priv.sysclk_ratio[TX] = pdata->sysclk_ratio[TX]; ++ priv->cpu_priv.sysclk_ratio[RX] = pdata->sysclk_ratio[RX]; ++ priv->cpu_priv.slot_width = pdata->slot_width; ++ ++ codec_dai_name[0] = pdata->codec_dai_name; ++ priv->codec_priv[0].mclk_id = pdata->codec_mclk_id; ++ if (pdata->has_pll) { ++ priv->codec_priv[0].fll_id = pdata->codec_fll_id; ++ priv->codec_priv[0].pll_id = pdata->codec_pll_id; ++ } ++ if (pdata->codec_pll_ratio_s24) ++ priv->codec_priv[0].pll_ratio_s24 = pdata->codec_pll_ratio_s24; ++ ++ if (pdata->playback_only) { + priv->dai_link[1].playback_only = 1; + priv->dai_link[2].playback_only = 1; +- priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; +- priv->card.dapm_routes = audio_map_tx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8962")) { +- codec_dai_name[0] = "wm8962"; +- priv->codec_priv[0].mclk_id = WM8962_SYSCLK_MCLK; +- priv->codec_priv[0].fll_id = WM8962_SYSCLK_FLL; +- priv->codec_priv[0].pll_id = WM8962_FLL; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- /* +- * WM8962 has same BCLK generation limitations as WM8960. +- * See WM8960 section for detailed explanation. +- */ +- if (of_node_name_eq(cpu_np, "sai")) +- priv->exclude_format = SNDRV_PCM_FMTBIT_S20_3LE; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8960")) { +- codec_dai_name[0] = "wm8960-hifi"; +- priv->codec_priv[0].fll_id = WM8960_SYSCLK_AUTO; +- priv->codec_priv[0].pll_id = WM8960_SYSCLK_AUTO; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- /* +- * WM8960 in master mode cannot generate exact 1.92 MHz BCLK +- * required for S20_3LE (48kHz × 2ch × 20bit). Closest available +- * is 2.048 MHz (SYSCLK/6), which causes right channel corruption. +- * +- * In SAI master mode, SAI derive BCLK from MCLK using integer +- * dividers only. S20_3LE requires non-integer divider ratios +- * with standard MCLK frequencies. For example, 48kHz stereo +- * needs 1.920 MHz BCLK, which requires a divider of 6.4 from +- * 12.288 MHz MCLK (not an integer). +- */ +- if (of_node_name_eq(cpu_np, "sai")) +- priv->exclude_format = SNDRV_PCM_FMTBIT_S20_3LE; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-ac97")) { +- codec_dai_name[0] = "ac97-hifi"; +- priv->dai_fmt = SND_SOC_DAIFMT_AC97; +- priv->card.dapm_routes = audio_map_ac97; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_ac97); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-mqs")) { +- codec_dai_name[0] = "fsl-mqs-dai"; +- priv->dai_fmt = SND_SOC_DAIFMT_LEFT_J | +- SND_SOC_DAIFMT_CBC_CFC | +- SND_SOC_DAIFMT_NB_NF; +- priv->dai_link[1].playback_only = 1; +- priv->dai_link[2].playback_only = 1; +- priv->card.dapm_routes = audio_map_tx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8524")) { +- codec_dai_name[0] = "wm8524-hifi"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; +- priv->dai_link[1].playback_only = 1; +- priv->dai_link[2].playback_only = 1; +- priv->cpu_priv.slot_width = 32; +- priv->card.dapm_routes = audio_map_tx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); +- priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.sysclk_ratio[TX] = 256; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-si476x")) { +- codec_dai_name[0] = "si476x-codec"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; +- priv->card.dapm_routes = audio_map_rx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_rx); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8958")) { +- codec_dai_name[0] = "wm8994-aif1"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- priv->codec_priv[0].mclk_id = WM8994_FLL_SRC_MCLK1; +- priv->codec_priv[0].fll_id = WM8994_SYSCLK_FLL1; +- priv->codec_priv[0].pll_id = WM8994_FLL1; +- priv->codec_priv[0].free_freq = priv->codec_priv[0].mclk_freq; +- priv->card.dapm_routes = NULL; +- priv->card.num_dapm_routes = 0; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-nau8822")) { +- codec_dai_name[0] = "nau8822-hifi"; +- priv->codec_priv[0].mclk_id = NAU8822_CLK_MCLK; +- priv->codec_priv[0].fll_id = NAU8822_CLK_PLL; +- priv->codec_priv[0].pll_id = NAU8822_CLK_PLL; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- if (codec_dev[0]) +- priv->codec_priv[0].mclk = devm_clk_get(codec_dev[0], NULL); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8904")) { +- codec_dai_name[0] = "wm8904-hifi"; +- priv->codec_priv[0].mclk_id = WM8904_FLL_MCLK; +- priv->codec_priv[0].fll_id = WM8904_CLK_FLL; +- priv->codec_priv[0].pll_id = WM8904_FLL_MCLK; +- priv->codec_priv[0].pll_ratio_s24 = 192; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-spdif")) { +- ret = fsl_asoc_card_spdif_init(codec_np, cpu_np, codec_dai_name, priv); ++ } ++ if (pdata->capture_only) { ++ priv->dai_link[1].capture_only = 1; ++ priv->dai_link[2].capture_only = 1; ++ } ++ ++ priv->dai_fmt = pdata->dai_fmt; ++ ++ priv->card.dapm_routes = pdata->dapm_routes; ++ priv->card.num_dapm_routes = pdata->num_dapm_routes; ++ ++ if (pdata->probe_init) { ++ ret = pdata->probe_init(codec_np, cpu_np, ++ codec_dai_name, priv); + if (ret) + goto asrc_fail; +- } else { +- dev_err(&pdev->dev, "unknown Device Tree compatible\n"); +- ret = -EINVAL; +- goto asrc_fail; + } + + /* +@@ -995,51 +1256,21 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + of_node_put(bitclkprovider); + of_node_put(frameprovider); + +- if (!fsl_asoc_card_is_ac97(priv) && !codec_dev[0] +- && codec_dai_name[0] != snd_soc_dummy_dlc.dai_name) { +- dev_dbg(&pdev->dev, "failed to find codec device\n"); +- ret = -EPROBE_DEFER; +- goto asrc_fail; +- } +- +- /* Common settings for corresponding Freescale CPU DAI driver */ +- if (of_node_name_eq(cpu_np, "ssi")) { +- /* Only SSI needs to configure AUDMUX */ +- ret = fsl_asoc_card_audmux_init(np, priv); +- if (ret) { +- dev_err(&pdev->dev, "failed to init audmux\n"); +- goto asrc_fail; +- } +- } else if (of_node_name_eq(cpu_np, "esai")) { +- struct clk *esai_clk = clk_get(&cpu_pdev->dev, "extal"); +- +- if (!IS_ERR(esai_clk)) { +- priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(esai_clk); +- priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(esai_clk); +- clk_put(esai_clk); +- } else if (PTR_ERR(esai_clk) == -EPROBE_DEFER) { +- ret = -EPROBE_DEFER; +- goto asrc_fail; +- } +- +- priv->cpu_priv.sysclk_id[1] = ESAI_HCKT_EXTAL; +- priv->cpu_priv.sysclk_id[0] = ESAI_HCKR_EXTAL; +- } else if (of_node_name_eq(cpu_np, "sai")) { +- priv->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1; +- priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1; +- } +- + /* Initialize sound card */ + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) { +- snprintf(priv->name, sizeof(priv->name), "%s-audio", +- fsl_asoc_card_is_ac97(priv) ? "ac97" : codec_dev_name[0]); +- priv->card.name = priv->name; ++ /* ++ * "model" is required by the DT binding. Enforce it here so ++ * the driver fails with a clear message. ++ */ ++ dev_err(&pdev->dev, "Error parsing card name: %d\n", ret); ++ goto asrc_fail; + } + priv->card.dai_link = priv->dai_link; + priv->card.late_probe = fsl_asoc_card_late_probe; ++ priv->card.remove = fsl_asoc_card_card_remove; + priv->card.dapm_widgets = fsl_asoc_card_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(fsl_asoc_card_dapm_widgets); + +@@ -1118,7 +1349,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-format", &asrc_fmt); +- priv->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ priv->asrc_format = asrc_fmt; + if (ret) { + /* Fallback to old binding; translate to asrc_format */ + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", +@@ -1146,39 +1377,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + goto asrc_fail; + } + +- /* +- * Properties "hp-det-gpios" and "mic-det-gpios" are optional, and +- * simple_util_init_jack() uses these properties for creating +- * Headphone Jack and Microphone Jack. +- * +- * The notifier is initialized in snd_soc_card_jack_new(), then +- * snd_soc_jack_notifier_register can be called. +- */ +- if (of_property_present(np, "hp-det-gpios") || +- of_property_present(np, "hp-det-gpio") /* deprecated */) { +- ret = simple_util_init_jack(&priv->card, &priv->hp_jack, +- 1, NULL, "Headphone Jack"); +- if (ret) +- goto asrc_fail; +- +- snd_soc_jack_notifier_register(&priv->hp_jack.jack, &hp_jack_nb); +- } +- +- if (of_property_present(np, "mic-det-gpios") || +- of_property_present(np, "mic-det-gpio") /* deprecated */) { +- ret = simple_util_init_jack(&priv->card, &priv->mic_jack, +- 0, NULL, "Mic Jack"); +- if (ret) +- goto asrc_fail; +- +- snd_soc_jack_notifier_register(&priv->mic_jack.jack, &mic_jack_nb); +- } +- + asrc_fail: + of_node_put(asrc_np); + of_node_put(codec_np[0]); + of_node_put(codec_np[1]); +- put_device(&cpu_pdev->dev); + fail: + of_node_put(cpu_np); + +@@ -1186,21 +1388,21 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + } + + static const struct of_device_id fsl_asoc_card_dt_ids[] = { +- { .compatible = "fsl,imx-audio-ac97", }, +- { .compatible = "fsl,imx-audio-cs42888", }, +- { .compatible = "fsl,imx-audio-cs427x", }, +- { .compatible = "fsl,imx-audio-tlv320aic32x4", }, +- { .compatible = "fsl,imx-audio-tlv320aic31xx", }, +- { .compatible = "fsl,imx-audio-sgtl5000", }, +- { .compatible = "fsl,imx-audio-wm8962", }, +- { .compatible = "fsl,imx-audio-wm8960", }, +- { .compatible = "fsl,imx-audio-mqs", }, +- { .compatible = "fsl,imx-audio-wm8524", }, +- { .compatible = "fsl,imx-audio-si476x", }, +- { .compatible = "fsl,imx-audio-wm8958", }, +- { .compatible = "fsl,imx-audio-nau8822", }, +- { .compatible = "fsl,imx-audio-wm8904", }, +- { .compatible = "fsl,imx-audio-spdif", }, ++ { .compatible = "fsl,imx-audio-ac97", .data = &fsl_asoc_ac97_pdata }, ++ { .compatible = "fsl,imx-audio-cs42888", .data = &fsl_asoc_cs42888_pdata }, ++ { .compatible = "fsl,imx-audio-cs427x", .data = &fsl_asoc_cs427x_pdata }, ++ { .compatible = "fsl,imx-audio-tlv320aic32x4", .data = &fsl_asoc_tlv320aic32x4_pdata }, ++ { .compatible = "fsl,imx-audio-tlv320aic31xx", .data = &fsl_asoc_tlv320aic31xx_pdata }, ++ { .compatible = "fsl,imx-audio-sgtl5000", .data = &fsl_asoc_sgtl5000_pdata }, ++ { .compatible = "fsl,imx-audio-wm8962", .data = &fsl_asoc_wm8962_pdata }, ++ { .compatible = "fsl,imx-audio-wm8960", .data = &fsl_asoc_wm8960_pdata }, ++ { .compatible = "fsl,imx-audio-mqs", .data = &fsl_asoc_mqs_pdata }, ++ { .compatible = "fsl,imx-audio-wm8524", .data = &fsl_asoc_wm8524_pdata }, ++ { .compatible = "fsl,imx-audio-si476x", .data = &fsl_asoc_si476x_pdata }, ++ { .compatible = "fsl,imx-audio-wm8958", .data = &fsl_asoc_wm8958_pdata }, ++ { .compatible = "fsl,imx-audio-nau8822", .data = &fsl_asoc_nau8822_pdata }, ++ { .compatible = "fsl,imx-audio-wm8904", .data = &fsl_asoc_wm8904_pdata }, ++ { .compatible = "fsl,imx-audio-spdif", .data = &fsl_asoc_spdif_pdata }, + {} + }; + MODULE_DEVICE_TABLE(of, fsl_asoc_card_dt_ids); +diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c +--- a/sound/soc/fsl/fsl_asrc.c ++++ b/sound/soc/fsl/fsl_asrc.c +@@ -222,10 +222,9 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) + enum asrc_pair_index index = ASRC_INVALID_PAIR; + struct fsl_asrc *asrc = pair->asrc; + struct device *dev = &asrc->pdev->dev; +- unsigned long lock_flags; + int i, ret = 0; + +- spin_lock_irqsave(&asrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&asrc->lock); + + for (i = ASRC_PAIR_A; i < ASRC_PAIR_MAX_NUM; i++) { + if (asrc->pair[i] != NULL) +@@ -250,8 +249,6 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) + pair->index = index; + } + +- spin_unlock_irqrestore(&asrc->lock, lock_flags); +- + return ret; + } + +@@ -265,19 +262,16 @@ static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair) + { + struct fsl_asrc *asrc = pair->asrc; + enum asrc_pair_index index = pair->index; +- unsigned long lock_flags; + + /* Make sure the pair is disabled */ + regmap_update_bits(asrc->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_MASK(index), 0); + +- spin_lock_irqsave(&asrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&asrc->lock); + + asrc->channel_avail += pair->channels; + asrc->pair[index] = NULL; + pair->error = 0; +- +- spin_unlock_irqrestore(&asrc->lock, lock_flags); + } + + /** +@@ -1358,7 +1352,7 @@ static int fsl_asrc_probe(struct platform_device *pdev) + } + + ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt); +- asrc->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ asrc->asrc_format = asrc_fmt; + if (ret) { + ret = of_property_read_u32(np, "fsl,asrc-width", &width); + if (ret) { +diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h +--- a/sound/soc/fsl/fsl_asrc.h ++++ b/sound/soc/fsl/fsl_asrc.h +@@ -444,7 +444,7 @@ struct dma_block { + }; + + /** +- * fsl_asrc_soc_data: soc specific data ++ * struct fsl_asrc_soc_data - soc specific data + * + * @use_edma: using edma as dma device or not + * @channel_bits: width of ASRCNCR register for each pair +@@ -457,7 +457,7 @@ struct fsl_asrc_soc_data { + }; + + /** +- * fsl_asrc_pair_priv: ASRC Pair private data ++ * struct fsl_asrc_pair_priv - ASRC Pair private data + * + * @config: configuration profile + */ +@@ -466,7 +466,7 @@ struct fsl_asrc_pair_priv { + }; + + /** +- * fsl_asrc_priv: ASRC private data ++ * struct fsl_asrc_priv - ASRC private data + * + * @asrck_clk: clock sources to driver ASRC internal logic + * @soc: soc specific data +diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c +--- a/sound/soc/fsl/fsl_asrc_dma.c ++++ b/sound/soc/fsl/fsl_asrc_dma.c +@@ -219,7 +219,9 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, + */ + component_be = snd_soc_lookup_component_nolocked(dev_be, SND_DMAENGINE_PCM_DRV_NAME); + if (component_be) { +- be_chan = soc_component_to_pcm(component_be)->chan[substream->stream]; ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component_be); ++ ++ be_chan = pcm->chan[substream->stream]; + tmp_chan = be_chan; + } + if (!tmp_chan) { +diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c +--- a/sound/soc/fsl/fsl_asrc_m2m.c ++++ b/sound/soc/fsl/fsl_asrc_m2m.c +@@ -367,13 +367,13 @@ static int fsl_asrc_m2m_comp_set_params(struct snd_compr_stream *stream, + if (ret) + return -EINVAL; + +- if (pcm_format_to_bits((__force snd_pcm_format_t)params->codec.format) & cap.fmt_in) +- pair->sample_format[IN] = (__force snd_pcm_format_t)params->codec.format; ++ if (pcm_format_to_bits(params->codec.format) & cap.fmt_in) ++ pair->sample_format[IN] = params->codec.format; + else + return -EINVAL; + +- if (pcm_format_to_bits((__force snd_pcm_format_t)params->codec.pcm_format) & cap.fmt_out) +- pair->sample_format[OUT] = (__force snd_pcm_format_t)params->codec.pcm_format; ++ if (pcm_format_to_bits(params->codec.pcm_format) & cap.fmt_out) ++ pair->sample_format[OUT] = params->codec.pcm_format; + else + return -EINVAL; + +@@ -600,7 +600,7 @@ static int fsl_asrc_m2m_fill_codec_caps(struct fsl_asrc *asrc, + cap.rate_in, + cap.rate_in_count * sizeof(__u32)); + codec->descriptor[j].num_sample_rates = cap.rate_in_count; +- codec->descriptor[j].formats = (__force __u32)k; ++ codec->descriptor[j].formats = k; + codec->descriptor[j].pcm_formats = cap.fmt_out; + codec->descriptor[j].src.out_sample_rate_min = cap.rate_out[0]; + codec->descriptor[j].src.out_sample_rate_max = +diff --git a/sound/soc/fsl/fsl_audmix.c b/sound/soc/fsl/fsl_audmix.c +--- a/sound/soc/fsl/fsl_audmix.c ++++ b/sound/soc/fsl/fsl_audmix.c +@@ -286,7 +286,6 @@ static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) + { + struct fsl_audmix *priv = snd_soc_dai_get_drvdata(dai); +- unsigned long lock_flags; + + /* Capture stream shall not be handled */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) +@@ -296,16 +295,14 @@ static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int cmd, + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +- spin_lock_irqsave(&priv->lock, lock_flags); +- priv->tdms |= BIT(dai->driver->id); +- spin_unlock_irqrestore(&priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &priv->lock) ++ priv->tdms |= BIT(dai->driver->id); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +- spin_lock_irqsave(&priv->lock, lock_flags); +- priv->tdms &= ~BIT(dai->driver->id); +- spin_unlock_irqrestore(&priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &priv->lock) ++ priv->tdms &= ~BIT(dai->driver->id); + break; + default: + return -EINVAL; +@@ -457,6 +454,9 @@ static const struct of_device_id fsl_audmix_ids[] = { + }; + MODULE_DEVICE_TABLE(of, fsl_audmix_ids); + ++static int fsl_audmix_runtime_resume(struct device *dev); ++static int fsl_audmix_runtime_suspend(struct device *dev); ++ + static int fsl_audmix_probe(struct platform_device *pdev) + { + struct device *dev = &pdev->dev; +@@ -488,13 +488,25 @@ static int fsl_audmix_probe(struct platform_device *pdev) + spin_lock_init(&priv->lock); + platform_set_drvdata(pdev, priv); + pm_runtime_enable(dev); ++ if (!pm_runtime_enabled(dev)) { ++ ret = fsl_audmix_runtime_resume(dev); ++ if (ret) ++ goto err_disable_pm; ++ } ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) ++ goto err_pm_get_sync; ++ ++ /* To enable regmap cache only when runtime PM enabled */ ++ pm_runtime_put(dev); + + ret = devm_snd_soc_register_component(dev, &fsl_audmix_component, + fsl_audmix_dai, + ARRAY_SIZE(fsl_audmix_dai)); + if (ret) { + dev_err(dev, "failed to register ASoC DAI\n"); +- goto err_disable_pm; ++ goto err_pm_get_sync; + } + + /* +@@ -506,12 +518,15 @@ static int fsl_audmix_probe(struct platform_device *pdev) + if (IS_ERR(priv->pdev)) { + ret = PTR_ERR(priv->pdev); + dev_err(dev, "failed to register platform: %d\n", ret); +- goto err_disable_pm; ++ goto err_pm_get_sync; + } + } + + return 0; + ++err_pm_get_sync: ++ if (!pm_runtime_status_suspended(dev)) ++ fsl_audmix_runtime_suspend(dev); + err_disable_pm: + pm_runtime_disable(dev); + return ret; +@@ -522,6 +537,8 @@ static void fsl_audmix_remove(struct platform_device *pdev) + struct fsl_audmix *priv = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); ++ if (!pm_runtime_status_suspended(&pdev->dev)) ++ fsl_audmix_runtime_suspend(&pdev->dev); + + if (priv->pdev) + platform_device_unregister(priv->pdev); +diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c +--- a/sound/soc/fsl/fsl_dma.c ++++ b/sound/soc/fsl/fsl_dma.c +@@ -18,8 +18,6 @@ + #include + #include + #include +-#include +-#include + #include + #include + +@@ -824,9 +822,34 @@ static int fsl_soc_dma_probe(struct platform_device *pdev) + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np; + struct resource res; ++ void __iomem *channel; + const uint32_t *iprop; ++ int irq; + int ret; + ++ channel = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(channel)) ++ return PTR_ERR(channel); ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return irq; ++ ++ dma = devm_kzalloc(&pdev->dev, sizeof(*dma), GFP_KERNEL); ++ if (!dma) ++ return -ENOMEM; ++ ++ dma->dai.name = DRV_NAME; ++ dma->dai.open = fsl_dma_open; ++ dma->dai.close = fsl_dma_close; ++ dma->dai.hw_params = fsl_dma_hw_params; ++ dma->dai.hw_free = fsl_dma_hw_free; ++ dma->dai.pointer = fsl_dma_pointer; ++ dma->dai.pcm_new = fsl_dma_new; ++ ++ dma->channel = channel; ++ dma->irq = irq; ++ + /* Find the SSI node that points to us. */ + ssi_np = find_ssi_node(np); + if (!ssi_np) { +@@ -842,55 +865,19 @@ static int fsl_soc_dma_probe(struct platform_device *pdev) + return ret; + } + +- dma = kzalloc_obj(*dma); +- if (!dma) { +- of_node_put(ssi_np); +- return -ENOMEM; +- } +- +- dma->dai.name = DRV_NAME; +- dma->dai.open = fsl_dma_open; +- dma->dai.close = fsl_dma_close; +- dma->dai.hw_params = fsl_dma_hw_params; +- dma->dai.hw_free = fsl_dma_hw_free; +- dma->dai.pointer = fsl_dma_pointer; +- dma->dai.pcm_new = fsl_dma_new; +- + /* Store the SSI-specific information that we need */ + dma->ssi_stx_phys = res.start + REG_SSI_STX0; + dma->ssi_srx_phys = res.start + REG_SSI_SRX0; + + iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL); ++ of_node_put(ssi_np); + if (iprop) + dma->ssi_fifo_depth = be32_to_cpup(iprop); + else + /* Older 8610 DTs didn't have the fifo-depth property */ + dma->ssi_fifo_depth = 8; + +- of_node_put(ssi_np); +- +- ret = devm_snd_soc_register_component(&pdev->dev, &dma->dai, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "could not register platform\n"); +- kfree(dma); +- return ret; +- } +- +- dma->channel = of_iomap(np, 0); +- dma->irq = irq_of_parse_and_map(np, 0); +- +- dev_set_drvdata(&pdev->dev, dma); +- +- return 0; +-} +- +-static void fsl_soc_dma_remove(struct platform_device *pdev) +-{ +- struct dma_object *dma = dev_get_drvdata(&pdev->dev); +- +- iounmap(dma->channel); +- irq_dispose_mapping(dma->irq); +- kfree(dma); ++ return devm_snd_soc_register_component(&pdev->dev, &dma->dai, NULL, 0); + } + + static const struct of_device_id fsl_soc_dma_ids[] = { +@@ -905,7 +892,6 @@ static struct platform_driver fsl_soc_dma_driver = { + .of_match_table = fsl_soc_dma_ids, + }, + .probe = fsl_soc_dma_probe, +- .remove = fsl_soc_dma_remove, + }; + + module_platform_driver(fsl_soc_dma_driver); +diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h +--- a/sound/soc/fsl/fsl_dma.h ++++ b/sound/soc/fsl/fsl_dma.h +@@ -92,7 +92,7 @@ static inline u32 CCSR_DMA_ECLNDAR_ADDR(u64 x) + #define CCSR_DMA_ATR_SNOOP 0x00050000 + #define CCSR_DMA_ATR_ESAD_MASK 0x0000000F + +-/** ++/* + * List Descriptor for extended chaining mode DMA operations. + * + * The CLSDAR register points to the first (in a linked-list) List +@@ -107,7 +107,7 @@ struct fsl_dma_list_descriptor { + u8 res[8]; /* Reserved */ + } __attribute__ ((aligned(32), packed)); + +-/** ++/* + * Link Descriptor for basic and extended chaining mode DMA operations. + * + * A Link Descriptor points to a single DMA buffer. Each link descriptor +diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c +--- a/sound/soc/fsl/fsl_easrc.c ++++ b/sound/soc/fsl/fsl_easrc.c +@@ -1025,7 +1025,6 @@ static int fsl_easrc_config_context(struct fsl_asrc *easrc, unsigned int ctx_id) + struct fsl_easrc_ctx_priv *ctx_priv; + struct fsl_asrc_pair *ctx; + struct device *dev; +- unsigned long lock_flags; + int ret; + + if (!easrc) +@@ -1053,9 +1052,8 @@ static int fsl_easrc_config_context(struct fsl_asrc *easrc, unsigned int ctx_id) + if (ret) + return ret; + +- spin_lock_irqsave(&easrc->lock, lock_flags); +- ret = fsl_easrc_config_slot(easrc, ctx->index); +- spin_unlock_irqrestore(&easrc->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &easrc->lock) ++ ret = fsl_easrc_config_slot(easrc, ctx->index); + if (ret) + return ret; + +@@ -1301,13 +1299,12 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) + enum asrc_pair_index index = ASRC_INVALID_PAIR; + struct fsl_asrc *easrc = ctx->asrc; + struct device *dev; +- unsigned long lock_flags; + int ret = 0; + int i; + + dev = &easrc->pdev->dev; + +- spin_lock_irqsave(&easrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&easrc->lock); + + for (i = ASRC_PAIR_A; i < EASRC_CTX_MAX_NUM; i++) { + if (easrc->pair[i]) +@@ -1331,8 +1328,6 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) + easrc->channel_avail -= channels; + } + +- spin_unlock_irqrestore(&easrc->lock, lock_flags); +- + return ret; + } + +@@ -1343,7 +1338,6 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) + */ + static void fsl_easrc_release_context(struct fsl_asrc_pair *ctx) + { +- unsigned long lock_flags; + struct fsl_asrc *easrc; + + if (!ctx) +@@ -1351,14 +1345,12 @@ static void fsl_easrc_release_context(struct fsl_asrc_pair *ctx) + + easrc = ctx->asrc; + +- spin_lock_irqsave(&easrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&easrc->lock); + + fsl_easrc_release_slot(easrc, ctx->index); + + easrc->channel_avail += ctx->channels; + easrc->pair[ctx->index] = NULL; +- +- spin_unlock_irqrestore(&easrc->lock, lock_flags); + } + + /* +@@ -2227,7 +2219,7 @@ static int fsl_easrc_probe(struct platform_device *pdev) + } + + ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt); +- easrc->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ easrc->asrc_format = asrc_fmt; + if (ret) { + dev_err(dev, "failed to asrc format\n"); + return ret; +@@ -2292,15 +2284,13 @@ static int fsl_easrc_runtime_suspend(struct device *dev) + { + struct fsl_asrc *easrc = dev_get_drvdata(dev); + struct fsl_easrc_priv *easrc_priv = easrc->private; +- unsigned long lock_flags; + + regcache_cache_only(easrc->regmap, true); + + clk_disable_unprepare(easrc->mem_clk); + +- spin_lock_irqsave(&easrc->lock, lock_flags); +- easrc_priv->firmware_loaded = 0; +- spin_unlock_irqrestore(&easrc->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &easrc->lock) ++ easrc_priv->firmware_loaded = 0; + + return 0; + } +@@ -2311,7 +2301,6 @@ static int fsl_easrc_runtime_resume(struct device *dev) + struct fsl_easrc_priv *easrc_priv = easrc->private; + struct fsl_easrc_ctx_priv *ctx_priv; + struct fsl_asrc_pair *ctx; +- unsigned long lock_flags; + int ret; + int i; + +@@ -2323,13 +2312,11 @@ static int fsl_easrc_runtime_resume(struct device *dev) + regcache_mark_dirty(easrc->regmap); + regcache_sync(easrc->regmap); + +- spin_lock_irqsave(&easrc->lock, lock_flags); +- if (easrc_priv->firmware_loaded) { +- spin_unlock_irqrestore(&easrc->lock, lock_flags); +- goto skip_load; ++ scoped_guard(spinlock_irqsave, &easrc->lock) { ++ if (easrc_priv->firmware_loaded) ++ return 0; ++ easrc_priv->firmware_loaded = 1; + } +- easrc_priv->firmware_loaded = 1; +- spin_unlock_irqrestore(&easrc->lock, lock_flags); + + ret = fsl_easrc_get_firmware(easrc); + if (ret) { +@@ -2377,9 +2364,7 @@ static int fsl_easrc_runtime_resume(struct device *dev) + goto disable_mem_clk; + } + +-skip_load: + return 0; +- + disable_mem_clk: + clk_disable_unprepare(easrc->mem_clk); + return ret; +diff --git a/sound/soc/fsl/fsl_easrc.h b/sound/soc/fsl/fsl_easrc.h +--- a/sound/soc/fsl/fsl_easrc.h ++++ b/sound/soc/fsl/fsl_easrc.h +@@ -584,7 +584,7 @@ struct fsl_easrc_slot { + }; + + /** +- * fsl_easrc_ctx_priv: EASRC context private data ++ * struct fsl_easrc_ctx_priv - EASRC context private data + * + * @in_params: input parameter + * @out_params: output parameter +@@ -625,7 +625,7 @@ struct fsl_easrc_ctx_priv { + }; + + /** +- * fsl_easrc_priv: EASRC private data ++ * struct fsl_easrc_priv - EASRC private data + * + * @slot: slot setting + * @firmware_hdr: the header of firmware +diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c +--- a/sound/soc/fsl/fsl_esai.c ++++ b/sound/soc/fsl/fsl_esai.c +@@ -709,10 +709,9 @@ static void fsl_esai_hw_reset(struct work_struct *work) + { + struct fsl_esai *esai_priv = container_of(work, struct fsl_esai, work); + bool tx = true, rx = false, enabled[2]; +- unsigned long lock_flags; + u32 tfcr, rfcr; + +- spin_lock_irqsave(&esai_priv->lock, lock_flags); ++ guard(spinlock_irqsave)(&esai_priv->lock); + /* Save the registers */ + regmap_read(esai_priv->regmap, REG_ESAI_TFCR, &tfcr); + regmap_read(esai_priv->regmap, REG_ESAI_RFCR, &rfcr); +@@ -750,8 +749,6 @@ static void fsl_esai_hw_reset(struct work_struct *work) + fsl_esai_trigger_start(esai_priv, tx); + if (enabled[rx]) + fsl_esai_trigger_start(esai_priv, rx); +- +- spin_unlock_irqrestore(&esai_priv->lock, lock_flags); + } + + static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, +@@ -759,7 +756,6 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, + { + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; +- unsigned long lock_flags; + + esai_priv->channels[tx] = substream->runtime->channels; + +@@ -767,16 +763,14 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +- spin_lock_irqsave(&esai_priv->lock, lock_flags); +- fsl_esai_trigger_start(esai_priv, tx); +- spin_unlock_irqrestore(&esai_priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &esai_priv->lock) ++ fsl_esai_trigger_start(esai_priv, tx); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +- spin_lock_irqsave(&esai_priv->lock, lock_flags); +- fsl_esai_trigger_stop(esai_priv, tx); +- spin_unlock_irqrestore(&esai_priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &esai_priv->lock) ++ fsl_esai_trigger_stop(esai_priv, tx); + break; + default: + return -EINVAL; +diff --git a/sound/soc/fsl/fsl_qmc_audio.c b/sound/soc/fsl/fsl_qmc_audio.c +--- a/sound/soc/fsl/fsl_qmc_audio.c ++++ b/sound/soc/fsl/fsl_qmc_audio.c +@@ -503,8 +503,8 @@ static int qmc_dai_constraints_interleaved(struct snd_pcm_substream *substream, + return ret; + } + +- access = 1ULL << (__force int)SNDRV_PCM_ACCESS_MMAP_INTERLEAVED | +- 1ULL << (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED; ++ access = 1ULL << SNDRV_PCM_ACCESS_MMAP_INTERLEAVED | ++ 1ULL << SNDRV_PCM_ACCESS_RW_INTERLEAVED; + ret = snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_ACCESS, + access); + if (ret) { +@@ -532,8 +532,8 @@ static int qmc_dai_constraints_noninterleaved(struct snd_pcm_substream *substrea + return ret; + } + +- access = 1ULL << (__force int)SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED | +- 1ULL << (__force int)SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; ++ access = 1ULL << SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED | ++ 1ULL << SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; + ret = snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_ACCESS, + access); + if (ret) { +diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c +--- a/sound/soc/fsl/fsl_spdif.c ++++ b/sound/soc/fsl/fsl_spdif.c +@@ -853,17 +853,15 @@ static int fsl_spdif_subcode_get(struct snd_kcontrol *kcontrol, + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; +- unsigned long flags; + int ret = -EAGAIN; + +- spin_lock_irqsave(&ctrl->ctl_lock, flags); ++ guard(spinlock_irqsave)(&ctrl->ctl_lock); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_UBITS_SIZE; + memcpy(&ucontrol->value.iec958.subcode[0], + &ctrl->subcode[idx], SPDIF_UBITS_SIZE); + ret = 0; + } +- spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; + } +@@ -885,17 +883,15 @@ static int fsl_spdif_qget(struct snd_kcontrol *kcontrol, + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; +- unsigned long flags; + int ret = -EAGAIN; + +- spin_lock_irqsave(&ctrl->ctl_lock, flags); ++ guard(spinlock_irqsave)(&ctrl->ctl_lock); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_QSUB_SIZE; + memcpy(&ucontrol->value.bytes.data[0], + &ctrl->qsub[idx], SPDIF_QSUB_SIZE); + ret = 0; + } +- spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; + } +diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c +--- a/sound/soc/fsl/fsl_ssi.c ++++ b/sound/soc/fsl/fsl_ssi.c +@@ -1218,13 +1218,13 @@ static void fsl_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + if (reg > 0x7f) + return; + +- mutex_lock(&fsl_ac97_data->ac97_reg_lock); ++ guard(mutex)(&fsl_ac97_data->ac97_reg_lock); + + ret = clk_prepare_enable(fsl_ac97_data->clk); + if (ret) { + pr_err("ac97 write clk_prepare_enable failed: %d\n", + ret); +- goto ret_unlock; ++ return; + } + + lreg = reg << 12; +@@ -1238,9 +1238,6 @@ static void fsl_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + udelay(100); + + clk_disable_unprepare(fsl_ac97_data->clk); +- +-ret_unlock: +- mutex_unlock(&fsl_ac97_data->ac97_reg_lock); + } + + static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, +@@ -1252,12 +1249,12 @@ static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, + unsigned int lreg; + int ret; + +- mutex_lock(&fsl_ac97_data->ac97_reg_lock); ++ guard(mutex)(&fsl_ac97_data->ac97_reg_lock); + + ret = clk_prepare_enable(fsl_ac97_data->clk); + if (ret) { + pr_err("ac97 read clk_prepare_enable failed: %d\n", ret); +- goto ret_unlock; ++ return val; + } + + lreg = (reg & 0x7f) << 12; +@@ -1272,8 +1269,6 @@ static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, + + clk_disable_unprepare(fsl_ac97_data->clk); + +-ret_unlock: +- mutex_unlock(&fsl_ac97_data->ac97_reg_lock); + return val; + } + +diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c +--- a/sound/soc/fsl/fsl_xcvr.c ++++ b/sound/soc/fsl/fsl_xcvr.c +@@ -797,10 +797,9 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + { + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; +- unsigned long lock_flags; + int ret = 0; + +- spin_lock_irqsave(&xcvr->lock, lock_flags); ++ guard(spinlock_irqsave)(&xcvr->lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: +@@ -812,7 +811,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_EXT_CTRL_DPTH_RESET(tx)); + if (ret < 0) { + dev_err(dai->dev, "Failed to set DPATH RESET: %d\n", ret); +- goto release_lock; ++ return ret; + } + + if (tx) { +@@ -824,7 +823,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_ISR_CMDC_TX_EN); + if (ret < 0) { + dev_err(dai->dev, "err updating isr %d\n", ret); +- goto release_lock; ++ return ret; + } + fallthrough; + case FSL_XCVR_MODE_SPDIF: +@@ -833,7 +832,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX); + if (ret < 0) { + dev_err(dai->dev, "Failed to start DATA_TX: %d\n", ret); +- goto release_lock; ++ return ret; + } + break; + } +@@ -844,14 +843,14 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_EXT_CTRL_DMA_DIS(tx), 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to enable DMA: %d\n", ret); +- goto release_lock; ++ return ret; + } + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0, + FSL_XCVR_IRQ_EARC_ALL, FSL_XCVR_IRQ_EARC_ALL); + if (ret < 0) { + dev_err(dai->dev, "Error while setting IER0: %d\n", ret); +- goto release_lock; ++ return ret; + } + + /* clear DPATH RESET */ +@@ -860,7 +859,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to clear DPATH RESET: %d\n", ret); +- goto release_lock; ++ return ret; + } + + break; +@@ -873,14 +872,14 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_EXT_CTRL_DMA_DIS(tx)); + if (ret < 0) { + dev_err(dai->dev, "Failed to disable DMA: %d\n", ret); +- goto release_lock; ++ return ret; + } + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0, + FSL_XCVR_IRQ_EARC_ALL, 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to clear IER0: %d\n", ret); +- goto release_lock; ++ return ret; + } + + if (tx) { +@@ -891,7 +890,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX); + if (ret < 0) { + dev_err(dai->dev, "Failed to stop DATA_TX: %d\n", ret); +- goto release_lock; ++ return ret; + } + if (xcvr->soc_data->spdif_only) + break; +@@ -905,7 +904,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + if (ret < 0) { + dev_err(dai->dev, + "Err updating ISR %d\n", ret); +- goto release_lock; ++ return ret; + } + break; + } +@@ -916,18 +915,16 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + break; + } + +-release_lock: +- spin_unlock_irqrestore(&xcvr->lock, lock_flags); + return ret; + } + + static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + { + struct device *dev = &xcvr->pdev->dev; +- const struct firmware *fw; + int ret = 0, rem, off, out, page = 0, size = FSL_XCVR_REG_OFFSET; + u32 mask, val; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, xcvr->soc_data->fw_name, dev); + if (ret) { + dev_err(dev, "failed to request firmware.\n"); +@@ -939,7 +936,6 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + /* RAM is 20KiB = 16KiB code + 4KiB data => max 10 pages 2KiB each */ + if (rem > 16384) { + dev_err(dev, "FW size %d is bigger than 16KiB.\n", rem); +- release_firmware(fw); + return -ENOMEM; + } + +@@ -950,7 +946,7 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + if (ret < 0) { + dev_err(dev, "FW: failed to set page %d, err=%d\n", + page, ret); +- goto err_firmware; ++ return ret; + } + + off = page * size; +@@ -971,11 +967,6 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + } + } + +-err_firmware: +- release_firmware(fw); +- if (ret < 0) +- return ret; +- + /* configure watermarks */ + mask = FSL_XCVR_EXT_CTRL_RX_FWM_MASK | FSL_XCVR_EXT_CTRL_TX_FWM_MASK; + val = FSL_XCVR_EXT_CTRL_RX_FWM(FSL_XCVR_FIFO_WMK_RX); +@@ -1448,11 +1439,10 @@ static void reset_rx_work(struct work_struct *work) + { + struct fsl_xcvr *xcvr = container_of(work, struct fsl_xcvr, work_rst); + struct device *dev = &xcvr->pdev->dev; +- unsigned long lock_flags; + u32 ext_ctrl; + + dev_dbg(dev, "reset rx path\n"); +- spin_lock_irqsave(&xcvr->lock, lock_flags); ++ guard(spinlock_irqsave)(&xcvr->lock); + regmap_read(xcvr->regmap, FSL_XCVR_EXT_CTRL, &ext_ctrl); + + if (!(ext_ctrl & FSL_XCVR_EXT_CTRL_DMA_RD_DIS)) { +@@ -1469,7 +1459,6 @@ static void reset_rx_work(struct work_struct *work) + FSL_XCVR_EXT_CTRL_RX_DPTH_RESET, + 0); + } +- spin_unlock_irqrestore(&xcvr->lock, lock_flags); + } + + static irqreturn_t irq0_isr(int irq, void *devid) +diff --git a/sound/soc/fsl/imx-audio-rpmsg.c b/sound/soc/fsl/imx-audio-rpmsg.c +--- a/sound/soc/fsl/imx-audio-rpmsg.c ++++ b/sound/soc/fsl/imx-audio-rpmsg.c +@@ -22,7 +22,6 @@ static int imx_audio_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + struct rpmsg_r_msg *r_msg = (struct rpmsg_r_msg *)data; + struct rpmsg_info *info; + struct rpmsg_msg *msg; +- unsigned long flags; + + if (!rpmsg->rpmsg_pdev) + return 0; +@@ -37,21 +36,21 @@ static int imx_audio_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + /* TYPE C is notification from M core */ + switch (r_msg->header.cmd) { + case TX_PERIOD_DONE: +- spin_lock_irqsave(&info->lock[TX], flags); +- msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; +- msg->r_msg.param.buffer_tail = +- r_msg->param.buffer_tail; +- msg->r_msg.param.buffer_tail %= info->num_period[TX]; +- spin_unlock_irqrestore(&info->lock[TX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[TX]) { ++ msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; ++ msg->r_msg.param.buffer_tail = ++ r_msg->param.buffer_tail; ++ msg->r_msg.param.buffer_tail %= info->num_period[TX]; ++ } + info->callback[TX](info->callback_param[TX]); + break; + case RX_PERIOD_DONE: +- spin_lock_irqsave(&info->lock[RX], flags); +- msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; +- msg->r_msg.param.buffer_tail = +- r_msg->param.buffer_tail; +- msg->r_msg.param.buffer_tail %= info->num_period[1]; +- spin_unlock_irqrestore(&info->lock[RX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[RX]) { ++ msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; ++ msg->r_msg.param.buffer_tail = ++ r_msg->param.buffer_tail; ++ msg->r_msg.param.buffer_tail %= info->num_period[1]; ++ } + info->callback[RX](info->callback_param[RX]); + break; + default: +diff --git a/sound/soc/fsl/imx-card.c b/sound/soc/fsl/imx-card.c +--- a/sound/soc/fsl/imx-card.c ++++ b/sound/soc/fsl/imx-card.c +@@ -531,7 +531,7 @@ static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); +- snd_mask_set(mask, (__force unsigned int)data->asrc_format); ++ snd_mask_set(mask, data->asrc_format); + + return 0; + } +@@ -684,7 +684,7 @@ static int imx_card_parse_of(struct imx_card_data *data) + } + + ret = of_property_read_u32(args.np, "fsl,asrc-format", &asrc_fmt); +- data->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ data->asrc_format = asrc_fmt; + if (ret) { + /* Fallback to old binding; translate to asrc_format */ + ret = of_property_read_u32(args.np, "fsl,asrc-width", &width); +diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c +--- a/sound/soc/fsl/imx-pcm-rpmsg.c ++++ b/sound/soc/fsl/imx-pcm-rpmsg.c +@@ -39,10 +39,9 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + struct rpmsg_device *rpdev = info->rpdev; + int ret = 0; + +- mutex_lock(&info->msg_lock); ++ guard(mutex)(&info->msg_lock); + if (!rpdev) { + dev_err(info->dev, "rpmsg channel not ready\n"); +- mutex_unlock(&info->msg_lock); + return -EINVAL; + } + +@@ -55,15 +54,12 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + sizeof(struct rpmsg_s_msg)); + if (ret) { + dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret); +- mutex_unlock(&info->msg_lock); + return ret; + } + + /* No receive msg for TYPE_C command */ +- if (msg->s_msg.header.type == MSG_TYPE_C) { +- mutex_unlock(&info->msg_lock); ++ if (msg->s_msg.header.type == MSG_TYPE_C) + return 0; +- } + + /* wait response from rpmsg */ + ret = wait_for_completion_timeout(&info->cmd_complete, +@@ -71,7 +67,6 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + if (!ret) { + dev_err(&rpdev->dev, "rpmsg_send cmd %d timeout!\n", + msg->s_msg.header.cmd); +- mutex_unlock(&info->msg_lock); + return -ETIMEDOUT; + } + +@@ -100,8 +95,6 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + dev_dbg(&rpdev->dev, "cmd:%d, resp %d\n", msg->s_msg.header.cmd, + info->r_msg.param.resp); + +- mutex_unlock(&info->msg_lock); +- + return 0; + } + +@@ -109,14 +102,13 @@ static int imx_rpmsg_insert_workqueue(struct snd_pcm_substream *substream, + struct rpmsg_msg *msg, + struct rpmsg_info *info) + { +- unsigned long flags; + int ret = 0; + + /* + * Queue the work to workqueue. + * If the queue is full, drop the message. + */ +- spin_lock_irqsave(&info->wq_lock, flags); ++ guard(spinlock_irqsave)(&info->wq_lock); + if (info->work_write_index != info->work_read_index) { + int index = info->work_write_index; + +@@ -130,7 +122,6 @@ static int imx_rpmsg_insert_workqueue(struct snd_pcm_substream *substream, + info->msg_drop_count[substream->stream]++; + ret = -EPIPE; + } +- spin_unlock_irqrestore(&info->wq_lock, flags); + + return ret; + } +@@ -523,7 +514,6 @@ static int imx_rpmsg_pcm_ack(struct snd_soc_component *component, + snd_pcm_sframes_t avail; + struct timer_list *timer; + struct rpmsg_msg *msg; +- unsigned long flags; + int buffer_tail = 0; + int written_num; + +@@ -553,11 +543,11 @@ static int imx_rpmsg_pcm_ack(struct snd_soc_component *component, + msg->s_msg.param.buffer_tail = buffer_tail; + + /* The notification message is updated to latest */ +- spin_lock_irqsave(&info->lock[substream->stream], flags); +- memcpy(&info->notify[substream->stream], msg, +- sizeof(struct rpmsg_s_msg)); +- info->notify_updated[substream->stream] = true; +- spin_unlock_irqrestore(&info->lock[substream->stream], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[substream->stream]) { ++ memcpy(&info->notify[substream->stream], msg, ++ sizeof(struct rpmsg_s_msg)); ++ info->notify_updated[substream->stream] = true; ++ } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + avail = snd_pcm_playback_hw_avail(runtime); +@@ -641,7 +631,7 @@ static void imx_rpmsg_pcm_work(struct work_struct *work) + bool is_notification = false; + struct rpmsg_info *info; + struct rpmsg_msg msg; +- unsigned long flags; ++ bool updated; + + work_of_rpmsg = container_of(work, struct work_of_rpmsg, work); + info = work_of_rpmsg->info; +@@ -652,25 +642,26 @@ static void imx_rpmsg_pcm_work(struct work_struct *work) + * enough data in M core side, need to let M core know + * data is updated immediately. + */ +- spin_lock_irqsave(&info->lock[TX], flags); +- if (info->notify_updated[TX]) { +- memcpy(&msg, &info->notify[TX], sizeof(struct rpmsg_s_msg)); +- info->notify_updated[TX] = false; +- spin_unlock_irqrestore(&info->lock[TX], flags); +- info->send_message(&msg, info); +- } else { +- spin_unlock_irqrestore(&info->lock[TX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[TX]) { ++ updated = info->notify_updated[TX]; ++ if (updated) { ++ memcpy(&msg, &info->notify[TX], sizeof(struct rpmsg_s_msg)); ++ info->notify_updated[TX] = false; ++ } + } ++ if (updated) ++ info->send_message(&msg, info); + +- spin_lock_irqsave(&info->lock[RX], flags); +- if (info->notify_updated[RX]) { +- memcpy(&msg, &info->notify[RX], sizeof(struct rpmsg_s_msg)); +- info->notify_updated[RX] = false; +- spin_unlock_irqrestore(&info->lock[RX], flags); +- info->send_message(&msg, info); +- } else { +- spin_unlock_irqrestore(&info->lock[RX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[RX]) { ++ updated = info->notify_updated[RX]; ++ if (updated) { ++ memcpy(&msg, &info->notify[RX], sizeof(struct rpmsg_s_msg)); ++ info->notify_updated[RX] = false; ++ } + } ++ if (updated) ++ info->send_message(&msg, info); ++ + + /* Skip the notification message for it has been processed above */ + if (work_of_rpmsg->msg.s_msg.header.type == MSG_TYPE_C && +@@ -682,10 +673,10 @@ static void imx_rpmsg_pcm_work(struct work_struct *work) + info->send_message(&work_of_rpmsg->msg, info); + + /* update read index */ +- spin_lock_irqsave(&info->wq_lock, flags); +- info->work_read_index++; +- info->work_read_index %= WORK_MAX_NUM; +- spin_unlock_irqrestore(&info->wq_lock, flags); ++ scoped_guard(spinlock_irqsave, &info->wq_lock) { ++ info->work_read_index++; ++ info->work_read_index %= WORK_MAX_NUM; ++ } + } + + static int imx_rpmsg_pcm_probe(struct platform_device *pdev) +@@ -819,9 +810,9 @@ static const struct dev_pm_ops imx_rpmsg_pcm_pm_ops = { + }; + + static const struct platform_device_id imx_rpmsg_pcm_id_table[] = { +- { .name = "rpmsg-audio-channel" }, +- { .name = "rpmsg-micfil-channel" }, +- { }, ++ { .name = "rpmsg-audio-channel" }, ++ { .name = "rpmsg-micfil-channel" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, imx_rpmsg_pcm_id_table); + +diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c +--- a/sound/soc/fsl/mpc5200_dma.c ++++ b/sound/soc/fsl/mpc5200_dma.c +@@ -77,18 +77,20 @@ static irqreturn_t psc_dma_bcom_irq(int irq, void *_psc_dma_stream) + { + struct psc_dma_stream *s = _psc_dma_stream; + +- spin_lock(&s->psc_dma->lock); +- /* For each finished period, dequeue the completed period buffer +- * and enqueue a new one in it's place. */ +- while (bcom_buffer_done(s->bcom_task)) { +- bcom_retrieve_buffer(s->bcom_task, NULL, NULL); ++ scoped_guard(spinlock, &s->psc_dma->lock) { ++ /* ++ * For each finished period, dequeue the completed period buffer ++ * and enqueue a new one in its place ++ */ ++ while (bcom_buffer_done(s->bcom_task)) { ++ bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + +- s->period_current = (s->period_current+1) % s->runtime->periods; +- s->period_count++; ++ s->period_current = (s->period_current+1) % s->runtime->periods; ++ s->period_count++; + +- psc_dma_bcom_enqueue_next_buffer(s); ++ psc_dma_bcom_enqueue_next_buffer(s); ++ } + } +- spin_unlock(&s->psc_dma->lock); + + /* If the stream is active, then also inform the PCM middle layer + * of the period finished event. */ +@@ -116,7 +118,6 @@ static int psc_dma_trigger(struct snd_soc_component *component, + struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma); + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + u16 imr; +- unsigned long flags; + int i; + + switch (cmd) { +@@ -135,19 +136,18 @@ static int psc_dma_trigger(struct snd_soc_component *component, + /* Fill up the bestcomm bd queue and enable DMA. + * This will begin filling the PSC's fifo. + */ +- spin_lock_irqsave(&psc_dma->lock, flags); ++ scoped_guard(spinlock_irqsave, &psc_dma->lock) { ++ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) ++ bcom_gen_bd_rx_reset(s->bcom_task); ++ else ++ bcom_gen_bd_tx_reset(s->bcom_task); + +- if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) +- bcom_gen_bd_rx_reset(s->bcom_task); +- else +- bcom_gen_bd_tx_reset(s->bcom_task); ++ for (i = 0; i < runtime->periods; i++) ++ if (!bcom_queue_full(s->bcom_task)) ++ psc_dma_bcom_enqueue_next_buffer(s); + +- for (i = 0; i < runtime->periods; i++) +- if (!bcom_queue_full(s->bcom_task)) +- psc_dma_bcom_enqueue_next_buffer(s); +- +- bcom_enable(s->bcom_task); +- spin_unlock_irqrestore(&psc_dma->lock, flags); ++ bcom_enable(s->bcom_task); ++ } + + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + +@@ -158,13 +158,13 @@ static int psc_dma_trigger(struct snd_soc_component *component, + substream->pstr->stream, s->period_count); + s->active = 0; + +- spin_lock_irqsave(&psc_dma->lock, flags); +- bcom_disable(s->bcom_task); +- if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) +- bcom_gen_bd_rx_reset(s->bcom_task); +- else +- bcom_gen_bd_tx_reset(s->bcom_task); +- spin_unlock_irqrestore(&psc_dma->lock, flags); ++ scoped_guard(spinlock_irqsave, &psc_dma->lock) { ++ bcom_disable(s->bcom_task); ++ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) ++ bcom_gen_bd_rx_reset(s->bcom_task); ++ else ++ bcom_gen_bd_tx_reset(s->bcom_task); ++ } + + break; + +@@ -314,35 +314,29 @@ int mpc5200_audio_dma_create(struct platform_device *op) + { + phys_addr_t fifo; + struct psc_dma *psc_dma; +- struct resource res; ++ struct resource *res; + int size, irq, rc; + const __be32 *prop; + void __iomem *regs; +- int ret; ++ ++ regs = devm_platform_get_and_ioremap_resource(op, 0, &res); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); + + /* Fetch the registers and IRQ of the PSC */ +- irq = irq_of_parse_and_map(op->dev.of_node, 0); +- if (of_address_to_resource(op->dev.of_node, 0, &res)) { +- dev_err(&op->dev, "Missing reg property\n"); +- return -ENODEV; +- } +- regs = devm_ioremap(&op->dev, res.start, resource_size(&res)); +- if (!regs) { +- dev_err(&op->dev, "Could not map registers\n"); +- return -ENODEV; +- } ++ irq = platform_get_irq(op, 0); ++ if (irq < 0) ++ return irq; + + /* Allocate and initialize the driver private data */ +- psc_dma = kzalloc_obj(*psc_dma); ++ psc_dma = devm_kzalloc(&op->dev, sizeof(*psc_dma), GFP_KERNEL); + if (!psc_dma) + return -ENOMEM; + + /* Get the PSC ID */ + prop = of_get_property(op->dev.of_node, "cell-index", &size); +- if (!prop || size < sizeof *prop) { +- ret = -ENODEV; +- goto out_free; +- } ++ if (!prop || size < sizeof *prop) ++ return -ENODEV; + + spin_lock_init(&psc_dma->lock); + mutex_init(&psc_dma->mutex); +@@ -357,7 +351,7 @@ int mpc5200_audio_dma_create(struct platform_device *op) + + /* Find the address of the fifo data registers and setup the + * DMA tasks */ +- fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32); ++ fifo = res->start + offsetof(struct mpc52xx_psc, buffer.buffer_32); + psc_dma->capture.bcom_task = + bcom_psc_gen_bd_rx_init(psc_dma->id, 10, fifo, 512); + psc_dma->playback.bcom_task = +@@ -365,8 +359,7 @@ int mpc5200_audio_dma_create(struct platform_device *op) + if (!psc_dma->capture.bcom_task || + !psc_dma->playback.bcom_task) { + dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); +- ret = -ENODEV; +- goto out_free; ++ return -ENODEV; + } + + /* Disable all interrupts and reset the PSC */ +@@ -399,16 +392,14 @@ int mpc5200_audio_dma_create(struct platform_device *op) + psc_dma->capture.irq = + bcom_get_task_irq(psc_dma->capture.bcom_task); + +- rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, ++ rc = devm_request_irq(&op->dev, psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, + "psc-dma-status", psc_dma); +- rc |= request_irq(psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED, ++ rc |= devm_request_irq(&op->dev, psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-capture", &psc_dma->capture); +- rc |= request_irq(psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED, ++ rc |= devm_request_irq(&op->dev, psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-playback", &psc_dma->playback); +- if (rc) { +- ret = -ENODEV; +- goto out_irq; +- } ++ if (rc) ++ return -ENODEV; + + /* Save what we've done so it can be found again later */ + dev_set_drvdata(&op->dev, psc_dma); +@@ -416,13 +407,6 @@ int mpc5200_audio_dma_create(struct platform_device *op) + /* Tell the ASoC OF helpers about it */ + return devm_snd_soc_register_component(&op->dev, + &mpc5200_audio_dma_component, NULL, 0); +-out_irq: +- free_irq(psc_dma->irq, psc_dma); +- free_irq(psc_dma->capture.irq, &psc_dma->capture); +- free_irq(psc_dma->playback.irq, &psc_dma->playback); +-out_free: +- kfree(psc_dma); +- return ret; + } + EXPORT_SYMBOL_GPL(mpc5200_audio_dma_create); + +@@ -435,12 +419,6 @@ int mpc5200_audio_dma_destroy(struct platform_device *op) + bcom_gen_bd_rx_release(psc_dma->capture.bcom_task); + bcom_gen_bd_tx_release(psc_dma->playback.bcom_task); + +- /* Release irqs */ +- free_irq(psc_dma->irq, psc_dma); +- free_irq(psc_dma->capture.irq, &psc_dma->capture); +- free_irq(psc_dma->playback.irq, &psc_dma->playback); +- +- kfree(psc_dma); + dev_set_drvdata(&op->dev, NULL); + + return 0; +diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c +--- a/sound/soc/fsl/mpc5200_psc_ac97.c ++++ b/sound/soc/fsl/mpc5200_psc_ac97.c +@@ -30,14 +30,13 @@ static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) + int status; + unsigned int val; + +- mutex_lock(&psc_dma->mutex); ++ guard(mutex)(&psc_dma->mutex); + + /* Wait for command send status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (rdy)\n"); +- mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + +@@ -53,19 +52,16 @@ static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) + if (status == 0) { + pr_err("timeout on ac97 read (val) %x\n", + in_be16(&psc_dma->psc_regs->sr_csr.status)); +- mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + /* Get the data */ + val = in_be32(&psc_dma->psc_regs->ac97_data); + if (((val >> 24) & 0x7f) != reg) { + pr_err("reg echo error on ac97 read\n"); +- mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + val = (val >> 8) & 0xffff; + +- mutex_unlock(&psc_dma->mutex); + return (unsigned short) val; + } + +@@ -74,52 +70,46 @@ static void psc_ac97_write(struct snd_ac97 *ac97, + { + int status; + +- mutex_lock(&psc_dma->mutex); ++ guard(mutex)(&psc_dma->mutex); + + /* Wait for command status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (write)\n"); +- goto out; ++ return; + } + /* Write data */ + out_be32(&psc_dma->psc_regs->ac97_cmd, + ((reg & 0x7f) << 24) | (val << 8)); +- +- out: +- mutex_unlock(&psc_dma->mutex); + } + + static void psc_ac97_warm_reset(struct snd_ac97 *ac97) + { + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + +- mutex_lock(&psc_dma->mutex); ++ guard(mutex)(&psc_dma->mutex); + + out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR); + udelay(3); + out_be32(®s->sicr, psc_dma->sicr); +- +- mutex_unlock(&psc_dma->mutex); + } + + static void psc_ac97_cold_reset(struct snd_ac97 *ac97) + { + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + +- mutex_lock(&psc_dma->mutex); +- dev_dbg(psc_dma->dev, "cold reset\n"); ++ scoped_guard(mutex, &psc_dma->mutex) { ++ dev_dbg(psc_dma->dev, "cold reset\n"); + +- mpc5200_psc_ac97_gpio_reset(psc_dma->id); ++ mpc5200_psc_ac97_gpio_reset(psc_dma->id); + +- /* Notify the PSC that a reset has occurred */ +- out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB); ++ /* Notify the PSC that a reset has occurred */ ++ out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB); + +- /* Re-enable RX and TX */ +- out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); +- +- mutex_unlock(&psc_dma->mutex); ++ /* Re-enable RX and TX */ ++ out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); ++ } + + usleep_range(1000, 2000); + psc_ac97_warm_reset(ac97); +@@ -286,7 +276,7 @@ static int psc_ac97_of_probe(struct platform_device *op) + return rc; + } + +- rc = snd_soc_register_component(&op->dev, &psc_ac97_component, ++ rc = devm_snd_soc_register_component(&op->dev, &psc_ac97_component, + psc_ac97_dai, ARRAY_SIZE(psc_ac97_dai)); + if (rc != 0) { + dev_err(&op->dev, "Failed to register DAI\n"); +@@ -312,8 +302,6 @@ static int psc_ac97_of_probe(struct platform_device *op) + static void psc_ac97_of_remove(struct platform_device *op) + { + mpc5200_audio_dma_destroy(op); +- snd_soc_unregister_component(&op->dev); +- snd_soc_set_ac97_ops(NULL); + } + + /* Match table for of_platform binding */ +diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c +--- a/sound/soc/fsl/mpc5200_psc_i2s.c ++++ b/sound/soc/fsl/mpc5200_psc_i2s.c +@@ -79,9 +79,12 @@ static int psc_i2s_hw_params(struct snd_pcm_substream *substream, + * and we don't care about the frequency. Return an error if the direction + * is not SND_SOC_CLOCK_IN. + * ++ * @cpu_dai: DAI runtime data pointer + * @clk_id: reserved, should be zero + * @freq: the frequency of the given clock ID, currently ignored + * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master) ++ * ++ * Returns: %0 on success or %-EINVAL on failure. + */ + static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +@@ -101,7 +104,10 @@ static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + * This driver only supports I2S mode. Return an error if the format is + * not SND_SOC_DAIFMT_I2S. + * ++ * @cpu_dai: DAI runtime data pointer + * @format: one of SND_SOC_DAIFMT_xxx ++ * ++ * Returns: %0 on success or %-EINVAL on failure. + */ + static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) + { +@@ -119,7 +125,7 @@ static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) + */ + + /** +- * psc_i2s_dai_template: template CPU Digital Audio Interface ++ * var psc_i2s_dai_ops - template CPU Digital Audio Interface + */ + static const struct snd_soc_dai_ops psc_i2s_dai_ops = { + .hw_params = psc_i2s_hw_params, +@@ -166,7 +172,7 @@ static int psc_i2s_of_probe(struct platform_device *op) + if (rc != 0) + return rc; + +- rc = snd_soc_register_component(&op->dev, &psc_i2s_component, ++ rc = devm_snd_soc_register_component(&op->dev, &psc_i2s_component, + psc_i2s_dai, ARRAY_SIZE(psc_i2s_dai)); + if (rc != 0) { + pr_err("Failed to register DAI\n"); +@@ -213,7 +219,6 @@ static int psc_i2s_of_probe(struct platform_device *op) + static void psc_i2s_of_remove(struct platform_device *op) + { + mpc5200_audio_dma_destroy(op); +- snd_soc_unregister_component(&op->dev); + } + + /* Match table for of_platform binding */ +diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c +--- a/sound/soc/fsl/p1022_ds.c ++++ b/sound/soc/fsl/p1022_ds.c +@@ -73,6 +73,8 @@ struct machine_data { + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ + }; + ++#define card_to_mdata(_card) container_of(_card, struct machine_data, card) ++ + /** + * p1022_ds_machine_probe: initialize the board + * +@@ -82,8 +84,7 @@ struct machine_data { + */ + static int p1022_ds_machine_probe(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -122,8 +123,7 @@ static int p1022_ds_machine_probe(struct snd_soc_card *card) + static int p1022_ds_startup(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); +- struct machine_data *mdata = +- container_of(rtd->card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(rtd->card); + struct device *dev = rtd->card->dev; + int ret = 0; + +@@ -156,8 +156,7 @@ static int p1022_ds_startup(struct snd_pcm_substream *substream) + */ + static int p1022_ds_machine_remove(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -399,8 +398,7 @@ static int p1022_ds_probe(struct platform_device *pdev) + static void p1022_ds_remove(struct platform_device *pdev) + { + struct snd_soc_card *card = platform_get_drvdata(pdev); +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + + snd_soc_unregister_card(card); + kfree(mdata); +diff --git a/sound/soc/fsl/p1022_rdk.c b/sound/soc/fsl/p1022_rdk.c +--- a/sound/soc/fsl/p1022_rdk.c ++++ b/sound/soc/fsl/p1022_rdk.c +@@ -79,6 +79,8 @@ struct machine_data { + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ + }; + ++#define card_to_mdata(_card) container_of(_card, struct machine_data, card) ++ + /** + * p1022_rdk_machine_probe - initialize the board + * @card: ASoC card instance +@@ -91,8 +93,7 @@ struct machine_data { + */ + static int p1022_rdk_machine_probe(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -134,8 +135,7 @@ static int p1022_rdk_machine_probe(struct snd_soc_card *card) + static int p1022_rdk_startup(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); +- struct machine_data *mdata = +- container_of(rtd->card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(rtd->card); + struct device *dev = rtd->card->dev; + int ret = 0; + +@@ -169,8 +169,7 @@ static int p1022_rdk_startup(struct snd_pcm_substream *substream) + */ + static int p1022_rdk_machine_remove(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -361,8 +360,7 @@ static int p1022_rdk_probe(struct platform_device *pdev) + static void p1022_rdk_remove(struct platform_device *pdev) + { + struct snd_soc_card *card = platform_get_drvdata(pdev); +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + + snd_soc_unregister_card(card); + kfree(mdata); +diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c +--- a/sound/soc/generic/audio-graph-card.c ++++ b/sound/soc/generic/audio-graph-card.c +@@ -579,11 +579,11 @@ int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) + goto end; + } + +- ret = simple_util_parse_widgets(card, NULL); ++ ret = simple_util_parse_widgets(priv, NULL); + if (ret < 0) + goto end; + +- ret = simple_util_parse_routing(card, NULL); ++ ret = simple_util_parse_routing(priv, NULL); + if (ret < 0) + goto end; + +@@ -594,6 +594,7 @@ int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) + if (ret < 0) + goto err; + ++ /* Card name should be set after graph_for_each_link() */ + ret = simple_util_parse_card_name(priv, NULL); + if (ret < 0) + goto err; +@@ -603,14 +604,13 @@ int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) + simple_util_debug_info(priv); + + ret = devm_snd_soc_register_card(dev, card); +- if (ret < 0) +- goto err; +- +- return 0; + err: +- simple_util_clean_reference(card); ++ if (ret < 0) { ++ simple_util_clean_reference(priv); ++ return dev_err_probe(dev, ret, "parse error\n"); ++ } + end: +- return dev_err_probe(dev, ret, "parse error\n"); ++ return graph_ret(priv, ret); + } + EXPORT_SYMBOL_GPL(audio_graph_parse_of); + +diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c +--- a/sound/soc/generic/audio-graph-card2.c ++++ b/sound/soc/generic/audio-graph-card2.c +@@ -1303,11 +1303,11 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + struct graph2_custom_hooks *hooks) + { + struct snd_soc_card *card = simple_priv_to_card(priv); +- int ret; ++ int ret = -ENOMEM; + + struct link_info *li __free(kfree) = kzalloc_obj(*li); + if (!li) +- return -ENOMEM; ++ goto end; + + card->probe = graph_util_card_probe; + card->owner = THIS_MODULE; +@@ -1316,31 +1316,35 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + if ((hooks) && (hooks)->hook_pre) { + ret = (hooks)->hook_pre(priv); + if (ret < 0) +- goto err; ++ goto end; + } + + ret = graph_for_each_link(priv, hooks, li, graph_count); + if (!li->link) + ret = -EINVAL; + if (ret < 0) +- goto err; ++ goto end; + + ret = simple_util_init_priv(priv, li); + if (ret < 0) +- goto err; ++ goto end; + + priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW); + if (IS_ERR(priv->pa_gpio)) { + ret = PTR_ERR(priv->pa_gpio); + dev_err(dev, "failed to get amplifier gpio: %d\n", ret); +- goto err; ++ goto end; + } + +- ret = simple_util_parse_widgets(card, NULL); ++ ret = simple_util_parse_widgets(priv, NULL); + if (ret < 0) +- goto err; ++ goto end; + +- ret = simple_util_parse_routing(card, NULL); ++ ret = simple_util_parse_routing(priv, NULL); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_aux_devs(priv, NULL); + if (ret < 0) + goto err; + +@@ -1349,6 +1353,7 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + if (ret < 0) + goto err; + ++ /* Card name should be set after graph_for_each_link() */ + ret = simple_util_parse_card_name(priv, NULL); + if (ret < 0) + goto err; +@@ -1363,15 +1368,13 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + + simple_util_debug_info(priv); + +- ret = snd_soc_of_parse_aux_devs(card, "aux-devs"); +- if (ret < 0) +- goto err; +- + ret = devm_snd_soc_register_card(dev, card); + err: +- if (ret < 0) +- dev_err_probe(dev, ret, "parse error\n"); +- ++ if (ret < 0) { ++ simple_util_clean_reference(priv); ++ return dev_err_probe(dev, ret, "parse error\n"); ++ } ++end: + return graph_ret(priv, ret); + } + EXPORT_SYMBOL_GPL(audio_graph2_parse_of); +diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c +--- a/sound/soc/generic/simple-card-utils.c ++++ b/sound/soc/generic/simple-card-utils.c +@@ -216,6 +216,27 @@ int simple_util_set_dailink_name(struct simple_util_priv *priv, + } + EXPORT_SYMBOL_GPL(simple_util_set_dailink_name); + ++int simple_util_parse_property(struct simple_util_priv *priv, ++ int (*func)(struct snd_soc_card *card, const char *propname), ++ char *prefix, char *property) ++{ ++ struct snd_soc_card *card = simple_priv_to_card(priv); ++ struct device_node *node = card->dev->of_node; ++ char prop[128]; ++ ++ if (!prefix) ++ prefix = ""; ++ ++ snprintf(prop, sizeof(prop), "%s%s", prefix, property); ++ ++ /* no property is not error */ ++ if (!of_property_present(node, prop)) ++ return 0; ++ ++ return func(card, prop); ++} ++EXPORT_SYMBOL_GPL(simple_util_parse_property); ++ + int simple_util_parse_card_name(struct simple_util_priv *priv, + char *prefix) + { +@@ -731,11 +752,12 @@ void simple_util_canonicalize_cpu(struct snd_soc_dai_link_component *cpus, + } + EXPORT_SYMBOL_GPL(simple_util_canonicalize_cpu); + +-void simple_util_clean_reference(struct snd_soc_card *card) ++void simple_util_clean_reference(struct simple_util_priv *priv) + { + struct snd_soc_dai_link *dai_link; + struct snd_soc_dai_link_component *cpu; + struct snd_soc_dai_link_component *codec; ++ struct snd_soc_card *card = simple_priv_to_card(priv); + int i, j; + + for_each_card_prelinks(card, i, dai_link) { +@@ -747,57 +769,6 @@ void simple_util_clean_reference(struct snd_soc_card *card) + } + EXPORT_SYMBOL_GPL(simple_util_clean_reference); + +-int simple_util_parse_routing(struct snd_soc_card *card, +- char *prefix) +-{ +- struct device_node *node = card->dev->of_node; +- char prop[128]; +- +- if (!prefix) +- prefix = ""; +- +- snprintf(prop, sizeof(prop), "%s%s", prefix, "routing"); +- +- if (!of_property_present(node, prop)) +- return 0; +- +- return snd_soc_of_parse_audio_routing(card, prop); +-} +-EXPORT_SYMBOL_GPL(simple_util_parse_routing); +- +-int simple_util_parse_widgets(struct snd_soc_card *card, +- char *prefix) +-{ +- struct device_node *node = card->dev->of_node; +- char prop[128]; +- +- if (!prefix) +- prefix = ""; +- +- snprintf(prop, sizeof(prop), "%s%s", prefix, "widgets"); +- +- if (of_property_present(node, prop)) +- return snd_soc_of_parse_audio_simple_widgets(card, prop); +- +- /* no widgets is not error */ +- return 0; +-} +-EXPORT_SYMBOL_GPL(simple_util_parse_widgets); +- +-int simple_util_parse_pin_switches(struct snd_soc_card *card, +- char *prefix) +-{ +- char prop[128]; +- +- if (!prefix) +- prefix = ""; +- +- snprintf(prop, sizeof(prop), "%s%s", prefix, "pin-switches"); +- +- return snd_soc_of_parse_pin_switches(card, prop); +-} +-EXPORT_SYMBOL_GPL(simple_util_parse_pin_switches); +- + int simple_util_init_jack(struct snd_soc_card *card, + struct simple_util_jack *sjack, + int is_hp, char *prefix, +@@ -854,9 +825,9 @@ int simple_util_init_jack(struct snd_soc_card *card, + } + EXPORT_SYMBOL_GPL(simple_util_init_jack); + +-int simple_util_init_aux_jacks(struct simple_util_priv *priv, char *prefix) ++int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix) + { +- struct snd_soc_card *card = simple_priv_to_card(priv); ++ struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component; + int found_jack_index = 0; + int type = 0; +@@ -1026,8 +997,9 @@ EXPORT_SYMBOL_GPL(simple_util_init_priv); + void simple_util_remove(struct platform_device *pdev) + { + struct snd_soc_card *card = platform_get_drvdata(pdev); ++ struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + +- simple_util_clean_reference(card); ++ simple_util_clean_reference(priv); + } + EXPORT_SYMBOL_GPL(simple_util_remove); + +diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c +--- a/sound/soc/generic/simple-card.c ++++ b/sound/soc/generic/simple-card.c +@@ -517,44 +517,6 @@ static int simple_populate_aux(struct simple_util_priv *priv) + return simple_ret(priv, ret); + } + +-static int simple_parse_of(struct simple_util_priv *priv, struct link_info *li) +-{ +- struct snd_soc_card *card = simple_priv_to_card(priv); +- int ret; +- +- ret = simple_util_parse_widgets(card, PREFIX); +- if (ret < 0) +- goto end; +- +- ret = simple_util_parse_routing(card, PREFIX); +- if (ret < 0) +- goto end; +- +- ret = simple_util_parse_pin_switches(card, PREFIX); +- if (ret < 0) +- goto end; +- +- /* Single/Muti DAI link(s) & New style of DT node */ +- memset(li, 0, sizeof(*li)); +- ret = simple_for_each_link(priv, li, +- simple_dai_link_of, +- simple_dai_link_of_dpcm); +- if (ret < 0) +- goto end; +- +- ret = simple_util_parse_card_name(priv, PREFIX); +- if (ret < 0) +- goto end; +- +- ret = simple_populate_aux(priv); +- if (ret < 0) +- goto end; +- +- ret = snd_soc_of_parse_aux_devs(card, PREFIX "aux-devs"); +-end: +- return simple_ret(priv, ret); +-} +- + static int simple_count_noml(struct simple_util_priv *priv, + struct device_node *np, + struct device_node *codec, +@@ -699,28 +661,19 @@ static int simple_soc_probe(struct snd_soc_card *card) + if (ret < 0) + goto end; + +- ret = simple_util_init_aux_jacks(priv, PREFIX); ++ ret = simple_util_init_aux_jacks(card, PREFIX); + end: + return simple_ret(priv, ret); + } + +-static int simple_probe(struct platform_device *pdev) ++static int simple_parse_of(struct simple_util_priv *priv) + { +- struct simple_util_priv *priv; +- struct device *dev = &pdev->dev; +- struct snd_soc_card *card; +- int ret; ++ struct snd_soc_card *card = simple_priv_to_card(priv); ++ struct device *dev = card->dev; ++ int ret = -EINVAL; + +- /* Allocate the private data and the DAI link array */ +- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); +- if (!priv) +- return -ENOMEM; +- +- card = simple_priv_to_card(priv); +- card->owner = THIS_MODULE; +- card->dev = dev; +- card->probe = simple_soc_probe; +- card->driver_name = "simple-card"; ++ if (!dev) ++ return simple_ret(priv, ret); + + ret = -ENOMEM; + struct link_info *li __free(kfree) = kzalloc_obj(*li); +@@ -739,25 +692,71 @@ static int simple_probe(struct platform_device *pdev) + if (ret < 0) + goto end; + +- ret = simple_parse_of(priv, li); +- if (ret < 0) { +- dev_err_probe(dev, ret, "parse error\n"); ++ ret = simple_util_parse_widgets(priv, PREFIX); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_routing(priv, PREFIX); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_pin_switches(priv, PREFIX); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_aux_devs(priv, PREFIX); ++ if (ret < 0) ++ goto err; ++ ++ /* Single/Muti DAI link(s) & New style of DT node */ ++ memset(li, 0, sizeof(*li)); ++ ret = simple_for_each_link(priv, li, ++ simple_dai_link_of, ++ simple_dai_link_of_dpcm); ++ if (ret < 0) ++ goto err; ++ ++ /* Card name should be set after simple_for_each_link() */ ++ ret = simple_util_parse_card_name(priv, PREFIX); ++ if (ret < 0) ++ goto err; ++ ++ ret = simple_populate_aux(priv); ++ if (ret < 0) + goto err; +- } + + snd_soc_card_set_drvdata(card, priv); + + simple_util_debug_info(priv); + + ret = devm_snd_soc_register_card(dev, card); +- if (ret < 0) +- goto err; +- +- return 0; + err: +- simple_util_clean_reference(card); ++ if (ret < 0) { ++ simple_util_clean_reference(priv); ++ return dev_err_probe(dev, ret, "parse error\n"); ++ } + end: +- return dev_err_probe(dev, ret, "parse error\n"); ++ return simple_ret(priv, ret); ++} ++ ++static int simple_probe(struct platform_device *pdev) ++{ ++ struct simple_util_priv *priv; ++ struct device *dev = &pdev->dev; ++ struct snd_soc_card *card; ++ ++ /* Allocate the private data and the DAI link array */ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ card = simple_priv_to_card(priv); ++ card->owner = THIS_MODULE; ++ card->dev = dev; ++ card->probe = simple_soc_probe; ++ card->driver_name = "simple-card"; ++ ++ return simple_parse_of(priv); + } + + static const struct of_device_id simple_of_match[] = { +diff --git a/sound/soc/intel/atom/sst-atom-controls.c b/sound/soc/intel/atom/sst-atom-controls.c +--- a/sound/soc/intel/atom/sst-atom-controls.c ++++ b/sound/soc/intel/atom/sst-atom-controls.c +@@ -14,6 +14,7 @@ + */ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + ++#include + #include + #include + #include +@@ -73,14 +74,10 @@ static int sst_fill_and_send_cmd(struct sst_data *drv, + u8 ipc_msg, u8 block, u8 task_id, u8 pipe_id, + void *cmd_data, u16 len) + { +- int ret; ++ guard(mutex)(&drv->lock); + +- mutex_lock(&drv->lock); +- ret = sst_fill_and_send_cmd_unlocked(drv, ipc_msg, block, +- task_id, pipe_id, cmd_data, len); +- mutex_unlock(&drv->lock); +- +- return ret; ++ return sst_fill_and_send_cmd_unlocked(drv, ipc_msg, block, ++ task_id, pipe_id, cmd_data, len); + } + + /* +@@ -167,7 +164,7 @@ static int sst_slot_get(struct snd_kcontrol *kcontrol, + unsigned int val, mux; + u8 *map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + val = 1 << ctl_no; + /* search which slot/channel has this bit set - there should be only one */ + for (mux = e->max; mux > 0; mux--) +@@ -175,7 +172,6 @@ static int sst_slot_get(struct snd_kcontrol *kcontrol, + break; + + ucontrol->value.enumerated.item[0] = mux; +- mutex_unlock(&drv->lock); + + dev_dbg(c->dev, "%s - %s map = %#x\n", + is_tx ? "tx channel" : "rx slot", +@@ -235,7 +231,7 @@ static int sst_slot_put(struct snd_kcontrol *kcontrol, + if (mux > e->max - 1) + return -EINVAL; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + /* first clear all registers of this bit */ + for (i = 0; i < e->max; i++) + map[i] &= ~val; +@@ -244,7 +240,6 @@ static int sst_slot_put(struct snd_kcontrol *kcontrol, + /* kctl set to 'none' and we reset the bits so send IPC */ + ret = sst_check_and_send_slot_map(drv, kcontrol); + +- mutex_unlock(&drv->lock); + return ret; + } + +@@ -258,7 +253,6 @@ static int sst_slot_put(struct snd_kcontrol *kcontrol, + + ret = sst_check_and_send_slot_map(drv, kcontrol); + +- mutex_unlock(&drv->lock); + return ret; + } + +@@ -354,13 +348,12 @@ static int sst_algo_control_set(struct snd_kcontrol *kcontrol, + struct sst_algo_control *bc = (void *)kcontrol->private_value; + + dev_dbg(cmpnt->dev, "control_name=%s\n", kcontrol->id.name); +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + switch (bc->type) { + case SST_ALGO_PARAMS: + memcpy(bc->params, ucontrol->value.bytes.data, bc->max); + break; + default: +- mutex_unlock(&drv->lock); + dev_err(cmpnt->dev, "Invalid Input- algo type:%d\n", + bc->type); + return -EINVAL; +@@ -368,7 +361,6 @@ static int sst_algo_control_set(struct snd_kcontrol *kcontrol, + /*if pipe is enabled, need to send the algo params from here*/ + if (bc->w && bc->w->power) + ret = sst_send_algo_cmd(drv, bc); +- mutex_unlock(&drv->lock); + + return ret; + } +@@ -475,7 +467,7 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol, + struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; + struct sst_gain_value *gv = mc->gain_val; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + + switch (mc->type) { + case SST_GAIN_TLV: +@@ -497,7 +489,6 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol, + break; + + default: +- mutex_unlock(&drv->lock); + dev_err(cmpnt->dev, "Invalid Input- gain type:%d\n", + mc->type); + return -EINVAL; +@@ -506,7 +497,6 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol, + if (mc->w && mc->w->power) + ret = sst_send_gain_cmd(drv, gv, mc->task_id, + mc->pipe_id | mc->instance_id, mc->module_id, 0); +- mutex_unlock(&drv->lock); + + return ret; + } +@@ -521,10 +511,9 @@ static int sst_send_pipe_module_params(struct snd_soc_dapm_widget *w, + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_ids *ids = w->priv; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + sst_find_and_send_pipe_algo(drv, w->name, ids); + sst_set_pipe_gain(ids, drv, 0); +- mutex_unlock(&drv->lock); + + return 0; + } +@@ -761,27 +750,29 @@ int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable) + return ret; + } + +- mutex_lock(&drv->lock); +- if (enable) +- timer_usage++; +- else +- timer_usage--; +- +- /* +- * Send the command only if this call is the first enable or last +- * disable +- */ +- if ((enable && (timer_usage == 1)) || +- (!enable && (timer_usage == 0))) { +- ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD, +- SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd, +- sizeof(cmd.header) + cmd.header.length); +- if (ret && enable) { ++ scoped_guard(mutex, &drv->lock) { ++ if (enable) ++ timer_usage++; ++ else + timer_usage--; +- enable = false; ++ ++ /* ++ * Send the command only if this call is the first enable or last ++ * disable ++ */ ++ if ((enable && timer_usage == 1) || ++ (!enable && timer_usage == 0)) { ++ ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD, ++ SST_FLAG_BLOCKED, ++ SST_TASK_SBA, 0, &cmd, ++ sizeof(cmd.header) + ++ cmd.header.length); ++ if (ret && enable) { ++ timer_usage--; ++ enable = false; ++ } + } + } +- mutex_unlock(&drv->lock); + + if (!enable) + sst->ops->power(sst->dev, false); +diff --git a/sound/soc/intel/atom/sst-mfld-platform-pcm.c b/sound/soc/intel/atom/sst-mfld-platform-pcm.c +--- a/sound/soc/intel/atom/sst-mfld-platform-pcm.c ++++ b/sound/soc/intel/atom/sst-mfld-platform-pcm.c +@@ -11,6 +11,7 @@ + */ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + ++#include + #include + #include + #include +@@ -32,7 +33,7 @@ int sst_register_dsp(struct sst_device *dev) + return -EINVAL; + if (!try_module_get(dev->dev->driver->owner)) + return -ENODEV; +- mutex_lock(&sst_lock); ++ guard(mutex)(&sst_lock); + if (sst) { + dev_err(dev->dev, "we already have a device %s\n", sst->name); + module_put(dev->dev->driver->owner); +@@ -41,7 +42,7 @@ int sst_register_dsp(struct sst_device *dev) + } + dev_dbg(dev->dev, "registering device %s\n", dev->name); + sst = dev; +- mutex_unlock(&sst_lock); ++ + return 0; + } + EXPORT_SYMBOL_GPL(sst_register_dsp); +@@ -53,17 +54,15 @@ int sst_unregister_dsp(struct sst_device *dev) + if (dev != sst) + return -EINVAL; + +- mutex_lock(&sst_lock); ++ guard(mutex)(&sst_lock); + +- if (!sst) { +- mutex_unlock(&sst_lock); ++ if (!sst) + return -EIO; +- } + + module_put(sst->dev->driver->owner); + dev_dbg(dev->dev, "unreg %s\n", sst->name); + sst = NULL; +- mutex_unlock(&sst_lock); ++ + return 0; + } + EXPORT_SYMBOL_GPL(sst_unregister_dsp); +@@ -103,21 +102,14 @@ static int sst_media_digital_mute(struct snd_soc_dai *dai, int mute, int stream) + void sst_set_stream_status(struct sst_runtime_stream *stream, + int state) + { +- unsigned long flags; +- spin_lock_irqsave(&stream->status_lock, flags); ++ guard(spinlock_irqsave)(&stream->status_lock); + stream->stream_status = state; +- spin_unlock_irqrestore(&stream->status_lock, flags); + } + + static inline int sst_get_stream_status(struct sst_runtime_stream *stream) + { +- int state; +- unsigned long flags; +- +- spin_lock_irqsave(&stream->status_lock, flags); +- state = stream->stream_status; +- spin_unlock_irqrestore(&stream->status_lock, flags); +- return state; ++ guard(spinlock_irqsave)(&stream->status_lock); ++ return stream->stream_status; + } + + static void sst_fill_alloc_params(struct snd_pcm_substream *substream, +@@ -244,12 +236,7 @@ static int sst_platform_alloc_stream(struct snd_pcm_substream *substream, + + stream->stream_info.str_id = str_params.stream_id; + +- ret_val = stream->ops->open(sst->dev, &str_params); +- if (ret_val <= 0) +- return ret_val; +- +- +- return ret_val; ++ return stream->ops->open(sst->dev, &str_params); + } + + static void sst_period_elapsed(void *arg) +@@ -304,7 +291,7 @@ static int sst_media_open(struct snd_pcm_substream *substream, + { + int ret_val = 0; + struct snd_pcm_runtime *runtime = substream->runtime; +- struct sst_runtime_stream *stream; ++ struct sst_runtime_stream *stream __free(kfree) = NULL; + + stream = kzalloc_obj(*stream); + if (!stream) +@@ -312,15 +299,14 @@ static int sst_media_open(struct snd_pcm_substream *substream, + spin_lock_init(&stream->status_lock); + + /* get the sst ops */ +- mutex_lock(&sst_lock); +- if (!sst || +- !try_module_get(sst->dev->driver->owner)) { +- dev_err(dai->dev, "no device available to run\n"); +- ret_val = -ENODEV; +- goto out_ops; ++ scoped_guard(mutex, &sst_lock) { ++ if (!sst || ++ !try_module_get(sst->dev->driver->owner)) { ++ dev_err(dai->dev, "no device available to run\n"); ++ return -ENODEV; ++ } ++ stream->ops = sst->ops; + } +- stream->ops = sst->ops; +- mutex_unlock(&sst_lock); + + stream->stream_info.str_id = 0; + +@@ -330,7 +316,7 @@ static int sst_media_open(struct snd_pcm_substream *substream, + + ret_val = power_up_sst(stream); + if (ret_val < 0) +- goto out_power_up; ++ return ret_val; + + /* + * Make sure the period to be multiple of 1ms to align the +@@ -347,12 +333,14 @@ static int sst_media_open(struct snd_pcm_substream *substream, + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIODS, 2); + +- return snd_pcm_hw_constraint_integer(runtime, +- SNDRV_PCM_HW_PARAM_PERIODS); +-out_ops: +- mutex_unlock(&sst_lock); +-out_power_up: +- kfree(stream); ++ ret_val = snd_pcm_hw_constraint_integer(runtime, ++ SNDRV_PCM_HW_PARAM_PERIODS); ++ ++ if (ret_val < 0) ++ return ret_val; ++ ++ stream = NULL; ++ + return ret_val; + } + +diff --git a/sound/soc/intel/atom/sst/sst_ipc.c b/sound/soc/intel/atom/sst/sst_ipc.c +--- a/sound/soc/intel/atom/sst/sst_ipc.c ++++ b/sound/soc/intel/atom/sst/sst_ipc.c +@@ -11,6 +11,7 @@ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ ++#include + #include + #include + #include +@@ -180,9 +181,8 @@ void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) + union interrupt_reg_mrfld isr; + union interrupt_reg_mrfld imr; + union ipc_header_mrfld clear_ipc; +- unsigned long irq_flags; + +- spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); ++ guard(spinlock_irqsave)(&sst_drv_ctx->ipc_spin_lock); + imr.full = sst_shim_read64(sst_drv_ctx->shim, SST_IMRX); + isr.full = sst_shim_read64(sst_drv_ctx->shim, SST_ISRX); + +@@ -200,7 +200,6 @@ void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) + /* un mask busy interrupt */ + imr.part.busy_interrupt = 0; + sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, imr.full); +- spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + } + + +diff --git a/sound/soc/intel/atom/sst/sst_pvt.c b/sound/soc/intel/atom/sst/sst_pvt.c +--- a/sound/soc/intel/atom/sst/sst_pvt.c ++++ b/sound/soc/intel/atom/sst/sst_pvt.c +@@ -11,6 +11,7 @@ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ ++#include + #include + #include + #include +@@ -64,9 +65,8 @@ u64 sst_shim_read64(void __iomem *addr, int offset) + void sst_set_fw_state_locked( + struct intel_sst_drv *sst_drv_ctx, int sst_state) + { +- mutex_lock(&sst_drv_ctx->sst_lock); ++ guard(mutex)(&sst_drv_ctx->sst_lock); + sst_drv_ctx->sst_state = sst_state; +- mutex_unlock(&sst_drv_ctx->sst_lock); + } + + /* +@@ -302,18 +302,17 @@ int sst_assign_pvt_id(struct intel_sst_drv *drv) + { + int local; + +- spin_lock(&drv->block_lock); ++ guard(spinlock)(&drv->block_lock); + /* find first zero index from lsb */ + local = ffz(drv->pvt_id); + dev_dbg(drv->dev, "pvt_id assigned --> %d\n", local); + if (local >= SST_MAX_BLOCKS){ +- spin_unlock(&drv->block_lock); + dev_err(drv->dev, "PVT _ID error: no free id blocks "); + return -EINVAL; + } + /* toggle the index */ + change_bit(local, &drv->pvt_id); +- spin_unlock(&drv->block_lock); ++ + return local; + } + +diff --git a/sound/soc/intel/avs/apl.c b/sound/soc/intel/avs/apl.c +--- a/sound/soc/intel/avs/apl.c ++++ b/sound/soc/intel/avs/apl.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -190,7 +191,7 @@ static bool avs_apl_lp_streaming(struct avs_dev *adev) + { + struct avs_path *path; + +- spin_lock(&adev->path_list_lock); ++ guard(spinlock)(&adev->path_list_lock); + /* Any gateway without buffer allocated in LP area disqualifies D0IX. */ + list_for_each_entry(path, &adev->path_list, node) { + struct avs_path_pipeline *ppl; +@@ -210,14 +211,11 @@ static bool avs_apl_lp_streaming(struct avs_dev *adev) + if (cfg->copier.dma_type == INVALID_OBJECT_ID) + continue; + +- if (!mod->gtw_attrs.lp_buffer_alloc) { +- spin_unlock(&adev->path_list_lock); ++ if (!mod->gtw_attrs.lp_buffer_alloc) + return false; +- } + } + } + } +- spin_unlock(&adev->path_list_lock); + + return true; + } +diff --git a/sound/soc/intel/avs/avs.h b/sound/soc/intel/avs/avs.h +--- a/sound/soc/intel/avs/avs.h ++++ b/sound/soc/intel/avs/avs.h +@@ -336,14 +336,13 @@ int avs_icl_load_basefw(struct avs_dev *adev, struct firmware *fw); + /* Soc component members */ + + struct avs_soc_component { +- struct snd_soc_component base; ++ struct snd_soc_component *base; + struct avs_tplg *tplg; + + struct list_head node; + }; + +-#define to_avs_soc_component(comp) \ +- container_of(comp, struct avs_soc_component, base) ++#define to_avs_soc_component(comp) snd_soc_component_to_priv(comp) + + extern const struct snd_soc_dai_ops avs_dai_fe_ops; + +diff --git a/sound/soc/intel/avs/boards/da7219.c b/sound/soc/intel/avs/boards/da7219.c +--- a/sound/soc/intel/avs/boards/da7219.c ++++ b/sound/soc/intel/avs/boards/da7219.c +@@ -175,7 +175,6 @@ static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + if (!dl || !platform) + return -ENOMEM; + +- dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); +@@ -259,10 +258,8 @@ static int avs_da7219_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_da7219_driver_ids[] = { +- { +- .name = "avs_da7219", +- }, +- {}, ++ { .name = "avs_da7219" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_da7219_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/dmic.c b/sound/soc/intel/avs/boards/dmic.c +--- a/sound/soc/intel/avs/boards/dmic.c ++++ b/sound/soc/intel/avs/boards/dmic.c +@@ -104,10 +104,8 @@ static int avs_dmic_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_dmic_driver_ids[] = { +- { +- .name = "avs_dmic", +- }, +- {}, ++ { .name = "avs_dmic" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_dmic_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/es8336.c b/sound/soc/intel/avs/boards/es8336.c +--- a/sound/soc/intel/avs/boards/es8336.c ++++ b/sound/soc/intel/avs/boards/es8336.c +@@ -309,10 +309,8 @@ static int avs_es8336_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_es8336_driver_ids[] = { +- { +- .name = "avs_es8336", +- }, +- {}, ++ { .name = "avs_es8336" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_es8336_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c +--- a/sound/soc/intel/avs/boards/hdaudio.c ++++ b/sound/soc/intel/avs/boards/hdaudio.c +@@ -231,10 +231,8 @@ static int avs_hdaudio_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_hdaudio_driver_ids[] = { +- { +- .name = "avs_hdaudio", +- }, +- {}, ++ { .name = "avs_hdaudio" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_hdaudio_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/i2s_test.c b/sound/soc/intel/avs/boards/i2s_test.c +--- a/sound/soc/intel/avs/boards/i2s_test.c ++++ b/sound/soc/intel/avs/boards/i2s_test.c +@@ -107,10 +107,8 @@ static int avs_i2s_test_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_i2s_test_driver_ids[] = { +- { +- .name = "avs_i2s_test", +- }, +- {}, ++ { .name = "avs_i2s_test" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_i2s_test_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/max98357a.c b/sound/soc/intel/avs/boards/max98357a.c +--- a/sound/soc/intel/avs/boards/max98357a.c ++++ b/sound/soc/intel/avs/boards/max98357a.c +@@ -136,10 +136,8 @@ static int avs_max98357a_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_max98357a_driver_ids[] = { +- { +- .name = "avs_max98357a", +- }, +- {}, ++ { .name = "avs_max98357a" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_max98357a_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/max98373.c b/sound/soc/intel/avs/boards/max98373.c +--- a/sound/soc/intel/avs/boards/max98373.c ++++ b/sound/soc/intel/avs/boards/max98373.c +@@ -191,10 +191,8 @@ static int avs_max98373_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_max98373_driver_ids[] = { +- { +- .name = "avs_max98373", +- }, +- {}, ++ { .name = "avs_max98373" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_max98373_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/max98927.c b/sound/soc/intel/avs/boards/max98927.c +--- a/sound/soc/intel/avs/boards/max98927.c ++++ b/sound/soc/intel/avs/boards/max98927.c +@@ -188,10 +188,8 @@ static int avs_max98927_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_max98927_driver_ids[] = { +- { +- .name = "avs_max98927", +- }, +- {}, ++ { .name = "avs_max98927" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_max98927_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/nau8825.c b/sound/soc/intel/avs/boards/nau8825.c +--- a/sound/soc/intel/avs/boards/nau8825.c ++++ b/sound/soc/intel/avs/boards/nau8825.c +@@ -293,10 +293,8 @@ static int avs_nau8825_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_nau8825_driver_ids[] = { +- { +- .name = "avs_nau8825", +- }, +- {}, ++ { .name = "avs_nau8825" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_nau8825_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/pcm3168a.c b/sound/soc/intel/avs/boards/pcm3168a.c +--- a/sound/soc/intel/avs/boards/pcm3168a.c ++++ b/sound/soc/intel/avs/boards/pcm3168a.c +@@ -132,10 +132,8 @@ static int avs_pcm3168a_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_pcm3168a_driver_ids[] = { +- { +- .name = "avs_pcm3168a", +- }, +- {}, ++ { .name = "avs_pcm3168a" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_pcm3168a_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/probe.c b/sound/soc/intel/avs/boards/probe.c +--- a/sound/soc/intel/avs/boards/probe.c ++++ b/sound/soc/intel/avs/boards/probe.c +@@ -64,7 +64,7 @@ static const struct platform_device_id avs_probe_mb_driver_ids[] = { + { + .name = "avs_probe_mb", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_probe_mb_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt274.c b/sound/soc/intel/avs/boards/rt274.c +--- a/sound/soc/intel/avs/boards/rt274.c ++++ b/sound/soc/intel/avs/boards/rt274.c +@@ -261,7 +261,7 @@ static const struct platform_device_id avs_rt274_driver_ids[] = { + { + .name = "avs_rt274", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt274_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt286.c b/sound/soc/intel/avs/boards/rt286.c +--- a/sound/soc/intel/avs/boards/rt286.c ++++ b/sound/soc/intel/avs/boards/rt286.c +@@ -231,7 +231,7 @@ static const struct platform_device_id avs_rt286_driver_ids[] = { + { + .name = "avs_rt286", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt286_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt298.c b/sound/soc/intel/avs/boards/rt298.c +--- a/sound/soc/intel/avs/boards/rt298.c ++++ b/sound/soc/intel/avs/boards/rt298.c +@@ -250,7 +250,7 @@ static const struct platform_device_id avs_rt298_driver_ids[] = { + { + .name = "avs_rt298", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt298_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5514.c b/sound/soc/intel/avs/boards/rt5514.c +--- a/sound/soc/intel/avs/boards/rt5514.c ++++ b/sound/soc/intel/avs/boards/rt5514.c +@@ -178,7 +178,7 @@ static const struct platform_device_id avs_rt5514_driver_ids[] = { + { + .name = "avs_rt5514", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5514_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5640.c b/sound/soc/intel/avs/boards/rt5640.c +--- a/sound/soc/intel/avs/boards/rt5640.c ++++ b/sound/soc/intel/avs/boards/rt5640.c +@@ -252,7 +252,7 @@ static const struct platform_device_id avs_rt5640_driver_ids[] = { + { + .name = "avs_rt5640", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5640_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5663.c b/sound/soc/intel/avs/boards/rt5663.c +--- a/sound/soc/intel/avs/boards/rt5663.c ++++ b/sound/soc/intel/avs/boards/rt5663.c +@@ -249,7 +249,7 @@ static const struct platform_device_id avs_rt5663_driver_ids[] = { + { + .name = "avs_rt5663", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5663_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5682.c b/sound/soc/intel/avs/boards/rt5682.c +--- a/sound/soc/intel/avs/boards/rt5682.c ++++ b/sound/soc/intel/avs/boards/rt5682.c +@@ -325,7 +325,7 @@ static const struct platform_device_id avs_rt5682_driver_ids[] = { + { + .name = "avs_rt5682", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5682_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/ssm4567.c b/sound/soc/intel/avs/boards/ssm4567.c +--- a/sound/soc/intel/avs/boards/ssm4567.c ++++ b/sound/soc/intel/avs/boards/ssm4567.c +@@ -180,7 +180,7 @@ static const struct platform_device_id avs_ssm4567_driver_ids[] = { + { + .name = "avs_ssm4567", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_ssm4567_driver_ids); + +diff --git a/sound/soc/intel/avs/control.c b/sound/soc/intel/avs/control.c +--- a/sound/soc/intel/avs/control.c ++++ b/sound/soc/intel/avs/control.c +@@ -27,7 +27,7 @@ static struct avs_path_module *avs_get_volume_module(struct avs_dev *adev, u32 i + struct avs_path_pipeline *ppl; + struct avs_path_module *mod; + +- spin_lock(&adev->path_list_lock); ++ guard(spinlock)(&adev->path_list_lock); + list_for_each_entry(path, &adev->path_list, node) { + list_for_each_entry(ppl, &path->ppl_list, node) { + list_for_each_entry(mod, &ppl->mod_list, node) { +@@ -35,14 +35,11 @@ static struct avs_path_module *avs_get_volume_module(struct avs_dev *adev, u32 i + + if ((guid_equal(type, &AVS_PEAKVOL_MOD_UUID) || + guid_equal(type, &AVS_GAIN_MOD_UUID)) && +- mod->template->ctl_id == id) { +- spin_unlock(&adev->path_list_lock); ++ mod->template->ctl_id == id) + return mod; +- } + } + } + } +- spin_unlock(&adev->path_list_lock); + + return NULL; + } +diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c +--- a/sound/soc/intel/avs/core.c ++++ b/sound/soc/intel/avs/core.c +@@ -14,6 +14,7 @@ + // foundation of this driver + // + ++#include + #include + #include + #include +@@ -273,7 +274,7 @@ static irqreturn_t avs_hda_interrupt(struct hdac_bus *bus) + if (snd_hdac_bus_handle_stream_irq(bus, status, hdac_update_stream)) + ret = IRQ_HANDLED; + +- spin_lock_irq(&bus->reg_lock); ++ guard(spinlock_irq)(&bus->reg_lock); + /* Clear RIRB interrupt. */ + status = snd_hdac_chip_readb(bus, RIRBSTS); + if (status & RIRB_INT_MASK) { +@@ -283,7 +284,6 @@ static irqreturn_t avs_hda_interrupt(struct hdac_bus *bus) + ret = IRQ_HANDLED; + } + +- spin_unlock_irq(&bus->reg_lock); + return ret; + } + +diff --git a/sound/soc/intel/avs/debug.h b/sound/soc/intel/avs/debug.h +--- a/sound/soc/intel/avs/debug.h ++++ b/sound/soc/intel/avs/debug.h +@@ -9,6 +9,7 @@ + #ifndef __SOUND_SOC_INTEL_AVS_DEBUG_H + #define __SOUND_SOC_INTEL_AVS_DEBUG_H + ++#include + #include "messages.h" + #include "registers.h" + +@@ -26,14 +27,9 @@ struct avs_dev; + + static inline int avs_log_buffer_status_locked(struct avs_dev *adev, union avs_notify_msg *msg) + { +- unsigned long flags; +- int ret; ++ guard(spinlock_irqsave)(&adev->trace_lock); + +- spin_lock_irqsave(&adev->trace_lock, flags); +- ret = avs_dsp_op(adev, log_buffer_status, msg); +- spin_unlock_irqrestore(&adev->trace_lock, flags); +- +- return ret; ++ return avs_dsp_op(adev, log_buffer_status, msg); + } + + struct avs_apl_log_buffer_layout { +diff --git a/sound/soc/intel/avs/debugfs.c b/sound/soc/intel/avs/debugfs.c +--- a/sound/soc/intel/avs/debugfs.c ++++ b/sound/soc/intel/avs/debugfs.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -251,24 +252,22 @@ static int strace_release(struct inode *inode, struct file *file) + union avs_notify_msg msg = AVS_NOTIFICATION(LOG_BUFFER_STATUS); + struct avs_dev *adev = file->private_data; + unsigned long resource_mask; +- unsigned long flags, i; ++ unsigned long i; + u32 num_cores; + + resource_mask = adev->logged_resources; + num_cores = adev->hw_cfg.dsp_cores; + +- spin_lock_irqsave(&adev->trace_lock, flags); ++ scoped_guard(spinlock_irqsave, &adev->trace_lock) { ++ /* Gather any remaining logs. */ ++ for_each_set_bit(i, &resource_mask, num_cores) { ++ msg.log.core = i; ++ avs_dsp_op(adev, log_buffer_status, &msg); ++ } + +- /* Gather any remaining logs. */ +- for_each_set_bit(i, &resource_mask, num_cores) { +- msg.log.core = i; +- avs_dsp_op(adev, log_buffer_status, &msg); ++ kfifo_free(&adev->trace_fifo); + } + +- kfifo_free(&adev->trace_fifo); +- +- spin_unlock_irqrestore(&adev->trace_lock, flags); +- + module_put(adev->dev->driver->owner); + return 0; + } +diff --git a/sound/soc/intel/avs/ipc.c b/sound/soc/intel/avs/ipc.c +--- a/sound/soc/intel/avs/ipc.c ++++ b/sound/soc/intel/avs/ipc.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -99,39 +100,39 @@ static void avs_dsp_recovery(struct avs_dev *adev) + unsigned int core_mask; + int ret; + +- mutex_lock(&adev->comp_list_mutex); +- /* disconnect all running streams */ +- list_for_each_entry(acomp, &adev->comp_list, node) { +- struct snd_soc_pcm_runtime *rtd; +- struct snd_soc_card *card; ++ scoped_guard(mutex, &adev->comp_list_mutex) { ++ /* disconnect all running streams */ ++ list_for_each_entry(acomp, &adev->comp_list, node) { ++ struct snd_soc_pcm_runtime *rtd; ++ struct snd_soc_card *card; + +- card = acomp->base.card; +- if (!card) +- continue; +- +- for_each_card_rtds(card, rtd) { +- struct snd_pcm *pcm; +- int dir; +- +- pcm = rtd->pcm; +- if (!pcm || rtd->dai_link->no_pcm) ++ card = acomp->base->card; ++ if (!card) + continue; + +- for_each_pcm_streams(dir) { +- struct snd_pcm_substream *substream; ++ for_each_card_rtds(card, rtd) { ++ struct snd_pcm *pcm; ++ int dir; + +- substream = pcm->streams[dir].substream; +- if (!substream || !substream->runtime) ++ pcm = rtd->pcm; ++ if (!pcm || rtd->dai_link->no_pcm) + continue; + +- /* No need for _irq() as we are in nonatomic context. */ +- snd_pcm_stream_lock(substream); +- snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); +- snd_pcm_stream_unlock(substream); ++ for_each_pcm_streams(dir) { ++ struct snd_pcm_substream *substream; ++ ++ substream = pcm->streams[dir].substream; ++ if (!substream || !substream->runtime) ++ continue; ++ ++ /* No need for _irq() as we are in nonatomic context. */ ++ snd_pcm_stream_lock(substream); ++ snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); ++ snd_pcm_stream_unlock(substream); ++ } + } + } + } +- mutex_unlock(&adev->comp_list_mutex); + + /* forcibly shutdown all cores */ + core_mask = GENMASK(adev->hw_cfg.dsp_cores - 1, 0); +@@ -397,7 +398,7 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + if (!ipc->ready) + return -EPERM; + +- mutex_lock(&ipc->msg_mutex); ++ guard(mutex)(&ipc->msg_mutex); + + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, reply); +@@ -412,7 +413,7 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + /* Same treatment as on exception, just stack_dump=0. */ + avs_dsp_exception_caught(adev, &msg); + } +- goto exit; ++ return ret; + } + + ret = ipc->rx.rsp.status; +@@ -436,8 +437,6 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + memcpy(reply->data, ipc->rx.data, reply->size); + } + +-exit: +- mutex_unlock(&ipc->msg_mutex); + return ret; + } + +@@ -501,7 +500,7 @@ static int avs_dsp_do_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *req + struct avs_ipc *ipc = adev->ipc; + int ret; + +- mutex_lock(&ipc->msg_mutex); ++ guard(mutex)(&ipc->msg_mutex); + + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, NULL); +@@ -522,8 +521,6 @@ static int avs_dsp_do_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *req + dev_err(adev->dev, "%s (0x%08x 0x%08x) failed: %d\n", + name, request->glb.primary, request->glb.ext.val, ret); + +- mutex_unlock(&ipc->msg_mutex); +- + return ret; + } + +diff --git a/sound/soc/intel/avs/loader.c b/sound/soc/intel/avs/loader.c +--- a/sound/soc/intel/avs/loader.c ++++ b/sound/soc/intel/avs/loader.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -630,15 +631,15 @@ static int avs_load_firmware(struct avs_dev *adev, bool purge) + if (ret) + goto reenable_gating; + +- mutex_lock(&adev->comp_list_mutex); +- list_for_each_entry(acomp, &adev->comp_list, node) { +- struct avs_tplg *tplg = acomp->tplg; ++ scoped_guard(mutex, &adev->comp_list_mutex) { ++ list_for_each_entry(acomp, &adev->comp_list, node) { ++ struct avs_tplg *tplg = acomp->tplg; + +- ret = avs_dsp_load_libraries(adev, tplg->libs, tplg->num_libs); +- if (ret < 0) +- break; ++ ret = avs_dsp_load_libraries(adev, tplg->libs, tplg->num_libs); ++ if (ret < 0) ++ break; ++ } + } +- mutex_unlock(&adev->comp_list_mutex); + + reenable_gating: + avs_hda_l1sen_enable(adev, true); +diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c +--- a/sound/soc/intel/avs/path.c ++++ b/sound/soc/intel/avs/path.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -69,16 +70,13 @@ avs_path_find_path(struct avs_dev *adev, const char *name, u32 template_id) + if (!template) + return NULL; + +- spin_lock(&adev->path_list_lock); ++ guard(spinlock)(&adev->path_list_lock); + /* Only one variant of given path template may be instantiated at a time. */ + list_for_each_entry(path, &adev->path_list, node) { +- if (path->template->owner == template) { +- spin_unlock(&adev->path_list_lock); ++ if (path->template->owner == template) + return path; +- } + } + +- spin_unlock(&adev->path_list_lock); + return NULL; + } + +@@ -1305,7 +1303,7 @@ void avs_path_free(struct avs_path *path) + struct avs_path *cpath, *csave; + struct avs_dev *adev = path->owner; + +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + + /* Free all condpaths this path spawned. */ + list_for_each_entry_safe(cpath, csave, &path->source_list, source_node) +@@ -1314,8 +1312,6 @@ void avs_path_free(struct avs_path *path) + avs_condpath_free(path->owner, cpath); + + avs_path_free_unlocked(path); +- +- mutex_unlock(&adev->path_mutex); + } + + struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, +@@ -1334,13 +1330,13 @@ struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, + } + + /* Serialize path and its components creation. */ +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + /* Satisfy needs of avs_path_find_tplg(). */ +- mutex_lock(&adev->comp_list_mutex); ++ guard(mutex)(&adev->comp_list_mutex); + + path = avs_path_create_unlocked(adev, dma_id, variant); + if (IS_ERR(path)) +- goto exit; ++ return path; + + ret = avs_condpaths_walk_all(adev, path); + if (ret) { +@@ -1348,10 +1344,6 @@ struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, + path = ERR_PTR(ret); + } + +-exit: +- mutex_unlock(&adev->comp_list_mutex); +- mutex_unlock(&adev->path_mutex); +- + return path; + } + +@@ -1496,15 +1488,13 @@ static void avs_condpaths_pause(struct avs_dev *adev, struct avs_path *path) + { + struct avs_path *cpath; + +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + + /* If either source or sink stops, so do the attached conditional paths. */ + list_for_each_entry(cpath, &path->source_list, source_node) + avs_condpath_pause(adev, cpath); + list_for_each_entry(cpath, &path->sink_list, sink_node) + avs_condpath_pause(adev, cpath); +- +- mutex_unlock(&adev->path_mutex); + } + + int avs_path_pause(struct avs_path *path) +@@ -1560,7 +1550,7 @@ static void avs_condpaths_run(struct avs_dev *adev, struct avs_path *path, int t + { + struct avs_path *cpath; + +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + + /* Run conditional paths only if source and sink are both running. */ + list_for_each_entry(cpath, &path->source_list, source_node) +@@ -1572,8 +1562,6 @@ static void avs_condpaths_run(struct avs_dev *adev, struct avs_path *path, int t + if (cpath->source->state == AVS_PPL_STATE_RUNNING && + cpath->sink->state == AVS_PPL_STATE_RUNNING) + avs_condpath_run(adev, cpath, trigger); +- +- mutex_unlock(&adev->path_mutex); + } + + int avs_path_run(struct avs_path *path, int trigger) +diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c +--- a/sound/soc/intel/avs/pcm.c ++++ b/sound/soc/intel/avs/pcm.c +@@ -958,7 +958,7 @@ static const struct file_operations topology_name_fops = { + static int avs_component_load_libraries(struct avs_soc_component *acomp) + { + struct avs_tplg *tplg = acomp->tplg; +- struct avs_dev *adev = to_avs_dev(acomp->base.dev); ++ struct avs_dev *adev = to_avs_dev(acomp->base->dev); + int ret; + + if (!tplg->num_libs) +@@ -1387,25 +1387,28 @@ int avs_register_component(struct device *dev, const char *name, + struct snd_soc_dai_driver *cpu_dais, int num_cpu_dais) + { + struct avs_soc_component *acomp; +- int ret; ++ const char *comp_name; + + acomp = devm_kzalloc(dev, sizeof(*acomp), GFP_KERNEL); + if (!acomp) + return -ENOMEM; + +- acomp->base.name = devm_kstrdup(dev, name, GFP_KERNEL); +- if (!acomp->base.name) ++ acomp->base = snd_soc_component_alloc(dev); ++ if (!acomp->base) ++ return -ENOMEM; ++ ++ comp_name = devm_kstrdup(dev, name, GFP_KERNEL); ++ if (!comp_name) + return -ENOMEM; + + INIT_LIST_HEAD(&acomp->node); + + drv->use_dai_pcm_id = !obsolete_card_names; + +- ret = snd_soc_component_initialize(&acomp->base, drv, dev); +- if (ret < 0) +- return ret; ++ snd_soc_component_set_name(acomp->base, comp_name); ++ snd_soc_component_set_priv(acomp->base, acomp); + +- return snd_soc_add_component(&acomp->base, cpu_dais, num_cpu_dais); ++ return snd_soc_register_component(acomp->base, drv, cpu_dais, num_cpu_dais); + } + + static struct snd_soc_dai_driver dmic_cpu_dais[] = { +diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c +--- a/sound/soc/intel/avs/probes.c ++++ b/sound/soc/intel/avs/probes.c +@@ -144,7 +144,7 @@ static int avs_probe_compr_set_params(struct snd_compr_stream *cstream, + ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); + if (ret < 0) + return ret; +- bps = snd_pcm_format_physical_width((__force snd_pcm_format_t)params->codec.format); ++ bps = snd_pcm_format_physical_width(params->codec.format); + if (bps < 0) + return bps; + format_val = snd_hdac_stream_format(params->codec.ch_out, bps, params->codec.sample_rate); +@@ -296,19 +296,19 @@ static const struct snd_soc_component_driver avs_probe_component_driver = { + int avs_register_probe_component(struct avs_dev *adev, const char *name) + { + struct snd_soc_component *component; +- int ret; ++ const char *comp_name; + +- component = devm_kzalloc(adev->dev, sizeof(*component), GFP_KERNEL); ++ component = snd_soc_component_alloc(adev->dev); + if (!component) + return -ENOMEM; + +- component->name = devm_kstrdup(adev->dev, name, GFP_KERNEL); +- if (!component->name) ++ comp_name = devm_kstrdup(adev->dev, name, GFP_KERNEL); ++ if (!comp_name) + return -ENOMEM; + +- ret = snd_soc_component_initialize(component, &avs_probe_component_driver, adev->dev); +- if (ret) +- return ret; ++ snd_soc_component_set_name(component, comp_name); + +- return snd_soc_add_component(component, probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); ++ return snd_soc_register_component(component, ++ &avs_probe_component_driver, ++ probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); + } +diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c +--- a/sound/soc/intel/avs/topology.c ++++ b/sound/soc/intel/avs/topology.c +@@ -2222,7 +2222,7 @@ struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp) + + int avs_load_topology(struct snd_soc_component *comp, const char *filename) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw, filename, comp->dev); +@@ -2235,7 +2235,6 @@ int avs_load_topology(struct snd_soc_component *comp, const char *filename) + if (ret < 0) + dev_err(comp->dev, "load topology \"%s\" failed: %d\n", filename, ret); + +- release_firmware(fw); + return ret; + } + +diff --git a/sound/soc/intel/avs/utils.c b/sound/soc/intel/avs/utils.c +--- a/sound/soc/intel/avs/utils.c ++++ b/sound/soc/intel/avs/utils.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -48,13 +49,12 @@ int avs_get_module_entry(struct avs_dev *adev, const guid_t *uuid, struct avs_mo + { + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_entry_index(adev, uuid); + if (idx >= 0) + memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); + +- mutex_unlock(&adev->modres_mutex); + return (idx < 0) ? idx : 0; + } + +@@ -62,13 +62,12 @@ int avs_get_module_id_entry(struct avs_dev *adev, u32 module_id, struct avs_modu + { + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx >= 0) + memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); + +- mutex_unlock(&adev->modres_mutex); + return (idx < 0) ? idx : 0; + } + +@@ -86,13 +85,12 @@ bool avs_is_module_ida_empty(struct avs_dev *adev, u32 module_id) + bool ret = false; + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx >= 0) + ret = ida_is_empty(adev->mod_idas[idx]); + +- mutex_unlock(&adev->modres_mutex); + return ret; + } + +@@ -163,68 +161,57 @@ int avs_module_info_init(struct avs_dev *adev, bool purge) + if (ret) + return AVS_IPC_RET(ret); + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + ret = avs_module_ida_alloc(adev, info, purge); + if (ret < 0) { + dev_err(adev->dev, "initialize module idas failed: %d\n", ret); +- goto exit; ++ return ret; + } + + /* Refresh current information with newly received table. */ + kfree(adev->mods_info); + adev->mods_info = info; + +-exit: +- mutex_unlock(&adev->modres_mutex); + return ret; + } + + void avs_module_info_free(struct avs_dev *adev) + { +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + avs_module_ida_destroy(adev); + kfree(adev->mods_info); + adev->mods_info = NULL; +- +- mutex_unlock(&adev->modres_mutex); + } + + int avs_module_id_alloc(struct avs_dev *adev, u16 module_id) + { +- int ret, idx, max_id; ++ int idx, max_id; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx == -ENOENT) { + dev_err(adev->dev, "invalid module id: %d", module_id); +- ret = -EINVAL; +- goto exit; ++ return -EINVAL; + } + max_id = adev->mods_info->entries[idx].instance_max_count - 1; +- ret = ida_alloc_max(adev->mod_idas[idx], max_id, GFP_KERNEL); +-exit: +- mutex_unlock(&adev->modres_mutex); +- return ret; ++ ++ return ida_alloc_max(adev->mod_idas[idx], max_id, GFP_KERNEL); + } + + void avs_module_id_free(struct avs_dev *adev, u16 module_id, u8 instance_id) + { + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); +- if (idx == -ENOENT) { ++ if (idx == -ENOENT) + dev_err(adev->dev, "invalid module id: %d", module_id); +- goto exit; +- } +- +- ida_free(adev->mod_idas[idx], instance_id); +-exit: +- mutex_unlock(&adev->modres_mutex); ++ else ++ ida_free(adev->mod_idas[idx], instance_id); + } + + /* +diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c +--- a/sound/soc/intel/boards/sof_sdw.c ++++ b/sound/soc/intel/boards/sof_sdw.c +@@ -1288,7 +1288,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) + goto err_dai; + } + +- ret = asoc_sdw_parse_sdw_endpoints(card, sof_aux, sof_dais, sof_ends, &num_confs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); + if (ret < 0) + goto err_end; + +@@ -1483,12 +1483,12 @@ static int mc_probe(struct platform_device *pdev) + dmi_check_system(sof_sdw_quirk_table); + + if (quirk_override != -1) { +- dev_info(card->dev, "Overriding quirk 0x%lx => 0x%x\n", ++ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + sof_sdw_quirk, quirk_override); + sof_sdw_quirk = quirk_override; + } + +- log_quirks(card->dev); ++ log_quirks(&pdev->dev); + + ctx->mc_quirk = sof_sdw_quirk; + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ +@@ -1507,13 +1507,13 @@ static int mc_probe(struct platform_device *pdev) + for (i = 0; i < ctx->codec_info_list_count; i++) + amp_num += codec_info_list[i].amp_num; + +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + " cfg-amp:%d", amp_num); + if (!card->components) + return -ENOMEM; + + if (mach->mach_params.dmic_num) { +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%s mic:dmic cfg-mics:%d", + card->components, + mach->mach_params.dmic_num); +@@ -1524,7 +1524,7 @@ static int mc_probe(struct platform_device *pdev) + /* Register the card */ + ret = devm_snd_soc_register_card(card->dev, card); + if (ret) { +- dev_err_probe(card->dev, ret, "snd_soc_register_card failed %d\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed %d\n", ret); + asoc_sdw_mc_dailink_exit_loop(card); + return ret; + } +@@ -1542,8 +1542,8 @@ static void mc_remove(struct platform_device *pdev) + } + + static const struct platform_device_id mc_id_table[] = { +- { "sof_sdw", }, +- {} ++ { .name = "sof_sdw" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, mc_id_table); + +diff --git a/sound/soc/intel/catpt/core.h b/sound/soc/intel/catpt/core.h +--- a/sound/soc/intel/catpt/core.h ++++ b/sound/soc/intel/catpt/core.h +@@ -139,9 +139,7 @@ int catpt_dsp_send_msg(struct catpt_dev *cdev, struct catpt_ipc_msg request, + + int catpt_first_boot_firmware(struct catpt_dev *cdev); + int catpt_boot_firmware(struct catpt_dev *cdev, bool restore); +-int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan); +-int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan); +-int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan); ++int catpt_store_firmware_context(struct catpt_dev *cdev); + int catpt_coredump(struct catpt_dev *cdev); + + #include +diff --git a/sound/soc/intel/catpt/device.c b/sound/soc/intel/catpt/device.c +--- a/sound/soc/intel/catpt/device.c ++++ b/sound/soc/intel/catpt/device.c +@@ -28,44 +28,17 @@ + static int catpt_do_suspend(struct device *dev) + { + struct catpt_dev *cdev = dev_get_drvdata(dev); +- struct dma_chan *chan; + int ret; + +- chan = catpt_dma_request_config_chan(cdev); +- if (IS_ERR(chan)) +- return PTR_ERR(chan); +- + memset(&cdev->dx_ctx, 0, sizeof(cdev->dx_ctx)); + ret = catpt_ipc_enter_dxstate(cdev, CATPT_DX_STATE_D3, &cdev->dx_ctx); +- if (ret) { +- ret = CATPT_IPC_RET(ret); +- goto release_dma_chan; +- } +- +- ret = catpt_dsp_stall(cdev, true); + if (ret) +- goto release_dma_chan; ++ return CATPT_IPC_RET(ret); + +- ret = catpt_store_memdumps(cdev, chan); +- if (ret) { +- dev_err(cdev->dev, "store memdumps failed: %d\n", ret); +- goto release_dma_chan; +- } +- +- ret = catpt_store_module_states(cdev, chan); +- if (ret) { +- dev_err(cdev->dev, "store module states failed: %d\n", ret); +- goto release_dma_chan; +- } +- +- ret = catpt_store_streams_context(cdev, chan); +- if (ret) +- dev_err(cdev->dev, "store streams ctx failed: %d\n", ret); +- +-release_dma_chan: +- dma_release_channel(chan); ++ ret = catpt_store_firmware_context(cdev); + if (ret) + return ret; ++ + return catpt_dsp_power_down(cdev); + } + +diff --git a/sound/soc/intel/catpt/loader.c b/sound/soc/intel/catpt/loader.c +--- a/sound/soc/intel/catpt/loader.c ++++ b/sound/soc/intel/catpt/loader.c +@@ -26,7 +26,7 @@ struct catpt_fw_hdr { + u32 reserved[4]; + } __packed; + +-struct catpt_fw_mod_hdr { ++struct catpt_fw_module_hdr { + char signature[FW_SIGNATURE_SIZE]; + u32 mod_size; + u32 blocks; +@@ -81,7 +81,7 @@ catpt_request_region(struct resource *root, resource_size_t size) + return __request_region(root, addr, size, NULL, 0); + } + +-int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + { + struct catpt_stream_runtime *stream; + +@@ -108,7 +108,7 @@ int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) + { + int i; + +@@ -138,7 +138,7 @@ int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_store_dram_data(struct catpt_dev *cdev, struct dma_chan *chan) + { + int i; + +@@ -171,6 +171,39 @@ int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + ++int catpt_store_firmware_context(struct catpt_dev *cdev) ++{ ++ struct dma_chan *chan; ++ int ret; ++ ++ chan = catpt_dma_request_config_chan(cdev); ++ if (IS_ERR(chan)) ++ return PTR_ERR(chan); ++ ++ ret = catpt_dsp_stall(cdev, true); ++ if (ret) ++ goto exit; ++ ++ ret = catpt_store_dram_data(cdev, chan); ++ if (ret) { ++ dev_err(cdev->dev, "store memdumps failed: %d\n", ret); ++ goto exit; ++ } ++ ++ ret = catpt_store_module_states(cdev, chan); ++ if (ret) { ++ dev_err(cdev->dev, "store module states failed: %d\n", ret); ++ goto exit; ++ } ++ ++ ret = catpt_store_streams_context(cdev, chan); ++ if (ret) ++ dev_err(cdev->dev, "store streams ctx failed: %d\n", ret); ++exit: ++ dma_release_channel(chan); ++ return ret; ++} ++ + static int + catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + { +@@ -199,7 +232,7 @@ catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-static int catpt_restore_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_restore_dram_data(struct catpt_dev *cdev, struct dma_chan *chan) + { + int i; + +@@ -234,9 +267,9 @@ static int catpt_restore_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-static int catpt_restore_fwimage(struct catpt_dev *cdev, +- struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_block_hdr *blk) ++static int catpt_restore_dram_rodata(struct catpt_dev *cdev, ++ struct dma_chan *chan, dma_addr_t paddr, ++ struct catpt_fw_block_hdr *blk) + { + struct resource r1 = {}; + int i; +@@ -324,28 +357,26 @@ static int catpt_load_block(struct catpt_dev *cdev, + + static int catpt_restore_basefw(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_mod_hdr *basefw) ++ struct catpt_fw_module_hdr *basefw) + { +- u32 offset = sizeof(*basefw); ++ u32 off = sizeof(*basefw); + int ret, i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + basefw, sizeof(*basefw), false); + +- /* restore basefw image */ ++ /* Restore IRAM and .rodata for DRAM based on the firmware image. */ + for (i = 0; i < basefw->blocks; i++) { + struct catpt_fw_block_hdr *blk; + +- blk = (struct catpt_fw_block_hdr *)((u8 *)basefw + offset); ++ blk = (struct catpt_fw_block_hdr *)((u8 *)basefw + off); + + switch (blk->ram_type) { + case CATPT_RAM_TYPE_IRAM: +- ret = catpt_load_block(cdev, chan, paddr + offset, +- blk, false); ++ ret = catpt_load_block(cdev, chan, paddr + off, blk, false); + break; + default: +- ret = catpt_restore_fwimage(cdev, chan, paddr + offset, +- blk); ++ ret = catpt_restore_dram_rodata(cdev, chan, paddr + off, blk); + break; + } + +@@ -354,11 +385,11 @@ static int catpt_restore_basefw(struct catpt_dev *cdev, + return ret; + } + +- offset += sizeof(*blk) + blk->size; ++ off += sizeof(*blk) + blk->size; + } + +- /* then proceed with memory dumps */ +- ret = catpt_restore_memdumps(cdev, chan); ++ /* Then proceed with DRAM .data saved before D3. */ ++ ret = catpt_restore_dram_data(cdev, chan); + if (ret) + dev_err(cdev->dev, "restore memdumps failed: %d\n", ret); + +@@ -367,9 +398,9 @@ static int catpt_restore_basefw(struct catpt_dev *cdev, + + static int catpt_restore_module(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_mod_hdr *mod) ++ struct catpt_fw_module_hdr *mod) + { +- u32 offset = sizeof(*mod); ++ u32 off = sizeof(*mod); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, +@@ -379,7 +410,7 @@ static int catpt_restore_module(struct catpt_dev *cdev, + struct catpt_fw_block_hdr *blk; + int ret; + +- blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset); ++ blk = (struct catpt_fw_block_hdr *)((u8 *)mod + off); + + switch (blk->ram_type) { + case CATPT_RAM_TYPE_INSTANCE: +@@ -390,7 +421,7 @@ static int catpt_restore_module(struct catpt_dev *cdev, + ALIGN(blk->size, 4)); + break; + default: +- ret = catpt_load_block(cdev, chan, paddr + offset, ++ ret = catpt_load_block(cdev, chan, paddr + off, + blk, false); + break; + } +@@ -400,7 +431,7 @@ static int catpt_restore_module(struct catpt_dev *cdev, + return ret; + } + +- offset += sizeof(*blk) + blk->size; ++ off += sizeof(*blk) + blk->size; + } + + return 0; +@@ -408,10 +439,10 @@ static int catpt_restore_module(struct catpt_dev *cdev, + + static int catpt_load_module(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_mod_hdr *mod) ++ struct catpt_fw_module_hdr *mod) + { + struct catpt_module_type *type; +- u32 offset = sizeof(*mod); ++ u32 off = sizeof(*mod); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, +@@ -423,9 +454,9 @@ static int catpt_load_module(struct catpt_dev *cdev, + struct catpt_fw_block_hdr *blk; + int ret; + +- blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset); ++ blk = (struct catpt_fw_block_hdr *)((u8 *)mod + off); + +- ret = catpt_load_block(cdev, chan, paddr + offset, blk, true); ++ ret = catpt_load_block(cdev, chan, paddr + off, blk, true); + if (ret) { + dev_err(cdev->dev, "load block failed: %d\n", ret); + return ret; +@@ -440,7 +471,7 @@ static int catpt_load_module(struct catpt_dev *cdev, + type->state_size = blk->size; + } + +- offset += sizeof(*blk) + blk->size; ++ off += sizeof(*blk) + blk->size; + } + + /* init module type static info */ +@@ -457,17 +488,17 @@ static int catpt_restore_firmware(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_hdr *fw) + { +- u32 offset = sizeof(*fw); ++ u32 off = sizeof(*fw); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + fw, sizeof(*fw), false); + + for (i = 0; i < fw->modules; i++) { +- struct catpt_fw_mod_hdr *mod; ++ struct catpt_fw_module_hdr *mod; + int ret; + +- mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset); ++ mod = (struct catpt_fw_module_hdr *)((u8 *)fw + off); + if (strncmp(fw->signature, mod->signature, + FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "module signature mismatch\n"); +@@ -479,12 +510,10 @@ static int catpt_restore_firmware(struct catpt_dev *cdev, + + switch (mod->module_id) { + case CATPT_MODID_BASE_FW: +- ret = catpt_restore_basefw(cdev, chan, paddr + offset, +- mod); ++ ret = catpt_restore_basefw(cdev, chan, paddr + off, mod); + break; + default: +- ret = catpt_restore_module(cdev, chan, paddr + offset, +- mod); ++ ret = catpt_restore_module(cdev, chan, paddr + off, mod); + break; + } + +@@ -493,7 +522,7 @@ static int catpt_restore_firmware(struct catpt_dev *cdev, + return ret; + } + +- offset += sizeof(*mod) + mod->mod_size; ++ off += sizeof(*mod) + mod->mod_size; + } + + return 0; +@@ -503,17 +532,17 @@ static int catpt_load_firmware(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_hdr *fw) + { +- u32 offset = sizeof(*fw); ++ u32 off = sizeof(*fw); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + fw, sizeof(*fw), false); + + for (i = 0; i < fw->modules; i++) { +- struct catpt_fw_mod_hdr *mod; ++ struct catpt_fw_module_hdr *mod; + int ret; + +- mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset); ++ mod = (struct catpt_fw_module_hdr *)((u8 *)fw + off); + if (strncmp(fw->signature, mod->signature, + FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "module signature mismatch\n"); +@@ -523,44 +552,40 @@ static int catpt_load_firmware(struct catpt_dev *cdev, + if (mod->module_id > CATPT_MODID_LAST) + return -EINVAL; + +- ret = catpt_load_module(cdev, chan, paddr + offset, mod); ++ ret = catpt_load_module(cdev, chan, paddr + off, mod); + if (ret) { + dev_err(cdev->dev, "load module failed: %d\n", ret); + return ret; + } + +- offset += sizeof(*mod) + mod->mod_size; ++ off += sizeof(*mod) + mod->mod_size; + } + + return 0; + } + +-static int catpt_load_image(struct catpt_dev *cdev, struct dma_chan *chan, +- const char *name, const char *signature, +- bool restore) ++static int catpt_request_load_firmware(struct catpt_dev *cdev, struct dma_chan *chan, ++ const char *name, bool restore) + { + struct catpt_fw_hdr *fw; +- struct firmware *img; + dma_addr_t paddr; + void *vaddr; + int ret; + +- ret = request_firmware((const struct firmware **)&img, name, cdev->dev); ++ const struct firmware *img __free(firmware) = NULL; ++ ret = request_firmware(&img, name, cdev->dev); + if (ret) + return ret; + + fw = (struct catpt_fw_hdr *)img->data; +- if (strncmp(fw->signature, signature, FW_SIGNATURE_SIZE)) { ++ if (strncmp(fw->signature, FW_SIGNATURE, FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "firmware signature mismatch\n"); +- ret = -EINVAL; +- goto release_fw; ++ return -EINVAL; + } + + vaddr = dma_alloc_coherent(cdev->dev, img->size, &paddr, GFP_KERNEL); +- if (!vaddr) { +- ret = -ENOMEM; +- goto release_fw; +- } ++ if (!vaddr) ++ return -ENOMEM; + + memcpy(vaddr, img->data, img->size); + fw = (struct catpt_fw_hdr *)vaddr; +@@ -570,12 +595,10 @@ static int catpt_load_image(struct catpt_dev *cdev, struct dma_chan *chan, + ret = catpt_load_firmware(cdev, chan, paddr, fw); + + dma_free_coherent(cdev->dev, img->size, vaddr, paddr); +-release_fw: +- release_firmware(img); + return ret; + } + +-static int catpt_load_images(struct catpt_dev *cdev, bool restore) ++static int catpt_request_dma_load_firmware(struct catpt_dev *cdev, bool restore) + { + struct dma_chan *chan; + int ret; +@@ -584,8 +607,7 @@ static int catpt_load_images(struct catpt_dev *cdev, bool restore) + if (IS_ERR(chan)) + return PTR_ERR(chan); + +- ret = catpt_load_image(cdev, chan, cdev->spec->fw_name, +- FW_SIGNATURE, restore); ++ ret = catpt_request_load_firmware(cdev, chan, cdev->spec->fw_name, restore); + if (ret) + goto release_dma_chan; + +@@ -605,7 +627,7 @@ int catpt_boot_firmware(struct catpt_dev *cdev, bool restore) + + catpt_dsp_stall(cdev, true); + +- ret = catpt_load_images(cdev, restore); ++ ret = catpt_request_dma_load_firmware(cdev, restore); + if (ret) { + dev_err(cdev->dev, "load binaries failed: %d\n", ret); + return ret; +@@ -619,10 +641,10 @@ int catpt_boot_firmware(struct catpt_dev *cdev, bool restore) + if (!ret) { + dev_err(cdev->dev, "firmware ready timeout\n"); + return -ETIMEDOUT; +- /* Wake up does not mean FW is ready, an exception could occur. */ +- } else if (!cdev->ipc.ready) { +- return -EREMOTEIO; + } ++ /* Wake up does not mean FW is ready, an exception could occur. */ ++ if (!cdev->ipc.ready) ++ return -EREMOTEIO; + + /* update sram pg & clock once done booting */ + catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask); +diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c +--- a/sound/soc/intel/catpt/pcm.c ++++ b/sound/soc/intel/catpt/pcm.c +@@ -75,8 +75,8 @@ static struct catpt_stream_template *catpt_topology[] = { + static struct catpt_stream_template * + catpt_get_stream_template(struct snd_pcm_substream *substream) + { +- struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); +- struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtm, 0); ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + enum catpt_stream_type type; + + type = cpu_dai->driver->id; +@@ -159,11 +159,11 @@ static void catpt_stream_read_position(struct catpt_dev *cdev, + static void catpt_arrange_page_table(struct snd_pcm_substream *substream, + struct snd_dma_buffer *pgtbl) + { +- struct snd_pcm_runtime *rtm = substream->runtime; ++ struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *databuf = snd_pcm_get_dma_buf(substream); + int i, pages; + +- pages = snd_sgbuf_aligned_pages(rtm->dma_bytes); ++ pages = snd_sgbuf_aligned_pages(runtime->dma_bytes); + + for (i = 0; i < pages; i++) { + u32 pfn, offset; +@@ -386,7 +386,7 @@ static int catpt_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { +- struct snd_pcm_runtime *rtm = substream->runtime; ++ struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *dmab; + struct catpt_stream_runtime *stream; + struct catpt_audio_format afmt; +@@ -412,8 +412,8 @@ static int catpt_dai_hw_params(struct snd_pcm_substream *substream, + + memset(&rinfo, 0, sizeof(rinfo)); + rinfo.page_table_addr = stream->pgtbl.addr; +- rinfo.num_pages = DIV_ROUND_UP(rtm->dma_bytes, PAGE_SIZE); +- rinfo.size = rtm->dma_bytes; ++ rinfo.num_pages = DIV_ROUND_UP(runtime->dma_bytes, PAGE_SIZE); ++ rinfo.size = runtime->dma_bytes; + rinfo.offset = 0; + rinfo.ring_first_page_pfn = PFN_DOWN(snd_sgbuf_get_addr(dmab, 0)); + +@@ -544,11 +544,11 @@ void catpt_stream_update_position(struct catpt_dev *cdev, + struct catpt_notify_position *pos) + { + struct snd_pcm_substream *substream = stream->substream; +- struct snd_pcm_runtime *r = substream->runtime; ++ struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t dsppos, newpos; + int ret; + +- dsppos = bytes_to_frames(r, pos->stream_position); ++ dsppos = bytes_to_frames(runtime, pos->stream_position); + + if (!stream->prepared) + goto exit; +@@ -556,8 +556,8 @@ void catpt_stream_update_position(struct catpt_dev *cdev, + if (stream->template->type != CATPT_STRM_TYPE_RENDER) + goto exit; + +- if (dsppos >= r->buffer_size / 2) +- newpos = r->buffer_size / 2; ++ if (dsppos >= runtime->buffer_size / 2) ++ newpos = runtime->buffer_size / 2; + else + newpos = 0; + /* +@@ -565,7 +565,7 @@ void catpt_stream_update_position(struct catpt_dev *cdev, + * (buffer half consumed) update wp to allow stream progression. + */ + ret = catpt_ipc_set_write_pos(cdev, stream->info.stream_hw_id, +- frames_to_bytes(r, newpos), ++ frames_to_bytes(runtime, newpos), + false, false); + if (ret) { + dev_err(cdev->dev, "update position for stream %d failed: %d\n", +@@ -600,11 +600,11 @@ static const struct snd_pcm_hardware catpt_pcm_hardware = { + }; + + static int catpt_component_pcm_new(struct snd_soc_component *component, +- struct snd_soc_pcm_runtime *rtm) ++ struct snd_soc_pcm_runtime *rtd) + { + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + +- snd_pcm_set_managed_buffer_all(rtm->pcm, SNDRV_DMA_TYPE_DEV_SG, ++ snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV_SG, + cdev->dev, + catpt_pcm_hardware.buffer_bytes_max, + catpt_pcm_hardware.buffer_bytes_max); +@@ -615,9 +615,9 @@ static int catpt_component_pcm_new(struct snd_soc_component *component, + static int catpt_component_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + +- if (!rtm->dai_link->no_pcm) ++ if (!rtd->dai_link->no_pcm) + snd_soc_set_runtime_hwparams(substream, &catpt_pcm_hardware); + return 0; + } +@@ -626,13 +626,13 @@ static snd_pcm_uframes_t + catpt_component_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); +- struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtm, 0); ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct catpt_stream_runtime *stream; + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + u32 pos; + +- if (rtm->dai_link->no_pcm) ++ if (rtd->dai_link->no_pcm) + return 0; + + stream = snd_soc_dai_get_dma_data(cpu_dai, substream); +@@ -650,10 +650,10 @@ static const struct snd_soc_dai_ops catpt_fe_dai_ops = { + .trigger = catpt_dai_trigger, + }; + +-static int catpt_dai_pcm_new(struct snd_soc_pcm_runtime *rtm, ++static int catpt_dai_pcm_new(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *dai) + { +- struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtm, 0); ++ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct catpt_ssp_device_format devfmt; + struct catpt_dev *cdev = dev_get_drvdata(dai->dev); + int ret; +@@ -871,8 +871,7 @@ static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol) + return CATPT_IPC_RET(ret); + } + +-static int catpt_volume_info(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_info *uinfo) ++static int catpt_volume_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) + { + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = CATPT_CHANNELS_MAX; +@@ -1073,18 +1072,14 @@ int catpt_arm_stream_templates(struct catpt_dev *cdev) + int catpt_register_plat_component(struct catpt_dev *cdev) + { + struct snd_soc_component *component; +- int ret; + +- component = devm_kzalloc(cdev->dev, sizeof(*component), GFP_KERNEL); ++ component = snd_soc_component_alloc(cdev->dev); + if (!component) + return -ENOMEM; + +- ret = snd_soc_component_initialize(component, &catpt_comp_driver, +- cdev->dev); +- if (ret) +- return ret; ++ snd_soc_component_set_name(component, catpt_comp_driver.name); + +- component->name = catpt_comp_driver.name; +- return snd_soc_add_component(component, dai_drivers, +- ARRAY_SIZE(dai_drivers)); ++ return snd_soc_register_component(component, ++ &catpt_comp_driver, ++ dai_drivers, ARRAY_SIZE(dai_drivers)); + } +diff --git a/sound/soc/intel/common/soc-acpi-intel-arl-match.c b/sound/soc/intel/common/soc-acpi-intel-arl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-arl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-arl-match.c +@@ -572,6 +572,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_sdw_machines[] = { + .links = arl_rt711_l0_rt1316_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-rt711-l0-rt1316-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(3), +@@ -600,12 +601,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_sdw_machines[] = { + .links = arl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = 0x1, /* link0 required */ + .links = arl_sdca_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-rt711-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(2), +diff --git a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +@@ -717,24 +717,28 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .links = lnl_3_in_1_sdca, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt711-l0-rt1316-l23-rt714-l1.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2) | BIT(3), + .links = lnl_cs42l43_l0_cs35l56_l23, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l0-cs35l56-l23.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(1) | BIT(2) | BIT(3), + .links = lnl_cs42l43_l2_cs35l56x6_l13, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l2-cs35l56x6-l13.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(3), + .links = lnl_cs42l43_l0_cs35l56_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l0-cs35l56-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -748,12 +752,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .links = lnl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(2) | BIT(3), + .links = lnl_712_only, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt712-l2-rt1712-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -766,19 +772,22 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .link_mask = GENMASK(2, 0), + .links = lnl_sdw_rt1318_l12_rt714_l0, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-lnl-rt1318-l12-rt714-l0.tplg" ++ .sof_tplg_filename = "sof-lnl-rt1318-l12-rt714-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(2, 0), + .links = lnl_sdw_rt1320_l12_rt714_l0, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-lnl-rt1320-l12-rt714-l0.tplg" ++ .sof_tplg_filename = "sof-lnl-rt1320-l12-rt714-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(1), + .links = lnl_sdw_rt713_l0_rt1318_l1, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-lnl-rt713-l0-rt1318-l1.tplg" ++ .sof_tplg_filename = "sof-lnl-rt713-l0-rt1318-l1.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(1) | BIT(2), +diff --git a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c +@@ -1321,60 +1321,70 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .links = tac5572_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tac5572.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tac5672_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tac5672.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tac5682_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tac5682.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tas2783_link0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tas2783.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tas2883_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tas2883.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(3, 0), + .links = mtl_rt713_l0_rt1316_l12_rt1713_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1316-l12-rt1713-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(3, 0), + .links = mtl_rt713_l0_rt1318_l12_rt1713_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1318-l12-rt1713-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(1) | BIT(3), + .links = mtl_rt713_l0_rt1318_l1_rt1713_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1318-l1-rt1713-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(2, 0), + .links = mtl_rt713_l0_rt1316_l12, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1316-l12.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(3) | BIT(0), + .links = mtl_712_l0_1712_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt712-l0-rt1712-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -1395,7 +1405,8 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .link_mask = GENMASK(2, 0), + .links = mtl_sdw_rt1318_l12_rt714_l0, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-mtl-rt1318-l12-rt714-l0.tplg" ++ .sof_tplg_filename = "sof-mtl-rt1318-l12-rt714-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2) | BIT(3), +@@ -1455,12 +1466,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .links = mtl_3_in_1_sdca, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711-l0-rt1316-l23-rt714-l1.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = 0x9, /* 2 active links required */ + .links = mtl_rt711_l0_rt1316_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711-l0-rt1316-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -1474,18 +1487,21 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .links = mtl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2), + .links = rt5682_link2_max98373_link0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-sdw-rt5682-l2-max98373-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2), + .links = cs42l42_link0_max98363_link2, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-sdw-cs42l42-l0-max98363-l2.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + {}, + }; +diff --git a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +@@ -492,7 +492,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { + .links = ptl_rt722_l0_rt1320_l23, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-rt722-l0-rt1320-l23.tplg", +- .get_function_tplg_files = sof_sdw_get_tplg_files, ++ .machine_check = snd_soc_acpi_intel_no_function_topology, + }, + { + .link_mask = BIT(1) | BIT(2), +@@ -522,14 +522,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { + .links = ptl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(3), + .links = ptl_sdw_rt712_vb_l3_rt1320_l3, + .drv_name = "sof_sdw", +- .machine_check = snd_soc_acpi_intel_sdca_is_device_rt712_vb, ++ .machine_check = snd_soc_acpi_intel_rt712_vb_no_function_topology, + .sof_tplg_filename = "sof-ptl-rt712-l3-rt1320-l3.tplg", +- .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + {}, + }; +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +@@ -53,6 +53,14 @@ bool snd_soc_acpi_intel_no_function_topology(void *arg) + } + EXPORT_SYMBOL_NS(snd_soc_acpi_intel_no_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); + ++bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg) ++{ ++ return snd_soc_acpi_intel_sdca_is_device_rt712_vb(arg) && ++ snd_soc_acpi_intel_no_function_topology(arg); ++} ++EXPORT_SYMBOL_NS(snd_soc_acpi_intel_rt712_vb_no_function_topology, ++ "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); ++ + MODULE_DESCRIPTION("ASoC ACPI Intel SDCA quirks"); + MODULE_LICENSE("GPL"); + MODULE_IMPORT_NS("SND_SOC_SDCA"); +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +@@ -11,5 +11,6 @@ + + bool snd_soc_acpi_intel_sdca_is_device_rt712_vb(void *arg); + bool snd_soc_acpi_intel_no_function_topology(void *arg); ++bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg); + + #endif +diff --git a/sound/soc/intel/keembay/kmb_platform.c b/sound/soc/intel/keembay/kmb_platform.c +--- a/sound/soc/intel/keembay/kmb_platform.c ++++ b/sound/soc/intel/keembay/kmb_platform.c +@@ -873,6 +873,8 @@ static int kmb_plat_dai_probe(struct platform_device *pdev) + + if (kmb_i2s->use_pio) { + irq = platform_get_irq_optional(pdev, 0); ++ if (irq == -EPROBE_DEFER) ++ return irq; + if (irq > 0) { + ret = devm_request_irq(dev, irq, kmb_i2s_irq_handler, 0, + pdev->name, kmb_i2s); +diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c +--- a/sound/soc/loongson/loongson_card.c ++++ b/sound/soc/loongson/loongson_card.c +@@ -2,24 +2,131 @@ + // + // Loongson ASoC Audio Machine driver + // +-// Copyright (C) 2023 Loongson Technology Corporation Limited ++// Copyright (C) 2023-2026 Loongson Technology Corporation Limited + // Author: Yingkun Meng ++// Binbin Zhou + // + ++#include ++#include + #include ++#include ++#include ++#include + #include + #include +-#include +-#include +-#include + + static char codec_name[SND_ACPI_I2C_ID_LEN]; + + struct loongson_card_data { + struct snd_soc_card snd_card; + unsigned int mclk_fs; ++ struct gpio_desc *gpiod_hp_det; ++ struct gpio_desc *gpiod_hp_ctl; ++ struct gpio_desc *gpiod_spkr_en; ++ const struct loongson_card_config *cfg; + }; + ++struct loongson_card_config { ++ unsigned int fmt; ++ bool add_hp_jack; ++ bool add_dapm_widgets; ++ bool add_dapm_routes; ++}; ++ ++static const struct loongson_card_config ls2k1000_card_config = { ++ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBC_CFC, ++ .add_hp_jack = false, ++ .add_dapm_widgets = false, ++ .add_dapm_routes = false, ++}; ++ ++static const struct loongson_card_config ls2k0300_forever_pi_card_config = { ++ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC, ++ .add_hp_jack = false, ++ .add_dapm_widgets = false, ++ .add_dapm_routes = false, ++}; ++ ++static const struct loongson_card_config ls2k0300_dl2k0300b_card_config = { ++ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC, ++ .add_hp_jack = true, ++ .add_dapm_widgets = true, ++ .add_dapm_routes = true, ++}; ++ ++static int tegra_machine_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *k, int event) ++{ ++ struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); ++ struct loongson_card_data *priv = snd_soc_card_get_drvdata(card); ++ ++ if (!snd_soc_dapm_widget_name_cmp(w, "Speaker")) ++ gpiod_set_value_cansleep(priv->gpiod_spkr_en, ++ SND_SOC_DAPM_EVENT_ON(event)); ++ ++ if (!snd_soc_dapm_widget_name_cmp(w, "Headphone")) ++ gpiod_set_value_cansleep(priv->gpiod_hp_ctl, ++ SND_SOC_DAPM_EVENT_ON(event)); ++ ++ return 0; ++} ++ ++static const struct snd_soc_dapm_widget loongson_aosc_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone", tegra_machine_event), ++ SND_SOC_DAPM_SPK("Speaker", tegra_machine_event), ++}; ++ ++/* Headphones Jack */ ++ ++static struct snd_soc_jack loongson_asoc_hp_jack; ++ ++static struct snd_soc_jack_pin loongson_asoc_hp_jack_pins[] = { ++ { ++ .pin = "Headphone", ++ .mask = SND_JACK_HEADPHONE ++ }, ++ { ++ .pin = "Speaker", ++ .mask = SND_JACK_HEADPHONE, ++ .invert = 1 ++ }, ++}; ++ ++static struct snd_soc_jack_gpio loongson_asoc_hp_jack_gpio = { ++ .name = "Headphones detection", ++ .report = SND_JACK_HEADPHONE, ++ .debounce_time = 150, ++}; ++ ++static int loongson_asoc_machine_init(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct snd_soc_card *card = rtd->card; ++ struct loongson_card_data *ls_priv = snd_soc_card_get_drvdata(card); ++ int ret = 0; ++ ++ if (!ls_priv->cfg->add_hp_jack || !ls_priv->gpiod_hp_det) ++ return 0; ++ ++ ret = snd_soc_card_jack_new_pins(card, "Headphones Jack", ++ SND_JACK_HEADPHONE, ++ &loongson_asoc_hp_jack, ++ loongson_asoc_hp_jack_pins, ++ ARRAY_SIZE(loongson_asoc_hp_jack_pins)); ++ if (ret) { ++ dev_err(rtd->dev, "Headphones Jack creation failed: %d\n", ret); ++ return ret; ++ } ++ ++ loongson_asoc_hp_jack_gpio.desc = ls_priv->gpiod_hp_det; ++ ++ ret = snd_soc_jack_add_gpios(&loongson_asoc_hp_jack, 1, &loongson_asoc_hp_jack_gpio); ++ if (ret) ++ dev_err(rtd->dev, "Headphone GPIO not added: %d\n", ret); ++ ++ return ret; ++} ++ + static int loongson_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) + { +@@ -45,7 +152,7 @@ static int loongson_card_hw_params(struct snd_pcm_substream *substream, + return ret; + } + +- return 0; ++ return snd_soc_runtime_set_dai_fmt(rtd, ls_card->cfg->fmt); + } + + static const struct snd_soc_ops loongson_ops = { +@@ -61,8 +168,7 @@ static struct snd_soc_dai_link loongson_dai_links[] = { + { + .name = "Loongson Audio Port", + .stream_name = "Loongson Audio", +- .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF +- | SND_SOC_DAIFMT_CBC_CFC, ++ .init = loongson_asoc_machine_init, + SND_SOC_DAILINK_REG(analog), + .ops = &loongson_ops, + }, +@@ -123,16 +229,35 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data) + + static int loongson_card_parse_of(struct loongson_card_data *data) + { +- struct device_node *cpu, *codec; + struct snd_soc_card *card = &data->snd_card; ++ struct device_node *cpu, *codec; + struct device *dev = card->dev; + int ret, i; + ++ data->gpiod_hp_det = devm_gpiod_get_optional(dev, "hp-det", GPIOD_IN); ++ if (IS_ERR(data->gpiod_hp_det)) ++ return PTR_ERR(data->gpiod_hp_det); ++ ++ data->gpiod_hp_ctl = devm_gpiod_get_optional(dev, "hp-ctl", GPIOD_OUT_LOW); ++ if (IS_ERR(data->gpiod_hp_ctl)) ++ return PTR_ERR(data->gpiod_hp_ctl); ++ ++ data->gpiod_spkr_en = devm_gpiod_get_optional(dev, "spkr-en", GPIOD_OUT_LOW); ++ if (IS_ERR(data->gpiod_spkr_en)) ++ return PTR_ERR(data->gpiod_spkr_en); ++ ++ if (data->cfg->add_dapm_routes) { ++ ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); ++ if (ret) ++ return ret; ++ } ++ + cpu = of_get_child_by_name(dev->of_node, "cpu"); + if (!cpu) { + dev_err(dev, "platform property missing or invalid\n"); + return -EINVAL; + } ++ + codec = of_get_child_by_name(dev->of_node, "codec"); + if (!codec) { + dev_err(dev, "audio-codec property missing or invalid\n"); +@@ -177,12 +302,22 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) + if (!ls_priv) + return -ENOMEM; + ++ ls_priv->cfg = (const struct loongson_card_config *)device_get_match_data(dev); ++ if (!ls_priv->cfg) ++ return -EINVAL; ++ + card = &ls_priv->snd_card; + + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = loongson_dai_links; + card->num_links = ARRAY_SIZE(loongson_dai_links); ++ ++ if (ls_priv->cfg->add_dapm_widgets) { ++ card->dapm_widgets = loongson_aosc_dapm_widgets; ++ card->num_dapm_widgets = ARRAY_SIZE(loongson_aosc_dapm_widgets); ++ } ++ + snd_soc_card_set_drvdata(card, ls_priv); + + ret = device_property_read_string(dev, "model", &card->name); +@@ -202,7 +337,19 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) + } + + static const struct of_device_id loongson_asoc_dt_ids[] = { +- { .compatible = "loongson,ls-audio-card" }, ++ /* Loongson-2K1000/Loongson-2K2000/LS7A */ ++ { ++ .compatible = "loongson,ls-audio-card", ++ .data = &ls2k1000_card_config ++ }, ++ { ++ .compatible = "loongson,ls2k0300-forever-pi-audio-card", ++ .data = &ls2k0300_forever_pi_card_config ++ }, ++ { ++ .compatible = "loongson,ls2k0300-dl2k0300b-audio-card", ++ .data = &ls2k0300_dl2k0300b_card_config ++ }, + { /* sentinel */ }, + }; + MODULE_DEVICE_TABLE(of, loongson_asoc_dt_ids); +diff --git a/sound/soc/loongson/loongson_i2s_plat.c b/sound/soc/loongson/loongson_i2s_plat.c +--- a/sound/soc/loongson/loongson_i2s_plat.c ++++ b/sound/soc/loongson/loongson_i2s_plat.c +@@ -2,7 +2,7 @@ + // + // Loongson I2S controller master mode dirver(platform device) + // +-// Copyright (C) 2023-2024 Loongson Technology Corporation Limited ++// Copyright (C) 2023-2026 Loongson Technology Corporation Limited + // + // Author: Yingkun Meng + // Binbin Zhou +@@ -21,6 +21,7 @@ + #include "loongson_i2s.h" + #include "loongson_dma.h" + ++/* Loongson-2K1000 APBDMA routing */ + #define LOONGSON_I2S_RX_DMA_OFFSET 21 + #define LOONGSON_I2S_TX_DMA_OFFSET 18 + +@@ -30,6 +31,11 @@ + #define LOONGSON_DMA3_CONF 0x3 + #define LOONGSON_DMA4_CONF 0x4 + ++struct loongson_i2s_plat_config { ++ int rev_id; ++ int (*i2s_dma_config)(struct platform_device *pdev); ++}; ++ + static int loongson_i2s_apbdma_config(struct platform_device *pdev) + { + int val; +@@ -47,8 +53,18 @@ static int loongson_i2s_apbdma_config(struct platform_device *pdev) + return 0; + } + ++static const struct loongson_i2s_plat_config ls2k0300_i2s_plat_config = { ++ .rev_id = 1, ++}; ++ ++static const struct loongson_i2s_plat_config ls2k1000_i2s_plat_config = { ++ .rev_id = 0, ++ .i2s_dma_config = loongson_i2s_apbdma_config, ++}; ++ + static int loongson_i2s_plat_probe(struct platform_device *pdev) + { ++ const struct loongson_i2s_plat_config *plat_config; + struct device *dev = &pdev->dev; + struct loongson_i2s *i2s; + struct resource *res; +@@ -59,12 +75,17 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev) + if (!i2s) + return -ENOMEM; + +- ret = loongson_i2s_apbdma_config(pdev); +- if (ret) +- return ret; ++ plat_config = device_get_match_data(dev); ++ if (!plat_config) ++ return -EINVAL; + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- i2s->reg_base = devm_ioremap_resource(&pdev->dev, res); ++ if (plat_config->i2s_dma_config) { ++ ret = plat_config->i2s_dma_config(pdev); ++ if (ret) ++ return ret; ++ } ++ ++ i2s->reg_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(i2s->reg_base)) + return dev_err_probe(dev, PTR_ERR(i2s->reg_base), + "devm_ioremap_resource failed\n"); +@@ -87,11 +108,17 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev) + if (IS_ERR(i2s_clk)) + return dev_err_probe(dev, PTR_ERR(i2s_clk), "clock property invalid\n"); + i2s->clk_rate = clk_get_rate(i2s_clk); ++ i2s->rev_id = plat_config->rev_id; + + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + dev_set_name(dev, LS_I2S_DRVNAME); + dev_set_drvdata(dev, i2s); + ++ if (i2s->rev_id == 1) { ++ regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET, I2S_CTRL_RESET); ++ fsleep(200); ++ } ++ + ret = devm_snd_soc_register_component(dev, &loongson_i2s_edma_component, + &loongson_i2s_dai, 1); + if (ret) +@@ -102,7 +129,8 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev) + } + + static const struct of_device_id loongson_i2s_ids[] = { +- { .compatible = "loongson,ls2k1000-i2s" }, ++ { .compatible = "loongson,ls2k0300-i2s", .data = &ls2k0300_i2s_plat_config }, ++ { .compatible = "loongson,ls2k1000-i2s", .data = &ls2k1000_i2s_plat_config }, + { /* sentinel */ }, + }; + MODULE_DEVICE_TABLE(of, loongson_i2s_ids); +diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig +--- a/sound/soc/mediatek/Kconfig ++++ b/sound/soc/mediatek/Kconfig +@@ -7,7 +7,7 @@ config SND_SOC_MEDIATEK + + config SND_SOC_MT2701 + tristate "ASoC support for Mediatek MT2701 chip" +- depends on ARCH_MEDIATEK ++ depends on ARCH_MEDIATEK || COMPILE_TEST + select SND_SOC_MEDIATEK + help + This adds ASoC driver for Mediatek MT2701 boards +diff --git a/sound/soc/mediatek/common/mtk-dsp-sof-common.c b/sound/soc/mediatek/common/mtk-dsp-sof-common.c +--- a/sound/soc/mediatek/common/mtk-dsp-sof-common.c ++++ b/sound/soc/mediatek/common/mtk-dsp-sof-common.c +@@ -232,6 +232,7 @@ int mtk_sof_dailink_parse_of(struct device *dev, struct snd_soc_card *card, + const char *propname) + { + struct device_node *np = dev->of_node; ++ struct snd_soc_dai_link *dai_link; + struct snd_soc_dai_link *parsed_dai_link; + const char *dai_name = NULL; + int i, j, ret, num_links, parsed_num_links = 0; +@@ -254,9 +255,9 @@ int mtk_sof_dailink_parse_of(struct device *dev, struct snd_soc_card *card, + return ret; + } + dev_dbg(dev, "ASoC: Property get dai_name:%s\n", dai_name); +- for (j = 0; j < card->num_links; j++) { +- if (!strcmp(dai_name, card->dai_link[j].name)) { +- memcpy(&parsed_dai_link[parsed_num_links++], &card->dai_link[j], ++ for_each_card_prelinks(card, j, dai_link) { ++ if (!strcmp(dai_name, dai_link->name)) { ++ memcpy(&parsed_dai_link[parsed_num_links++], dai_link, + sizeof(struct snd_soc_dai_link)); + break; + } +diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650.c b/sound/soc/mediatek/mt8173/mt8173-rt5650.c +--- a/sound/soc/mediatek/mt8173/mt8173-rt5650.c ++++ b/sound/soc/mediatek/mt8173/mt8173-rt5650.c +@@ -315,7 +315,7 @@ static int mt8173_rt5650_dev_probe(struct platform_device *pdev) + &mt8173_rt5650_priv.pll_from); + if (ret) { + dev_err(&pdev->dev, +- "%s snd_soc_register_card fail %d\n", ++ "%s device_property_read_u32() fail %d\n", + __func__, ret); + } + } +diff --git a/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c +--- a/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c ++++ b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c +@@ -172,7 +172,7 @@ static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S32_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + +@@ -184,7 +184,7 @@ static int mt8183_rt1015_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +diff --git a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c +--- a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c ++++ b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c +@@ -99,7 +99,7 @@ static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to S32_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + return 0; +@@ -112,7 +112,7 @@ static int mt8183_rt1015_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + return 0; +diff --git a/sound/soc/mediatek/mt8186/mt8186-mt6366.c b/sound/soc/mediatek/mt8186/mt8186-mt6366.c +--- a/sound/soc/mediatek/mt8186/mt8186-mt6366.c ++++ b/sound/soc/mediatek/mt8186/mt8186-mt6366.c +@@ -387,7 +387,7 @@ static int mt8186_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, fmt); + +diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c +--- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c ++++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c +@@ -623,7 +623,7 @@ static int mt8188_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + +diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-adda.c b/sound/soc/mediatek/mt8189/mt8189-dai-adda.c +--- a/sound/soc/mediatek/mt8189/mt8189-dai-adda.c ++++ b/sound/soc/mediatek/mt8189/mt8189-dai-adda.c +@@ -385,8 +385,6 @@ static int mtk_adda_pad_top_event(struct snd_soc_dapm_widget *w, + if (event == SND_SOC_DAPM_PRE_PMU) { + if (afe_priv->mtkaif_protocol == MTKAIF_PROTOCOL_2_CLK_P2) + regmap_write(afe->regmap, AFE_AUD_PAD_TOP_CFG0, 0xB8); +- else if (afe_priv->mtkaif_protocol == MTKAIF_PROTOCOL_2) +- regmap_write(afe->regmap, AFE_AUD_PAD_TOP_CFG0, 0xB0); + else + regmap_write(afe->regmap, AFE_AUD_PAD_TOP_CFG0, 0xB0); + } +diff --git a/sound/soc/mediatek/mt8189/mt8189-nau8825.c b/sound/soc/mediatek/mt8189/mt8189-nau8825.c +--- a/sound/soc/mediatek/mt8189/mt8189-nau8825.c ++++ b/sound/soc/mediatek/mt8189/mt8189-nau8825.c +@@ -174,7 +174,7 @@ static int mt8189_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + +diff --git a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +--- a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c ++++ b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +@@ -382,7 +382,7 @@ static int mt8192_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +@@ -1019,7 +1019,7 @@ static struct snd_soc_card mt8192_mt6359_rt1015p_rt5682x_card = { + .num_dapm_routes = ARRAY_SIZE(mt8192_mt6359_rt1015p_rt5682x_routes), + }; + +-static int mt8192_mt6359_card_set_be_link(struct snd_soc_card *card, ++static int mt8192_mt6359_card_set_be_link(struct device *dev, + struct snd_soc_dai_link *link, + struct device_node *node, + char *link_name) +@@ -1027,9 +1027,9 @@ static int mt8192_mt6359_card_set_be_link(struct snd_soc_card *card, + int ret; + + if (node && strcmp(link->name, link_name) == 0) { +- ret = snd_soc_of_get_dai_link_codecs(card->dev, node, link); ++ ret = snd_soc_of_get_dai_link_codecs(dev, node, link); + if (ret < 0) { +- dev_err_probe(card->dev, ret, "get dai link codecs fail\n"); ++ dev_err_probe(dev, ret, "get dai link codecs fail\n"); + return ret; + } + } +@@ -1065,21 +1065,21 @@ static int mt8192_mt6359_legacy_probe(struct mtk_soc_card_data *soc_card_data) + } + + for_each_card_prelinks(card, i, dai_link) { +- ret = mt8192_mt6359_card_set_be_link(card, dai_link, speaker_codec, "I2S3"); ++ ret = mt8192_mt6359_card_set_be_link(dev, dai_link, speaker_codec, "I2S3"); + if (ret) { + dev_err_probe(dev, ret, "%s set speaker_codec fail\n", + dai_link->name); + break; + } + +- ret = mt8192_mt6359_card_set_be_link(card, dai_link, headset_codec, "I2S8"); ++ ret = mt8192_mt6359_card_set_be_link(dev, dai_link, headset_codec, "I2S8"); + if (ret) { + dev_err_probe(dev, ret, "%s set headset_codec fail\n", + dai_link->name); + break; + } + +- ret = mt8192_mt6359_card_set_be_link(card, dai_link, headset_codec, "I2S9"); ++ ret = mt8192_mt6359_card_set_be_link(dev, dai_link, headset_codec, "I2S9"); + if (ret) { + dev_err_probe(dev, ret, "%s set headset_codec fail\n", + dai_link->name); +diff --git a/sound/soc/mediatek/mt8195/mt8195-mt6359.c b/sound/soc/mediatek/mt8195/mt8195-mt6359.c +--- a/sound/soc/mediatek/mt8195/mt8195-mt6359.c ++++ b/sound/soc/mediatek/mt8195/mt8195-mt6359.c +@@ -387,7 +387,7 @@ static int mt8195_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +@@ -650,7 +650,7 @@ static int mt8195_etdm_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +diff --git a/sound/soc/mediatek/mt8196/mt8196-nau8825.c b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +--- a/sound/soc/mediatek/mt8196/mt8196-nau8825.c ++++ b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +@@ -104,8 +104,6 @@ static const struct snd_kcontrol_new mt8196_nau8825_controls[] = { + + #define EXT_SPK_AMP_W_NAME "Ext_Speaker_Amp" + +-static struct snd_soc_card mt8196_nau8825_soc_card; +- + static const struct snd_soc_dapm_widget mt8196_nau8825_card_widgets[] = { + /* SOF Uplink */ + SND_SOC_DAPM_MIXER("SOF_DMA_UL0", SND_SOC_NOPM, 0, 0, NULL, 0), +@@ -180,7 +178,7 @@ static int mt8196_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + return 0; +diff --git a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c +--- a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c ++++ b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c +@@ -2120,19 +2120,15 @@ static int mt8365_afe_pcm_dev_probe(struct platform_device *pdev) + spin_lock_init(&afe_priv->afe_ctrl_lock); + mutex_init(&afe_priv->afe_clk_mutex); + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- afe->base_addr = devm_ioremap_resource(&pdev->dev, res); ++ afe->base_addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(afe->base_addr)) + return PTR_ERR(afe->base_addr); + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 1); +- if (res) { +- afe_priv->afe_sram_vir_addr = +- devm_ioremap_resource(&pdev->dev, res); +- if (!IS_ERR(afe_priv->afe_sram_vir_addr)) { +- afe_priv->afe_sram_phy_addr = res->start; +- afe_priv->afe_sram_size = resource_size(res); +- } ++ afe_priv->afe_sram_vir_addr = ++ devm_platform_get_and_ioremap_resource(pdev, 1, &res); ++ if (!IS_ERR(afe_priv->afe_sram_vir_addr)) { ++ afe_priv->afe_sram_phy_addr = res->start; ++ afe_priv->afe_sram_size = resource_size(res); + } + + /* initial audio related clock */ +diff --git a/sound/soc/mediatek/mt8365/mt8365-mt6357.c b/sound/soc/mediatek/mt8365/mt8365-mt6357.c +--- a/sound/soc/mediatek/mt8365/mt8365-mt6357.c ++++ b/sound/soc/mediatek/mt8365/mt8365-mt6357.c +@@ -70,7 +70,8 @@ static const struct snd_soc_dapm_route mt8365_mt6357_routes[] = { + static int mt8365_mt6357_int_adda_startup(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = substream->private_data; +- struct mt8365_mt6357_priv *priv = snd_soc_card_get_drvdata(rtd->card); ++ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(rtd->card); ++ struct mt8365_mt6357_priv *priv = soc_card_data->mach_priv; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +@@ -101,7 +102,8 @@ static int mt8365_mt6357_int_adda_startup(struct snd_pcm_substream *substream) + static void mt8365_mt6357_int_adda_shutdown(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = substream->private_data; +- struct mt8365_mt6357_priv *priv = snd_soc_card_get_drvdata(rtd->card); ++ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(rtd->card); ++ struct mt8365_mt6357_priv *priv = soc_card_data->mach_priv; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +@@ -245,7 +247,8 @@ static struct snd_soc_dai_link mt8365_mt6357_dais[] = { + + static int mt8365_mt6357_gpio_probe(struct snd_soc_card *card) + { +- struct mt8365_mt6357_priv *priv = snd_soc_card_get_drvdata(card); ++ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(card); ++ struct mt8365_mt6357_priv *priv = soc_card_data->mach_priv; + struct device *dev = card->dev; + int ret, i; + +@@ -294,7 +297,6 @@ static int mt8365_mt6357_dev_probe(struct mtk_soc_card_data *soc_card_data, bool + struct mt8365_mt6357_priv *mach_priv; + int ret; + +- card->dev = dev; + ret = parse_dai_link_info(card); + if (ret) + goto err; +diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile +--- a/sound/soc/meson/Makefile ++++ b/sound/soc/meson/Makefile +@@ -4,6 +4,8 @@ snd-soc-meson-aiu-y := aiu.o + snd-soc-meson-aiu-y += aiu-acodec-ctrl.o + snd-soc-meson-aiu-y += aiu-codec-ctrl.o + snd-soc-meson-aiu-y += aiu-encoder-i2s.o ++snd-soc-meson-aiu-y += gx-formatter.o ++snd-soc-meson-aiu-y += aiu-formatter-i2s.o + snd-soc-meson-aiu-y += aiu-encoder-spdif.o + snd-soc-meson-aiu-y += aiu-fifo.o + snd-soc-meson-aiu-y += aiu-fifo-i2s.o +diff --git a/sound/soc/meson/aiu-encoder-i2s.c b/sound/soc/meson/aiu-encoder-i2s.c +--- a/sound/soc/meson/aiu-encoder-i2s.c ++++ b/sound/soc/meson/aiu-encoder-i2s.c +@@ -10,14 +10,11 @@ + #include + + #include "aiu.h" ++#include "gx-formatter.h" ++#include "gx-interface.h" + +-#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0) +-#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5) +-#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9) + #define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11) +-#define AIU_RST_SOFT_I2S_FAST BIT(0) + +-#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2) + #define AIU_CLK_CTRL_I2S_DIV_EN BIT(0) + #define AIU_CLK_CTRL_I2S_DIV GENMASK(3, 2) + #define AIU_CLK_CTRL_AOCLK_INVERT BIT(6) +@@ -35,49 +32,6 @@ static void aiu_encoder_i2s_divider_enable(struct snd_soc_component *component, + enable ? AIU_CLK_CTRL_I2S_DIV_EN : 0); + } + +-static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component, +- struct snd_pcm_hw_params *params) +-{ +- /* Always operate in split (classic interleaved) mode */ +- unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT; +- +- /* Reset required to update the pipeline */ +- snd_soc_component_write(component, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST); +- snd_soc_component_read(component, AIU_I2S_SYNC); +- +- switch (params_physical_width(params)) { +- case 16: /* Nothing to do */ +- break; +- +- case 32: +- desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT | +- AIU_I2S_SOURCE_DESC_MODE_32BIT); +- break; +- +- default: +- return -EINVAL; +- } +- +- switch (params_channels(params)) { +- case 2: /* Nothing to do */ +- break; +- case 8: +- desc |= AIU_I2S_SOURCE_DESC_MODE_8CH; +- break; +- default: +- return -EINVAL; +- } +- +- snd_soc_component_update_bits(component, AIU_I2S_SOURCE_DESC, +- AIU_I2S_SOURCE_DESC_MODE_8CH | +- AIU_I2S_SOURCE_DESC_MODE_24BIT | +- AIU_I2S_SOURCE_DESC_MODE_32BIT | +- AIU_I2S_SOURCE_DESC_MODE_SPLIT, +- desc); +- +- return 0; +-} +- + static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component, + struct snd_pcm_hw_params *params, + unsigned int bs) +@@ -108,6 +62,32 @@ static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component, + return 0; + } + ++/* ++ * Return true if the given combination of channels and sample width requires ++ * the bs quirk. Return false otherwise. ++ */ ++static bool aiu_encoder_is_bs_quirk(unsigned int channels, int width) ++{ ++ return (channels == 8) && (width == 16); ++} ++ ++static int aiu_encoder_check_bs_quirk(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct gx_stream *other_stream = snd_soc_dai_dma_data_get(dai, !substream->stream); ++ ++ /* Nothing to do if the other stream doesn't exist or it's not configured yet. */ ++ if (!other_stream || !other_stream->channels) ++ return 0; ++ ++ if (aiu_encoder_is_bs_quirk(other_stream->channels, other_stream->width) != ++ aiu_encoder_is_bs_quirk(params_channels(params), params_width(params))) ++ return -EINVAL; ++ ++ return 0; ++} ++ + static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, + struct snd_pcm_hw_params *params, + unsigned int bs) +@@ -119,7 +99,7 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, + * increased by 50% to get the correct output rate. + * No idea why ! + */ +- if (params_width(params) == 16 && params_channels(params) == 8) { ++ if (aiu_encoder_is_bs_quirk(params_channels(params), params_width(params))) { + if (bs % 2) { + dev_err(component->dev, + "Cannot increase i2s divider by 50%%\n"); +@@ -141,25 +121,23 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, + return 0; + } + +-static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component, +- struct snd_pcm_hw_params *params) ++static int aiu_encoder_i2s_set_clocks(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) + { ++ struct snd_soc_component *component = dai->component; + struct aiu *aiu = snd_soc_component_get_drvdata(component); ++ struct gx_iface *iface = &aiu->i2s.iface; + unsigned int srate = params_rate(params); + unsigned int fs, bs; + int ret; + + /* Get the oversampling factor */ +- fs = DIV_ROUND_CLOSEST(clk_get_rate(aiu->i2s.clks[MCLK].clk), srate); ++ fs = DIV_ROUND_CLOSEST(iface->mclk_rate, srate); + +- if (fs % 64) ++ if ((fs % 64) || (fs == 0)) + return -EINVAL; + +- /* Send data MSB first */ +- snd_soc_component_update_bits(component, AIU_I2S_DAC_CFG, +- AIU_I2S_DAC_CFG_MSB_FIRST, +- AIU_I2S_DAC_CFG_MSB_FIRST); +- + /* Set bclk to lrlck ratio */ + snd_soc_component_update_bits(component, AIU_CODEC_DAC_LRCLK_CTRL, + AIU_CODEC_DAC_LRCLK_CTRL_DIV, +@@ -168,10 +146,22 @@ static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component, + + bs = fs / 64; + +- if (aiu->platform->has_clk_ctrl_more_i2s_div) ++ if (aiu->platform->has_clk_ctrl_more_i2s_div) { ++ /* ++ * The hw rules added in startup() make this unreachable in the ++ * sequential case, but both streams may be refined concurrently ++ * before either commits its config, since only ops->hw_params ++ * runs under the card's pcm_mutex. Re-check against the committed ++ * state of the other stream, which is stable under that mutex. ++ */ ++ if (aiu_encoder_check_bs_quirk(substream, params, dai)) { ++ dev_err(dai->dev, "bclk requirements incompatible with other stream\n"); ++ return -EINVAL; ++ } + ret = aiu_encoder_i2s_set_more_div(component, params, bs); +- else ++ } else { + ret = aiu_encoder_i2s_set_legacy_div(component, params, bs); ++ } + + if (ret) + return ret; +@@ -188,23 +178,37 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); ++ int ret; ++ ++ ret = aiu_encoder_i2s_set_clocks(substream, params, dai); ++ if (ret) { ++ dev_err(dai->dev, "setting i2s clocks failed: %d\n", ret); ++ return ret; ++ } ++ ++ ts->physical_width = params_physical_width(params); ++ ts->width = params_width(params); ++ ts->channels = params_channels(params); ++ ++ return 0; ++} ++ ++static int aiu_encoder_i2s_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct snd_soc_component *component = dai->component; + int ret; + +- /* Disable the clock while changing the settings */ +- aiu_encoder_i2s_divider_enable(component, false); ++ if (ts->clk_enabled) ++ return 0; + +- ret = aiu_encoder_i2s_setup_desc(component, params); +- if (ret) { +- dev_err(dai->dev, "setting i2s desc failed\n"); ++ ret = clk_prepare_enable(ts->iface->mclk); ++ if (ret) + return ret; +- } + +- ret = aiu_encoder_i2s_set_clocks(component, params); +- if (ret) { +- dev_err(dai->dev, "setting i2s clocks failed\n"); +- return ret; +- } ++ ts->clk_enabled = true; + + aiu_encoder_i2s_divider_enable(component, true); + +@@ -214,9 +218,24 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream, + static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct snd_soc_component *component = dai->component; + +- aiu_encoder_i2s_divider_enable(component, false); ++ /* ++ * If this is the last substream being closed then disable the i2s ++ * clock divider. ++ */ ++ if (snd_soc_dai_active(dai) <= 1) ++ aiu_encoder_i2s_divider_enable(component, 0); ++ ++ if (ts->clk_enabled) { ++ clk_disable_unprepare(ts->iface->mclk); ++ ts->clk_enabled = false; ++ } ++ ++ ts->channels = 0; ++ ts->width = 0; ++ ts->physical_width = 0; + + return 0; + } +@@ -224,6 +243,8 @@ static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream, + static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + { + struct snd_soc_component *component = dai->component; ++ struct aiu *aiu = snd_soc_component_get_drvdata(component); ++ struct gx_iface *iface = &aiu->i2s.iface; + unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK; + unsigned int val = 0; + unsigned int skew; +@@ -255,9 +276,12 @@ static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + skew = 0; + break; + default: ++ dev_err(dai->dev, "unsupported dai format\n"); + return -EINVAL; + } + ++ iface->fmt = fmt; ++ + val |= FIELD_PREP(AIU_CLK_CTRL_LRCLK_SKEW, skew); + snd_soc_component_update_bits(component, AIU_CLK_CTRL, + AIU_CLK_CTRL_LRCLK_INVERT | +@@ -272,6 +296,7 @@ static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) + { + struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); ++ struct gx_iface *iface = &aiu->i2s.iface; + int ret; + + if (WARN_ON(clk_id != 0)) +@@ -280,11 +305,15 @@ static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + if (dir == SND_SOC_CLOCK_IN) + return 0; + +- ret = clk_set_rate(aiu->i2s.clks[MCLK].clk, freq); +- if (ret) +- dev_err(dai->dev, "Failed to set sysclk to %uHz", freq); ++ ret = clk_set_rate(iface->mclk, freq); ++ if (ret) { ++ dev_err(dai->dev, "Failed to set sysclk to %uHz: %d", freq, ret); ++ return ret; ++ } + +- return ret; ++ iface->mclk_rate = freq; ++ ++ return 0; + } + + static const unsigned int hw_channels[] = {2, 8}; +@@ -294,10 +323,45 @@ static const struct snd_pcm_hw_constraint_list hw_channel_constraints = { + .mask = 0, + }; + ++static int aiu_encoder_i2s_pcm_hw_rule(struct snd_pcm_hw_params *params, ++ struct snd_pcm_hw_rule *rule) ++{ ++ struct gx_stream *other = rule->private; ++ struct snd_interval *ch = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); ++ /* ++ * The quirk is technically based on the significant bits whereas here ++ * we're using the physical width for simplicity. This works because ++ * S16_LE is the only format supported by this encoder that has: ++ * significant bits = physical width = 16-bits ++ */ ++ struct snd_interval *phys_width = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS); ++ struct snd_interval new_i; ++ ++ if (other->channels == 0) ++ return 0; ++ ++ snd_interval_any(&new_i); ++ ++ if (rule->var == SNDRV_PCM_HW_PARAM_CHANNELS) { ++ if (aiu_encoder_is_bs_quirk(other->channels, other->width)) ++ new_i.min = new_i.max = 8; ++ else if (snd_interval_single(phys_width) && phys_width->min == 16) ++ new_i.max = 2; /* Force 2ch */ ++ } else { /* SNDRV_PCM_HW_PARAM_SAMPLE_BITS */ ++ if (aiu_encoder_is_bs_quirk(other->channels, other->width)) ++ new_i.min = new_i.max = 16; ++ else if (snd_interval_single(ch) && ch->min == 8) ++ new_i.min = 17; /* Request physical width > 16 bits */ ++ } ++ ++ return snd_interval_refine(hw_param_interval(params, rule->var), &new_i); ++} ++ + static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { + struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); ++ struct gx_stream *other_stream = snd_soc_dai_dma_data_get(dai, !substream->stream); + int ret; + + /* Make sure the encoder gets either 2 or 8 channels */ +@@ -305,15 +369,79 @@ static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream, + SNDRV_PCM_HW_PARAM_CHANNELS, + &hw_channel_constraints); + if (ret) { +- dev_err(dai->dev, "adding channels constraints failed\n"); ++ dev_err(dai->dev, "adding channels constraints failed: %d\n", ret); + return ret; + } + +- ret = clk_bulk_prepare_enable(aiu->i2s.clk_num, aiu->i2s.clks); +- if (ret) +- dev_err(dai->dev, "failed to enable i2s clocks\n"); ++ /* ++ * If DAI supports both playback and capture streams ensure the bs-quirk is ++ * handled correctly. ++ * This is only valid for GX platforms (has_clk_ctrl_more_i2s_div=true). ++ */ ++ if (aiu->platform->has_clk_ctrl_more_i2s_div && other_stream) { ++ ret = snd_pcm_hw_rule_add(substream->runtime, 0, ++ SNDRV_PCM_HW_PARAM_CHANNELS, ++ aiu_encoder_i2s_pcm_hw_rule, ++ other_stream, ++ SNDRV_PCM_HW_PARAM_CHANNELS, ++ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); ++ if (ret) ++ return ret; + +- return ret; ++ ret = snd_pcm_hw_rule_add(substream->runtime, 0, ++ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, ++ aiu_encoder_i2s_pcm_hw_rule, ++ other_stream, ++ SNDRV_PCM_HW_PARAM_CHANNELS, ++ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); ++ if (ret) ++ return ret; ++ } ++ ++ /* ++ * Enable only clocks which are required for the interface internal ++ * logic. MCLK is enabled/disabled from the formatter and the I2S ++ * divider is enabled/disabled in "hw_params"/"hw_free", respectively. ++ */ ++ ret = clk_prepare_enable(aiu->i2s.clks[PCLK].clk); ++ if (ret) { ++ dev_err(dai->dev, "failed to enable PCLK: %d\n", ret); ++ return ret; ++ } ++ ret = clk_prepare_enable(aiu->i2s.clks[MIXER].clk); ++ if (ret) { ++ dev_err(dai->dev, "failed to enable MIXER: %d\n", ret); ++ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); ++ return ret; ++ } ++ ret = clk_prepare_enable(aiu->i2s.clks[AOCLK].clk); ++ if (ret) { ++ dev_err(dai->dev, "failed to enable AOCLK: %d\n", ret); ++ clk_disable_unprepare(aiu->i2s.clks[MIXER].clk); ++ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); ++ return ret; ++ } ++ ++ /* ++ * We're always operating in split mode for the playback stream. ++ * ++ * This setting arguably belong to the 'aiu-formatter', but it's kept ++ * here for backward compatibility reason. At reset the I2S encoder ++ * operates in normal mode which would only support 8ch, but by default ++ * only 2ch are enabled. If a playback stream is started without ++ * changing to split mode, then the I2S encoder doesn't consume audio ++ * samples and the playback fails. ++ * Moving this to 'aiu-formatter' would cause the split mode to be set ++ * only when the formatter is enabled, which doesn't happen at boot as ++ * the default value for "HDMI CTRL SRC" is "DISABLED". ++ */ ++ ret = snd_soc_component_update_bits(dai->component, AIU_I2S_SOURCE_DESC, ++ AIU_I2S_SOURCE_DESC_MODE_SPLIT, ++ AIU_I2S_SOURCE_DESC_MODE_SPLIT); ++ if (ret < 0) ++ dev_err(dai->dev, "failed to update AIU_I2S_SOURCE_DESC: %d", ret); ++ ++ return 0; + } + + static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream, +@@ -321,14 +449,89 @@ static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream, + { + struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); + +- clk_bulk_disable_unprepare(aiu->i2s.clk_num, aiu->i2s.clks); ++ clk_disable_unprepare(aiu->i2s.clks[AOCLK].clk); ++ clk_disable_unprepare(aiu->i2s.clks[MIXER].clk); ++ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); ++} ++ ++static int aiu_encoder_i2s_trigger(struct snd_pcm_substream *substream, ++ int cmd, ++ struct snd_soc_dai *dai) ++{ ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); ++ int ret; ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ ret = gx_stream_start(ts); ++ break; ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ case SNDRV_PCM_TRIGGER_STOP: ++ gx_stream_stop(ts); ++ ret = 0; ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int aiu_encoder_i2s_remove_dai(struct snd_soc_dai *dai) ++{ ++ int stream; ++ ++ for_each_pcm_streams(stream) { ++ struct gx_stream *ts; ++ ++ ts = snd_soc_dai_dma_data_get(dai, stream); ++ if (ts) ++ gx_stream_free(ts); ++ ++ snd_soc_dai_dma_data_set(dai, stream, NULL); ++ } ++ ++ return 0; ++} ++ ++static int aiu_encoder_i2s_probe_dai(struct snd_soc_dai *dai) ++{ ++ struct aiu *aiu = snd_soc_dai_get_drvdata(dai); ++ struct gx_iface *iface = &aiu->i2s.iface; ++ int stream; ++ ++ for_each_pcm_streams(stream) { ++ struct gx_stream *ts; ++ ++ if (!snd_soc_dai_get_widget(dai, stream)) ++ continue; ++ ++ ts = gx_stream_alloc(iface); ++ if (!ts) { ++ aiu_encoder_i2s_remove_dai(dai); ++ return -ENOMEM; ++ } ++ snd_soc_dai_dma_data_set(dai, stream, ts); ++ } ++ ++ iface->mclk = aiu->i2s.clks[MCLK].clk; ++ iface->mclk_rate = clk_get_rate(iface->mclk); ++ ++ return 0; + } + + const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops = { ++ .probe = aiu_encoder_i2s_probe_dai, ++ .remove = aiu_encoder_i2s_remove_dai, + .hw_params = aiu_encoder_i2s_hw_params, ++ .prepare = aiu_encoder_i2s_prepare, + .hw_free = aiu_encoder_i2s_hw_free, + .set_fmt = aiu_encoder_i2s_set_fmt, + .set_sysclk = aiu_encoder_i2s_set_sysclk, + .startup = aiu_encoder_i2s_startup, + .shutdown = aiu_encoder_i2s_shutdown, ++ .trigger = aiu_encoder_i2s_trigger, + }; +diff --git a/sound/soc/meson/aiu-formatter-i2s.c b/sound/soc/meson/aiu-formatter-i2s.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/aiu-formatter-i2s.c +@@ -0,0 +1,103 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// ++// Copyright (c) 2026 BayLibre, SAS. ++// Author: Valerio Setti ++ ++#include ++#include ++#include ++ ++#include "aiu.h" ++#include "gx-formatter.h" ++ ++#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0) ++#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5) ++#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9) ++ ++#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2) ++ ++static struct snd_soc_dai * ++aiu_formatter_i2s_get_be(struct snd_soc_dapm_widget *w) ++{ ++ struct snd_soc_dapm_path *p; ++ struct snd_soc_dai *be; ++ ++ snd_soc_dapm_widget_for_each_sink_path(w, p) { ++ if (!p->connect) ++ continue; ++ ++ if (p->sink->id == snd_soc_dapm_dai_in) ++ return (struct snd_soc_dai *)p->sink->priv; ++ ++ be = aiu_formatter_i2s_get_be(p->sink); ++ if (be) ++ return be; ++ } ++ ++ return NULL; ++} ++ ++static struct gx_stream * ++aiu_formatter_i2s_get_stream(struct snd_soc_dapm_widget *w) ++{ ++ struct snd_soc_dai *be = aiu_formatter_i2s_get_be(w); ++ ++ if (!be) ++ return NULL; ++ ++ return snd_soc_dai_dma_data_get_playback(be); ++} ++ ++static int aiu_formatter_i2s_prepare(struct regmap *map, ++ const struct gx_formatter_hw *quirks, ++ struct gx_stream *ts) ++{ ++ /* Always operate in split (classic interleaved) mode */ ++ unsigned int desc = 0; ++ ++ /* ++ * Pipeline reset is already implemented in aiu_fifo_i2s_trigger() at ++ * trigger time. ++ */ ++ ++ switch (ts->physical_width) { ++ case 16: /* Nothing to do */ ++ break; ++ ++ case 32: ++ desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT | ++ AIU_I2S_SOURCE_DESC_MODE_32BIT); ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ switch (ts->channels) { ++ case 2: /* Nothing to do */ ++ break; ++ case 8: ++ desc |= AIU_I2S_SOURCE_DESC_MODE_8CH; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ regmap_update_bits(map, AIU_I2S_SOURCE_DESC, ++ AIU_I2S_SOURCE_DESC_MODE_8CH | ++ AIU_I2S_SOURCE_DESC_MODE_24BIT | ++ AIU_I2S_SOURCE_DESC_MODE_32BIT, ++ desc); ++ ++ /* Send data MSB first */ ++ regmap_update_bits(map, AIU_I2S_DAC_CFG, ++ AIU_I2S_DAC_CFG_MSB_FIRST, ++ AIU_I2S_DAC_CFG_MSB_FIRST); ++ ++ return 0; ++} ++ ++const struct gx_formatter_ops aiu_formatter_i2s_ops = { ++ .get_stream = aiu_formatter_i2s_get_stream, ++ .prepare = aiu_formatter_i2s_prepare, ++}; +diff --git a/sound/soc/meson/aiu.c b/sound/soc/meson/aiu.c +--- a/sound/soc/meson/aiu.c ++++ b/sound/soc/meson/aiu.c +@@ -29,13 +29,22 @@ static SOC_ENUM_SINGLE_DECL(aiu_spdif_encode_sel_enum, AIU_I2S_MISC, + static const struct snd_kcontrol_new aiu_spdif_encode_mux = + SOC_DAPM_ENUM("SPDIF Buffer Src", aiu_spdif_encode_sel_enum); + +-static const struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = { +- SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0, +- &aiu_spdif_encode_mux), ++#define AIU_WIDGET_SPDIF_SRC_SEL 0 ++#define AIU_WIDGET_I2S_FORMATTER 1 ++ ++static struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = { ++ [AIU_WIDGET_SPDIF_SRC_SEL] = ++ SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0, ++ &aiu_spdif_encode_mux), ++ [AIU_WIDGET_I2S_FORMATTER] = ++ SND_SOC_DAPM_PGA_E("I2S Formatter", SND_SOC_NOPM, 0, 0, NULL, 0, ++ gx_formatter_event, ++ (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)), + }; + + static const struct snd_soc_dapm_route aiu_cpu_dapm_routes[] = { +- { "I2S Encoder Playback", NULL, "I2S FIFO Playback" }, ++ { "I2S Formatter", NULL, "I2S FIFO Playback" }, ++ { "I2S Encoder Playback", NULL, "I2S Formatter" }, + { "SPDIF SRC SEL", "SPDIF", "SPDIF FIFO Playback" }, + { "SPDIF SRC SEL", "I2S", "I2S FIFO Playback" }, + { "SPDIF Encoder Playback", NULL, "SPDIF SRC SEL" }, +@@ -145,6 +154,7 @@ static struct snd_soc_dai_driver aiu_cpu_dai_drv[] = { + .formats = AIU_FORMATS, + }, + .ops = &aiu_encoder_i2s_dai_ops, ++ .symmetric_rate = 1, + }, + [CPU_SPDIF_ENCODER] = { + .name = "SPDIF Encoder", +@@ -172,6 +182,11 @@ static const struct regmap_config aiu_regmap_cfg = { + .max_register = 0x2ac, + }; + ++static const struct gx_formatter_driver aiu_formatter_i2s_drv = { ++ .regmap_cfg = &aiu_regmap_cfg, ++ .ops = &aiu_formatter_i2s_ops, ++}; ++ + static int aiu_clk_bulk_get(struct device *dev, + const char * const *ids, + unsigned int num, +@@ -282,6 +297,14 @@ static int aiu_probe(struct platform_device *pdev) + if (ret) + return ret; + ++ /* Allocate the aiu-formatter into its widget */ ++ ret = gx_formatter_create(dev, &aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER], ++ &aiu_formatter_i2s_drv, map); ++ if (ret) { ++ dev_err(dev, "Failed to allocate aiu formatter\n"); ++ goto err; ++ } ++ + /* Register the cpu component of the aiu */ + ret = snd_soc_register_component(dev, &aiu_cpu_component, + aiu_cpu_dai_drv, +@@ -310,12 +333,14 @@ static int aiu_probe(struct platform_device *pdev) + + return 0; + err: ++ gx_formatter_free(&aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER]); + snd_soc_unregister_component(dev); + return ret; + } + + static void aiu_remove(struct platform_device *pdev) + { ++ gx_formatter_free(&aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER]); + snd_soc_unregister_component(&pdev->dev); + } + +diff --git a/sound/soc/meson/aiu.h b/sound/soc/meson/aiu.h +--- a/sound/soc/meson/aiu.h ++++ b/sound/soc/meson/aiu.h +@@ -7,6 +7,8 @@ + #ifndef _MESON_AIU_H + #define _MESON_AIU_H + ++#include "gx-formatter.h" ++ + struct clk; + struct clk_bulk_data; + struct device; +@@ -25,6 +27,7 @@ struct aiu_interface { + struct clk_bulk_data *clks; + unsigned int clk_num; + int irq; ++ struct gx_iface iface; + }; + + struct aiu_platform_data { +@@ -58,6 +61,7 @@ extern const struct snd_soc_dai_ops aiu_fifo_i2s_dai_ops; + extern const struct snd_soc_dai_ops aiu_fifo_spdif_dai_ops; + extern const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops; + extern const struct snd_soc_dai_ops aiu_encoder_spdif_dai_ops; ++extern const struct gx_formatter_ops aiu_formatter_i2s_ops; + + #define AIU_IEC958_BPF 0x000 + #define AIU_958_MISC 0x010 +diff --git a/sound/soc/meson/axg-card.c b/sound/soc/meson/axg-card.c +--- a/sound/soc/meson/axg-card.c ++++ b/sound/soc/meson/axg-card.c +@@ -107,6 +107,7 @@ static int axg_card_add_tdm_loopback(struct snd_soc_card *card, + struct snd_soc_dai_link *pad; + struct snd_soc_dai_link *lb; + struct snd_soc_dai_link_component *dlc; ++ struct device *dev = card->dev; + int ret; + + /* extend links */ +@@ -117,11 +118,11 @@ static int axg_card_add_tdm_loopback(struct snd_soc_card *card, + pad = &card->dai_link[*index]; + lb = &card->dai_link[*index + 1]; + +- lb->name = devm_kasprintf(card->dev, GFP_KERNEL, "%s-lb", pad->name); ++ lb->name = devm_kasprintf(dev, GFP_KERNEL, "%s-lb", pad->name); + if (!lb->name) + return -ENOMEM; + +- dlc = devm_kzalloc(card->dev, sizeof(*dlc), GFP_KERNEL); ++ dlc = devm_kzalloc(dev, sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + +@@ -158,13 +159,14 @@ static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + struct device_node *node, + struct axg_dai_link_tdm_data *be) + { ++ struct device *dev = card->dev; + char propname[32]; + u32 tx, rx; + int i; + +- be->tx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, ++ be->tx_mask = devm_kcalloc(dev, AXG_TDM_NUM_LANES, + sizeof(*be->tx_mask), GFP_KERNEL); +- be->rx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, ++ be->rx_mask = devm_kcalloc(dev, AXG_TDM_NUM_LANES, + sizeof(*be->rx_mask), GFP_KERNEL); + if (!be->tx_mask || !be->rx_mask) + return -ENOMEM; +@@ -191,7 +193,7 @@ static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + + /* ... but the interface should at least have one direction */ + if (!tx && !rx) { +- dev_err(card->dev, "tdm link has no cpu slots\n"); ++ dev_err(dev, "tdm link has no cpu slots\n"); + return -EINVAL; + } + +@@ -207,7 +209,7 @@ static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + * Error if the slots can't accommodate the largest mask or + * if it is just too big + */ +- dev_err(card->dev, "bad slot number\n"); ++ dev_err(dev, "bad slot number\n"); + return -EINVAL; + } + +@@ -222,8 +224,9 @@ static int axg_card_parse_codecs_masks(struct snd_soc_card *card, + struct axg_dai_link_tdm_data *be) + { + struct axg_dai_link_tdm_mask *codec_mask; ++ struct device *dev = card->dev; + +- codec_mask = devm_kcalloc(card->dev, link->num_codecs, ++ codec_mask = devm_kcalloc(dev, link->num_codecs, + sizeof(*codec_mask), GFP_KERNEL); + if (!codec_mask) + return -ENOMEM; +@@ -249,10 +252,11 @@ static int axg_card_parse_tdm(struct snd_soc_card *card, + struct meson_card *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link *link = &card->dai_link[*index]; + struct axg_dai_link_tdm_data *be; ++ struct device *dev = card->dev; + int ret; + + /* Allocate tdm link parameters */ +- be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); ++ be = devm_kzalloc(dev, sizeof(*be), GFP_KERNEL); + if (!be) + return -ENOMEM; + priv->link_data[*index] = be; +@@ -266,7 +270,7 @@ static int axg_card_parse_tdm(struct snd_soc_card *card, + + ret = axg_card_parse_cpu_tdm_slots(card, link, node, be); + if (ret) { +- dev_err(card->dev, "error parsing tdm link slots\n"); ++ dev_err(dev, "error parsing tdm link slots\n"); + return ret; + } + +@@ -310,9 +314,10 @@ static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np, + { + struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; + struct snd_soc_dai_link_component *cpu; ++ struct device *dev = card->dev; + int ret; + +- cpu = devm_kzalloc(card->dev, sizeof(*cpu), GFP_KERNEL); ++ cpu = devm_kzalloc(dev, sizeof(*cpu), GFP_KERNEL); + if (!cpu) + return -ENOMEM; + +diff --git a/sound/soc/meson/g12a-toacodec.c b/sound/soc/meson/g12a-toacodec.c +--- a/sound/soc/meson/g12a-toacodec.c ++++ b/sound/soc/meson/g12a-toacodec.c +@@ -312,7 +312,7 @@ static int g12a_toacodec_probe(struct platform_device *pdev) + + ret = device_reset(dev); + if (ret) +- return ret; ++ return dev_err_probe(dev, ret, "failed to reset device\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) +diff --git a/sound/soc/meson/g12a-tohdmitx.c b/sound/soc/meson/g12a-tohdmitx.c +--- a/sound/soc/meson/g12a-tohdmitx.c ++++ b/sound/soc/meson/g12a-tohdmitx.c +@@ -251,7 +251,7 @@ static int g12a_tohdmitx_probe(struct platform_device *pdev) + + ret = device_reset(dev); + if (ret) +- return ret; ++ return dev_err_probe(dev, ret, "failed to reset device\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) +diff --git a/sound/soc/meson/gx-card.c b/sound/soc/meson/gx-card.c +--- a/sound/soc/meson/gx-card.c ++++ b/sound/soc/meson/gx-card.c +@@ -48,9 +48,10 @@ static int gx_card_parse_i2s(struct snd_soc_card *card, + struct meson_card *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link *link = &card->dai_link[*index]; + struct gx_dai_link_i2s_data *be; ++ struct device *dev = card->dev; + + /* Allocate i2s link parameters */ +- be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); ++ be = devm_kzalloc(dev, sizeof(*be), GFP_KERNEL); + if (!be) + return -ENOMEM; + priv->link_data[*index] = be; +@@ -81,9 +82,10 @@ static int gx_card_add_link(struct snd_soc_card *card, struct device_node *np, + { + struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; + struct snd_soc_dai_link_component *cpu; ++ struct device *dev = card->dev; + int ret; + +- cpu = devm_kzalloc(card->dev, sizeof(*cpu), GFP_KERNEL); ++ cpu = devm_kzalloc(dev, sizeof(*cpu), GFP_KERNEL); + if (!cpu) + return -ENOMEM; + +diff --git a/sound/soc/meson/gx-formatter.c b/sound/soc/meson/gx-formatter.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/gx-formatter.c +@@ -0,0 +1,282 @@ ++// SPDX-License-Identifier: (GPL-2.0 OR MIT) ++// ++// Copyright (c) 2026 BayLibre, SAS. ++// Author: Valerio Setti ++ ++#include ++#include ++#include ++#include ++ ++#include "gx-formatter.h" ++ ++struct gx_formatter { ++ struct list_head list; ++ struct gx_stream *stream; ++ const struct gx_formatter_driver *drv; ++ bool enabled; ++ struct regmap *map; ++}; ++ ++static int gx_formatter_enable(struct gx_formatter *formatter) ++{ ++ int ret; ++ ++ /* Do nothing if the formatter is already enabled */ ++ if (formatter->enabled) ++ return 0; ++ ++ /* Setup the stream parameter in the formatter */ ++ if (formatter->drv->ops->prepare) { ++ ret = formatter->drv->ops->prepare(formatter->map, ++ formatter->drv->quirks, ++ formatter->stream); ++ if (ret) ++ return ret; ++ } ++ ++ /* Finally, actually enable the formatter */ ++ if (formatter->drv->ops->enable) ++ formatter->drv->ops->enable(formatter->map); ++ ++ formatter->enabled = true; ++ ++ return 0; ++} ++ ++static void gx_formatter_disable(struct gx_formatter *formatter) ++{ ++ /* Do nothing if the formatter is already disabled */ ++ if (!formatter->enabled) ++ return; ++ ++ if (formatter->drv->ops->disable) ++ formatter->drv->ops->disable(formatter->map); ++ ++ formatter->enabled = false; ++} ++ ++static int gx_formatter_attach(struct gx_formatter *formatter) ++{ ++ struct gx_stream *ts = formatter->stream; ++ int ret = 0; ++ ++ mutex_lock(&ts->lock); ++ ++ /* Catch up if the stream is already running when we attach */ ++ if (ts->ready) { ++ ret = gx_formatter_enable(formatter); ++ if (ret) { ++ pr_err("failed to enable formatter\n"); ++ goto out; ++ } ++ } ++ ++ list_add_tail(&formatter->list, &ts->formatter_list); ++out: ++ mutex_unlock(&ts->lock); ++ return ret; ++} ++ ++static void gx_formatter_detach(struct gx_formatter *formatter) ++{ ++ struct gx_stream *ts = formatter->stream; ++ ++ if (!ts) ++ return; ++ ++ mutex_lock(&ts->lock); ++ list_del(&formatter->list); ++ mutex_unlock(&ts->lock); ++ ++ gx_formatter_disable(formatter); ++} ++ ++static int gx_formatter_power_up(struct gx_formatter *formatter, ++ struct snd_soc_dapm_widget *w) ++{ ++ struct gx_stream *ts = formatter->drv->ops->get_stream(w); ++ int ret; ++ ++ /* ++ * If we don't get a stream at this stage, it would mean that the ++ * widget is powering up but is not attached to any backend DAI. ++ * It should not happen, ever ! ++ */ ++ if (WARN_ON(!ts)) ++ return -ENODEV; ++ ++ formatter->stream = ts; ++ INIT_LIST_HEAD(&formatter->list); ++ ret = gx_formatter_attach(formatter); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static void gx_formatter_power_down(struct gx_formatter *formatter) ++{ ++ gx_formatter_detach(formatter); ++ formatter->stream = NULL; ++} ++ ++int gx_formatter_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *control, ++ int event) ++{ ++ struct snd_soc_component *c; ++ struct gx_formatter *formatter; ++ int ret = 0; ++ ++ c = snd_soc_dapm_to_component(w->dapm); ++ ++ if (w->priv) ++ formatter = w->priv; ++ else ++ formatter = snd_soc_component_get_drvdata(c); ++ ++ switch (event) { ++ case SND_SOC_DAPM_PRE_PMU: ++ ret = gx_formatter_power_up(formatter, w); ++ break; ++ ++ case SND_SOC_DAPM_PRE_PMD: ++ gx_formatter_power_down(formatter); ++ break; ++ ++ default: ++ dev_err(c->dev, "Unexpected event %d\n", event); ++ return -EINVAL; ++ } ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(gx_formatter_event); ++ ++int gx_formatter_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ const struct gx_formatter_driver *drv; ++ struct gx_formatter *formatter; ++ void __iomem *regs; ++ ++ drv = of_device_get_match_data(dev); ++ if (!drv) { ++ dev_err(dev, "failed to match device\n"); ++ return -ENODEV; ++ } ++ ++ formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); ++ if (!formatter) ++ return -ENOMEM; ++ platform_set_drvdata(pdev, formatter); ++ formatter->drv = drv; ++ ++ regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); ++ ++ formatter->map = devm_regmap_init_mmio(dev, regs, drv->regmap_cfg); ++ if (IS_ERR(formatter->map)) { ++ dev_err(dev, "failed to init regmap: %ld\n", ++ PTR_ERR(formatter->map)); ++ return PTR_ERR(formatter->map); ++ } ++ ++ return devm_snd_soc_register_component(dev, drv->component_drv, ++ NULL, 0); ++} ++EXPORT_SYMBOL_GPL(gx_formatter_probe); ++ ++int gx_formatter_create(struct device *dev, ++ struct snd_soc_dapm_widget *w, ++ const struct gx_formatter_driver *drv, ++ struct regmap *regmap) ++{ ++ struct gx_formatter *formatter; ++ ++ formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); ++ if (!formatter) ++ return -ENOMEM; ++ ++ formatter->drv = drv; ++ formatter->map = regmap; ++ ++ w->priv = formatter; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(gx_formatter_create); ++ ++int gx_stream_start(struct gx_stream *ts) ++{ ++ struct gx_formatter *formatter; ++ int ret = 0; ++ ++ mutex_lock(&ts->lock); ++ ++ /* Start all the formatters attached to the stream */ ++ list_for_each_entry(formatter, &ts->formatter_list, list) { ++ ret = gx_formatter_enable(formatter); ++ if (ret) { ++ pr_err("failed to enable formatter\n"); ++ goto out; ++ } ++ } ++ ++ ts->ready = true; ++ ++out: ++ mutex_unlock(&ts->lock); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(gx_stream_start); ++ ++void gx_stream_stop(struct gx_stream *ts) ++{ ++ struct gx_formatter *formatter; ++ ++ mutex_lock(&ts->lock); ++ ts->ready = false; ++ ++ /* Stop all the formatters attached to the stream */ ++ list_for_each_entry(formatter, &ts->formatter_list, list) { ++ gx_formatter_disable(formatter); ++ } ++ ++ mutex_unlock(&ts->lock); ++} ++EXPORT_SYMBOL_GPL(gx_stream_stop); ++ ++struct gx_stream *gx_stream_alloc(struct gx_iface *iface) ++{ ++ struct gx_stream *ts; ++ ++ ts = kzalloc(sizeof(*ts), GFP_KERNEL); ++ if (ts) { ++ INIT_LIST_HEAD(&ts->formatter_list); ++ mutex_init(&ts->lock); ++ ts->iface = iface; ++ } ++ ++ return ts; ++} ++EXPORT_SYMBOL_GPL(gx_stream_alloc); ++ ++void gx_stream_free(struct gx_stream *ts) ++{ ++ /* ++ * If the list is not empty, it would mean that one of the formatter ++ * widget is still powered and attached to the interface while we ++ * are removing the TDM DAI. It should not be possible ++ */ ++ WARN_ON(!list_empty(&ts->formatter_list)); ++ mutex_destroy(&ts->lock); ++ kfree(ts); ++} ++EXPORT_SYMBOL_GPL(gx_stream_free); ++ ++MODULE_DESCRIPTION("Amlogic GX formatter driver"); ++MODULE_AUTHOR("Valerio Setti "); ++MODULE_LICENSE("GPL"); +diff --git a/sound/soc/meson/gx-formatter.h b/sound/soc/meson/gx-formatter.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/gx-formatter.h +@@ -0,0 +1,56 @@ ++/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ ++/* ++ * Copyright (c) 2026 Baylibre SAS. ++ * Author: Valerio Setti ++ */ ++ ++#ifndef _MESON_GX_FORMATTER_H ++#define _MESON_GX_FORMATTER_H ++ ++#include "gx-interface.h" ++ ++struct platform_device; ++struct regmap; ++struct snd_soc_dapm_widget; ++struct snd_kcontrol; ++ ++struct gx_formatter_hw { ++ unsigned int skew_offset; ++}; ++ ++struct gx_formatter_ops { ++ struct gx_stream *(*get_stream)(struct snd_soc_dapm_widget *w); ++ void (*enable)(struct regmap *map); ++ void (*disable)(struct regmap *map); ++ int (*prepare)(struct regmap *map, ++ const struct gx_formatter_hw *quirks, ++ struct gx_stream *ts); ++}; ++ ++struct gx_formatter_driver { ++ const struct snd_soc_component_driver *component_drv; ++ const struct regmap_config *regmap_cfg; ++ const struct gx_formatter_ops *ops; ++ const struct gx_formatter_hw *quirks; ++}; ++ ++int gx_formatter_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *control, ++ int event); ++int gx_formatter_probe(struct platform_device *pdev); ++ ++int gx_formatter_create(struct device *dev, ++ struct snd_soc_dapm_widget *w, ++ const struct gx_formatter_driver *drv, ++ struct regmap *regmap); ++ ++/* ++ * Formatter data is already freed when the associated device is removed, ++ * so we just need to remove the pointer from the widget. ++ */ ++static inline void gx_formatter_free(struct snd_soc_dapm_widget *w) ++{ ++ w->priv = NULL; ++} ++ ++#endif /* _MESON_GX_FORMATTER_H */ +diff --git a/sound/soc/meson/gx-interface.h b/sound/soc/meson/gx-interface.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/gx-interface.h +@@ -0,0 +1,42 @@ ++/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ ++/* ++ * Copyright (c) 2026 Baylibre SAS. ++ * Author: Valerio Setti ++ */ ++ ++#ifndef _MESON_GX_INTERFACE_H ++#define _MESON_GX_INTERFACE_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++struct gx_iface { ++ struct clk *mclk; ++ unsigned long mclk_rate; ++ ++ /* format is common to all the DAIs of the iface */ ++ unsigned int fmt; ++}; ++ ++struct gx_stream { ++ struct gx_iface *iface; ++ struct list_head formatter_list; ++ struct mutex lock; ++ unsigned int channels; ++ unsigned int width; ++ unsigned int physical_width; ++ bool ready; ++ ++ /* For continuous clock tracking */ ++ bool clk_enabled; ++}; ++ ++struct gx_stream *gx_stream_alloc(struct gx_iface *iface); ++void gx_stream_free(struct gx_stream *ts); ++int gx_stream_start(struct gx_stream *ts); ++void gx_stream_stop(struct gx_stream *ts); ++ ++#endif /* _MESON_GX_INTERFACE_H */ +diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-utils.c +--- a/sound/soc/meson/meson-card-utils.c ++++ b/sound/soc/meson/meson-card-utils.c +@@ -50,25 +50,20 @@ int meson_card_reallocate_links(struct snd_soc_card *card, + num_links * sizeof(*priv->card.dai_link), + GFP_KERNEL | __GFP_ZERO); + if (!links) +- goto err_links; ++ return -ENOMEM; ++ ++ priv->card.dai_link = links; ++ priv->card.num_links = num_links; + + ldata = krealloc(priv->link_data, + num_links * sizeof(*priv->link_data), + GFP_KERNEL | __GFP_ZERO); ++ /* meson_card_clean_references() will free the links on this error path */ + if (!ldata) +- goto err_ldata; ++ return -ENOMEM; + +- priv->card.dai_link = links; + priv->link_data = ldata; +- priv->card.num_links = num_links; + return 0; +- +-err_ldata: +- kfree(links); +-err_links: +- dev_err(priv->card.dev, "failed to allocate links\n"); +- return -ENOMEM; +- + } + EXPORT_SYMBOL_GPL(meson_card_reallocate_links); + +@@ -76,6 +71,7 @@ int meson_card_parse_dai(struct snd_soc_card *card, + struct device_node *node, + struct snd_soc_dai_link_component *dlc) + { ++ struct device *dev = card->dev; + int ret; + + if (!dlc || !node) +@@ -83,7 +79,7 @@ int meson_card_parse_dai(struct snd_soc_card *card, + + ret = snd_soc_of_get_dlc(node, NULL, dlc, 0); + if (ret) +- return dev_err_probe(card->dev, ret, "can't parse dai\n"); ++ return dev_err_probe(dev, ret, "can't parse dai\n"); + + return ret; + } +@@ -94,7 +90,8 @@ static int meson_card_set_link_name(struct snd_soc_card *card, + struct device_node *node, + const char *prefix) + { +- char *name = devm_kasprintf(card->dev, GFP_KERNEL, "%s.%s", ++ struct device *dev = card->dev; ++ char *name = devm_kasprintf(dev, GFP_KERNEL, "%s.%s", + prefix, node->full_name); + if (!name) + return -ENOMEM; +@@ -137,16 +134,17 @@ int meson_card_set_be_link(struct snd_soc_card *card, + struct device_node *node) + { + struct snd_soc_dai_link_component *codec; ++ struct device *dev = card->dev; + int ret, num_codecs; + + num_codecs = of_get_child_count(node); + if (!num_codecs) { +- dev_err(card->dev, "be link %s has no codec\n", ++ dev_err(dev, "be link %s has no codec\n", + node->full_name); + return -EINVAL; + } + +- codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec), GFP_KERNEL); ++ codec = devm_kcalloc(dev, num_codecs, sizeof(*codec), GFP_KERNEL); + if (!codec) + return -ENOMEM; + +@@ -163,7 +161,7 @@ int meson_card_set_be_link(struct snd_soc_card *card, + + ret = meson_card_set_link_name(card, link, node, "be"); + if (ret) +- dev_err(card->dev, "error setting %pOFn link name\n", node); ++ dev_err(dev, "error setting %pOFn link name\n", node); + + return ret; + } +@@ -194,12 +192,13 @@ EXPORT_SYMBOL_GPL(meson_card_set_fe_link); + static int meson_card_add_links(struct snd_soc_card *card) + { + struct meson_card *priv = snd_soc_card_get_drvdata(card); +- struct device_node *node = card->dev->of_node; ++ struct device *dev = card->dev; ++ struct device_node *node = dev->of_node; + int num, i, ret; + + num = of_get_child_count(node); + if (!num) { +- dev_err(card->dev, "card has no links\n"); ++ dev_err(dev, "card has no links\n"); + return -EINVAL; + } + +@@ -224,8 +223,10 @@ static int meson_card_parse_of_optional(struct snd_soc_card *card, + int (*func)(struct snd_soc_card *c, + const char *p)) + { ++ struct device *dev = card->dev; ++ + /* If property is not provided, don't fail ... */ +- if (!of_property_present(card->dev->of_node, propname)) ++ if (!of_property_present(dev->of_node, propname)) + return 0; + + /* ... but do fail if it is provided and the parsing fails */ +diff --git a/sound/soc/meson/meson-codec-glue.c b/sound/soc/meson/meson-codec-glue.c +--- a/sound/soc/meson/meson-codec-glue.c ++++ b/sound/soc/meson/meson-codec-glue.c +@@ -74,7 +74,7 @@ int meson_codec_glue_input_hw_params(struct snd_pcm_substream *substream, + data->params.rates = snd_pcm_rate_to_rate_bit(params_rate(params)); + data->params.rate_min = params_rate(params); + data->params.rate_max = params_rate(params); +- data->params.formats = 1ULL << (__force int) params_format(params); ++ data->params.formats = 1ULL << params_format(params); + data->params.channels_min = params_channels(params); + data->params.channels_max = params_channels(params); + data->params.sig_bits = dai->driver->playback.sig_bits; +diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c +--- a/sound/soc/meson/t9015.c ++++ b/sound/soc/meson/t9015.c +@@ -78,8 +78,14 @@ static int t9015_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + return 0; + } + ++static const u64 t9015_dai_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J; ++ + static const struct snd_soc_dai_ops t9015_dai_ops = { + .set_fmt = t9015_dai_set_fmt, ++ .auto_selectable_formats = &t9015_dai_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver t9015_dai = { +@@ -265,10 +271,8 @@ static int t9015_probe(struct platform_device *pdev) + return dev_err_probe(dev, PTR_ERR(priv->avdd), "failed to AVDD\n"); + + ret = device_reset(dev); +- if (ret) { +- dev_err(dev, "reset failed\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to reset device\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) { +diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c +--- a/sound/soc/mxs/mxs-saif.c ++++ b/sound/soc/mxs/mxs-saif.c +@@ -826,12 +826,9 @@ static int mxs_saif_probe(struct platform_device *pdev) + mxs_saif[saif->id] = saif; + + saif->clk = devm_clk_get(&pdev->dev, NULL); +- if (IS_ERR(saif->clk)) { +- ret = PTR_ERR(saif->clk); +- dev_err(&pdev->dev, "Cannot get the clock: %d\n", +- ret); +- return ret; +- } ++ if (IS_ERR(saif->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(saif->clk), ++ "Cannot get the clock\n"); + + saif->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(saif->base)) +@@ -844,10 +841,8 @@ static int mxs_saif_probe(struct platform_device *pdev) + saif->dev = &pdev->dev; + ret = devm_request_irq(&pdev->dev, irq, mxs_saif_irq, 0, + dev_name(&pdev->dev), saif); +- if (ret) { +- dev_err(&pdev->dev, "failed to request irq\n"); ++ if (ret) + return ret; +- } + + platform_set_drvdata(pdev, saif); + +@@ -860,16 +855,12 @@ static int mxs_saif_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, &mxs_saif_component, + &mxs_saif_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "register DAI failed\n"); ++ if (ret) + return ret; +- } + + ret = mxs_pcm_platform_register(&pdev->dev); +- if (ret) { +- dev_err(&pdev->dev, "register PCM failed: %d\n", ret); ++ if (ret) + return ret; +- } + + return 0; + } +diff --git a/sound/soc/mxs/mxs-sgtl5000.c b/sound/soc/mxs/mxs-sgtl5000.c +--- a/sound/soc/mxs/mxs-sgtl5000.c ++++ b/sound/soc/mxs/mxs-sgtl5000.c +@@ -155,8 +155,6 @@ static int mxs_sgtl5000_probe(struct platform_device *pdev) + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) { +- dev_err(&pdev->dev, "failed to parse audio-routing (%d)\n", +- ret); + mxs_saif_put_mclk(0); + return ret; + } +diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c +--- a/sound/soc/pxa/pxa-ssp.c ++++ b/sound/soc/pxa/pxa-ssp.c +@@ -769,13 +769,10 @@ static int pxa_ssp_probe(struct snd_soc_dai *dai) + goto err_priv; + } + +- priv->extclk = devm_clk_get(dev, "extclk"); ++ priv->extclk = devm_clk_get_optional(dev, "extclk"); + if (IS_ERR(priv->extclk)) { + ret = PTR_ERR(priv->extclk); +- if (ret == -EPROBE_DEFER) +- goto err_priv; +- +- priv->extclk = NULL; ++ goto err_priv; + } + } else { + priv->ssp = pxa_ssp_request(dai->id + 1, "SoC audio"); +diff --git a/sound/soc/pxa/pxa2xx-ac97-lib.c b/sound/soc/pxa/pxa2xx-ac97-lib.c +--- a/sound/soc/pxa/pxa2xx-ac97-lib.c ++++ b/sound/soc/pxa/pxa2xx-ac97-lib.c +@@ -316,10 +316,8 @@ int pxa2xx_ac97_hw_probe(struct platform_device *dev) + int irq; + + ac97_reg_base = devm_platform_ioremap_resource(dev, 0); +- if (IS_ERR(ac97_reg_base)) { +- dev_err(&dev->dev, "Missing MMIO resource\n"); ++ if (IS_ERR(ac97_reg_base)) + return PTR_ERR(ac97_reg_base); +- } + + if (cpu_is_pxa27x()) { + /* Assert reset using GPIOD_OUT_HIGH, because reset is GPIO_ACTIVE_LOW */ +diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c +--- a/sound/soc/qcom/common.c ++++ b/sound/soc/qcom/common.c +@@ -23,6 +23,161 @@ static const struct snd_soc_dapm_widget qcom_jack_snd_widgets[] = { + SND_SOC_DAPM_SPK("DP7 Jack", NULL), + }; + ++static struct device_node *qcom_snd_get_link_node(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_card *card = rtd->card; ++ struct of_phandle_args args; ++ int ret; ++ ++ if (!card->dev || !card->dev->of_node) ++ return NULL; ++ ++ for_each_available_child_of_node_scoped(card->dev->of_node, np) { ++ struct device_node *cpu_np __free(device_node) = ++ of_get_child_by_name(np, "cpu"); ++ ++ if (!cpu_np) ++ continue; ++ ++ ret = of_parse_phandle_with_args(cpu_np, "sound-dai", "#sound-dai-cells", 0, ++ &args); ++ if (ret) ++ continue; ++ ++ if (args.np == rtd->dai_link->cpus[0].of_node && ++ args.args_count == 1 && args.args[0] == cpu_dai->id) { ++ of_node_put(args.np); ++ return of_node_get(np); ++ } ++ ++ of_node_put(args.np); ++ } ++ ++ return NULL; ++} ++ ++static int qcom_snd_parse_tdm_slot(struct device_node *np, ++ struct qcom_snd_tdm_slot_cfg *cfg) ++{ ++ memset(cfg, 0, sizeof(*cfg)); ++ ++ return snd_soc_of_parse_tdm_slot(np, &cfg->tx_mask, &cfg->rx_mask, ++ &cfg->slots, &cfg->slot_width); ++} ++ ++static int qcom_snd_normalize_tdm_slots(struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ unsigned int slots; ++ unsigned int slot_width; ++ ++ if (cpu_cfg->slots && codec_cfg->slots && cpu_cfg->slots != codec_cfg->slots) ++ return -EINVAL; ++ ++ if (cpu_cfg->slot_width && codec_cfg->slot_width && ++ cpu_cfg->slot_width != codec_cfg->slot_width) ++ return -EINVAL; ++ ++ slots = cpu_cfg->slots ?: codec_cfg->slots; ++ if (!slots) ++ return 0; ++ ++ slot_width = cpu_cfg->slot_width ?: codec_cfg->slot_width; ++ if (!slot_width) ++ return -EINVAL; ++ ++ cpu_cfg->slots = slots; ++ codec_cfg->slots = slots; ++ cpu_cfg->slot_width = slot_width; ++ codec_cfg->slot_width = slot_width; ++ ++ return 0; ++} ++ ++static int qcom_snd_parse_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, ++ struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ struct device_node *link_np __free(device_node) = qcom_snd_get_link_node(rtd); ++ int ret; ++ ++ if (!link_np) ++ return -EINVAL; ++ ++ struct device_node *cpu_np __free(device_node) = ++ of_get_child_by_name(link_np, "cpu"); ++ struct device_node *codec_np __free(device_node) = ++ of_get_child_by_name(link_np, "codec"); ++ if (!cpu_np || !codec_np) ++ return -EINVAL; ++ ++ ret = qcom_snd_parse_tdm_slot(cpu_np, cpu_cfg); ++ if (ret) ++ return ret; ++ ++ return qcom_snd_parse_tdm_slot(codec_np, codec_cfg); ++} ++ ++int qcom_snd_get_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, ++ struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ int ret; ++ ++ ret = qcom_snd_parse_dai_tdm_slots(rtd, cpu_cfg, codec_cfg); ++ if (ret) ++ return ret; ++ ++ return qcom_snd_normalize_tdm_slots(cpu_cfg, codec_cfg); ++} ++EXPORT_SYMBOL_GPL(qcom_snd_get_dai_tdm_slots); ++ ++int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd, ++ const struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ const struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_dai *codec_dai; ++ int i; ++ int ret; ++ ++ if (!cpu_cfg->slots) ++ return 0; ++ ++ ret = snd_soc_dai_set_tdm_slot(cpu_dai, cpu_cfg->tx_mask, cpu_cfg->rx_mask, ++ cpu_cfg->slots, cpu_cfg->slot_width); ++ if (ret) ++ return ret; ++ ++ for_each_rtd_codec_dais(rtd, i, codec_dai) { ++ ret = snd_soc_dai_set_tdm_slot(codec_dai, ++ codec_cfg->tx_mask, ++ codec_cfg->rx_mask, ++ codec_cfg->slots, ++ codec_cfg->slot_width); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(qcom_snd_apply_dai_tdm_slots_cfg); ++ ++int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct qcom_snd_tdm_slot_cfg cpu_cfg; ++ struct qcom_snd_tdm_slot_cfg codec_cfg; ++ int ret; ++ ++ ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); ++ if (ret) ++ return ret == -EINVAL ? 0 : ret; ++ ++ return qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); ++} ++EXPORT_SYMBOL_GPL(qcom_snd_apply_dai_tdm_slots); ++ + int qcom_snd_parse_of(struct snd_soc_card *card) + { + struct device *dev = card->dev; +@@ -91,7 +246,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + + ret = of_property_read_string(np, "link-name", &link->name); + if (ret) { +- dev_err(card->dev, "error getting codec dai_link name\n"); ++ dev_err(dev, "error getting codec dai_link name\n"); + return ret; + } + +@@ -109,7 +264,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + + ret = snd_soc_of_get_dlc(cpu, &args, link->cpus, 0); + if (ret) { +- dev_err_probe(card->dev, ret, ++ dev_err_probe(dev, ret, + "%s: error getting cpu dai name\n", link->name); + return ret; + } +@@ -126,7 +281,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + "sound-dai", + 0); + if (!link->platforms->of_node) { +- dev_err(card->dev, "%s: platform dai not found\n", link->name); ++ dev_err(dev, "%s: platform dai not found\n", link->name); + return -EINVAL; + } + } else { +@@ -136,7 +291,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + if (codec) { + ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); + if (ret < 0) { +- dev_err_probe(card->dev, ret, ++ dev_err_probe(dev, ret, + "%s: codec dai not found\n", link->name); + return ret; + } +@@ -215,6 +370,7 @@ int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, + } + + switch (cpu_dai->id) { ++ case LPI_MI2S_RX_0: + case TX_CODEC_DMA_TX_0: + case TX_CODEC_DMA_TX_1: + case TX_CODEC_DMA_TX_2: +diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h +--- a/sound/soc/qcom/common.h ++++ b/sound/soc/qcom/common.h +@@ -7,9 +7,23 @@ + #include + #include + +-#define LPASS_MAX_PORT (SENARY_MI2S_TX + 1) ++#define LPASS_MAX_PORT (LPI_MI2S_TX_6 + 1) ++ ++struct qcom_snd_tdm_slot_cfg { ++ unsigned int tx_mask; ++ unsigned int rx_mask; ++ unsigned int slots; ++ unsigned int slot_width; ++}; + + int qcom_snd_parse_of(struct snd_soc_card *card); ++int qcom_snd_get_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, ++ struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg); ++int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd, ++ const struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ const struct qcom_snd_tdm_slot_cfg *codec_cfg); ++int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd); + int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, bool *jack_setup); + int qcom_snd_dp_jack_setup(struct snd_soc_pcm_runtime *rtd, +diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c +--- a/sound/soc/qcom/qdsp6/audioreach.c ++++ b/sound/soc/qcom/qdsp6/audioreach.c +@@ -152,6 +152,13 @@ struct apm_i2s_module_intf_cfg { + + #define APM_I2S_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_i2s_module_intf_cfg), 8) + ++struct apm_audio_if_module_intf_cfg { ++ struct apm_module_param_data param_data; ++ struct param_id_audio_if_intf_cfg cfg; ++} __packed; ++ ++#define APM_AUDIO_IF_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_audio_if_module_intf_cfg), 8) ++ + struct apm_module_hw_ep_mf_cfg { + struct apm_module_param_data param_data; + struct param_id_hw_ep_mf mf; +@@ -168,6 +175,13 @@ struct apm_module_frame_size_factor_cfg { + + #define APM_FS_CFG_PSIZE ALIGN(sizeof(struct apm_module_frame_size_factor_cfg), 8) + ++struct apm_module_hw_ep_frame_duration_cfg { ++ struct apm_module_param_data param_data; ++ struct param_id_hw_ep_frame_duration frame_duration; ++} __packed; ++ ++#define APM_HW_EP_FRAME_DURATION_PSIZE ALIGN(sizeof(struct apm_module_hw_ep_frame_duration_cfg), 8) ++ + struct apm_module_hw_ep_power_mode_cfg { + struct apm_module_param_data param_data; + struct param_id_hw_ep_power_mode_cfg power_mode; +@@ -703,6 +717,7 @@ static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph, + int pm_sz = APM_HW_EP_PMODE_CFG_PSIZE; + int size = ic_sz + ep_sz + fs_sz + pm_sz; + void *p; ++ int i; + + struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0); + if (IS_ERR(pkt)) +@@ -741,7 +756,12 @@ static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph, + + intf_cfg->cfg.lpaif_type = module->hw_interface_type; + intf_cfg->cfg.intf_index = module->hw_interface_idx; +- intf_cfg->cfg.active_channels_mask = (1 << cfg->num_channels) - 1; ++ intf_cfg->cfg.active_channels_mask = 0; ++ /* Convert the physical channel mapping into a bit field */ ++ for (i = 0; i < AR_PCM_MAX_NUM_CHANNEL; i++) ++ if (cfg->channel_map[i]) ++ intf_cfg->cfg.active_channels_mask |= BIT(i); ++ + p += ic_sz; + + pm_cfg = p; +@@ -840,7 +860,7 @@ static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, + uint32_t num_channels = cfg->num_channels; + int payload_size = APM_MFC_CFG_PSIZE(media_format, num_channels) + + APM_MODULE_PARAM_DATA_SIZE; +- int i; ++ int i, j; + void *p; + + struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); +@@ -860,8 +880,12 @@ static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, + media_format->sample_rate = cfg->sample_rate; + media_format->bit_width = cfg->bit_width; + media_format->num_channels = cfg->num_channels; +- for (i = 0; i < num_channels; i++) +- media_format->channel_mapping[i] = cfg->channel_map[i]; ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < cfg->num_channels; i++) { ++ if (!cfg->channel_map[i]) ++ continue; ++ media_format->channel_mapping[j++] = cfg->channel_map[i]; ++ } + + return q6apm_send_cmd_sync(graph->apm, pkt, 0); + } +@@ -1042,6 +1066,81 @@ static int audioreach_i2s_set_media_format(struct q6apm_graph *graph, + return q6apm_send_cmd_sync(graph->apm, pkt, 0); + } + ++static int audioreach_audio_if_set_media_format(struct q6apm_graph *graph, ++ const struct audioreach_module *module, ++ const struct audioreach_module_config *cfg) ++{ ++ struct apm_module_hw_ep_frame_duration_cfg *fd_cfg; ++ struct apm_module_param_data *param_data; ++ struct apm_audio_if_module_intf_cfg *intf_cfg; ++ struct apm_module_hw_ep_mf_cfg *hw_cfg; ++ int ic_sz = APM_AUDIO_IF_INTF_CFG_PSIZE; ++ int ep_sz = APM_HW_EP_CFG_PSIZE; ++ int fd_sz = APM_HW_EP_FRAME_DURATION_PSIZE; ++ int size = ic_sz + ep_sz + fd_sz; ++ u32 slot_mask = cfg->slot_mask ? cfg->slot_mask : module->slot_mask; ++ u16 nslots_per_frame = cfg->nslots_per_frame ? ++ (u16)cfg->nslots_per_frame : module->nslots_per_frame; ++ u16 slot_width = cfg->slot_width ? (u16)cfg->slot_width : module->slot_width; ++ void *p; ++ ++ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0); ++ if (IS_ERR(pkt)) ++ return PTR_ERR(pkt); ++ ++ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; ++ intf_cfg = p; ++ ++ param_data = &intf_cfg->param_data; ++ param_data->module_instance_id = module->instance_id; ++ param_data->error_code = 0; ++ param_data->param_id = PARAM_ID_AUDIO_IF_INTF_CFG; ++ param_data->param_size = ic_sz - APM_MODULE_PARAM_DATA_SIZE; ++ intf_cfg->cfg.qaif_type = module->qaif_type; ++ intf_cfg->cfg.intf_idx = (u16)module->hw_interface_idx; ++ intf_cfg->cfg.intf_mode = module->intf_mode; ++ intf_cfg->cfg.ctrl_data_out_enable = module->ctrl_data_out_enable; ++ intf_cfg->cfg.active_slot_mask = slot_mask; ++ intf_cfg->cfg.nslots_per_frame = nslots_per_frame; ++ intf_cfg->cfg.slot_width = slot_width; ++ intf_cfg->cfg.active_lane_mask = module->active_lane_mask; ++ intf_cfg->cfg.frame_sync_rate = module->frame_sync_rate; ++ intf_cfg->cfg.frame_sync_src = module->sync_src; ++ intf_cfg->cfg.frame_sync_mode = module->sync_mode; ++ intf_cfg->cfg.invert_frame_sync_pulse = module->ctrl_invert_sync_pulse; ++ intf_cfg->cfg.frame_sync_data_delay = module->ctrl_sync_data_delay; ++ intf_cfg->cfg.bit_clk_type = module->bit_clk_type; ++ intf_cfg->cfg.inv_int_bit_clk = module->inv_int_bit_clk; ++ intf_cfg->cfg.inv_ext_bit_clk = module->inv_ext_bit_clk; ++ ++ p += ic_sz; ++ hw_cfg = p; ++ param_data = &hw_cfg->param_data; ++ param_data->module_instance_id = module->instance_id; ++ param_data->error_code = 0; ++ param_data->param_id = PARAM_ID_HW_EP_MF_CFG; ++ param_data->param_size = ep_sz - APM_MODULE_PARAM_DATA_SIZE; ++ ++ hw_cfg->mf.sample_rate = cfg->sample_rate; ++ hw_cfg->mf.bit_width = cfg->bit_width; ++ hw_cfg->mf.num_channels = cfg->num_channels; ++ hw_cfg->mf.data_format = module->data_format; ++ ++ p += ep_sz; ++ fd_cfg = p; ++ param_data = &fd_cfg->param_data; ++ param_data->module_instance_id = module->instance_id; ++ param_data->error_code = 0; ++ param_data->param_id = PARAM_ID_HW_EP_FRAME_DURATION; ++ param_data->param_size = fd_sz - APM_MODULE_PARAM_DATA_SIZE; ++ fd_cfg->frame_duration.frame_duration_in_us = AUDIO_IF_FRAME_DURATION_US; ++ fd_cfg->frame_duration.allow_frame_duration_normalization = AUDIO_IF_FRAME_DURATION_NORMALIZATION_ENABLE; ++ fd_cfg->frame_duration.min_normalized_frame_dur_us = AUDIO_IF_FRAME_DURATION_MIN_US; ++ fd_cfg->frame_duration.max_normalized_frame_dur_us = AUDIO_IF_FRAME_DURATION_MAX_US; ++ ++ return q6apm_send_cmd_sync(graph->apm, pkt, 0); ++} ++ + static int audioreach_logging_set_media_format(struct q6apm_graph *graph, + const struct audioreach_module *module) + { +@@ -1080,6 +1179,7 @@ static int audioreach_pcm_set_media_format(struct q6apm_graph *graph, + struct apm_pcm_module_media_fmt_cmd *cfg; + struct apm_module_param_data *param_data; + int payload_size; ++ int i, j; + + if (num_channels > 4) { + dev_err(graph->dev, "Error: Invalid channels (%d)!\n", num_channels); +@@ -1113,7 +1213,12 @@ static int audioreach_pcm_set_media_format(struct q6apm_graph *graph, + media_cfg->num_channels = mcfg->num_channels; + media_cfg->q_factor = mcfg->bit_width - 1; + media_cfg->bits_per_sample = mcfg->bit_width; +- memcpy(media_cfg->channel_mapping, mcfg->channel_map, mcfg->num_channels); ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < mcfg->num_channels; i++) { ++ if (!mcfg->channel_map[i]) ++ continue; ++ media_cfg->channel_mapping[j++] = mcfg->channel_map[i]; ++ } + + return q6apm_send_cmd_sync(graph->apm, pkt, 0); + } +@@ -1163,6 +1268,7 @@ static int audioreach_shmem_set_media_format(struct q6apm_graph *graph, + struct payload_media_fmt_pcm *cfg; + struct media_format *header; + int rc, payload_size; ++ int i, j; + void *p; + + if (num_channels > 4) { +@@ -1202,7 +1308,12 @@ static int audioreach_shmem_set_media_format(struct q6apm_graph *graph, + cfg->q_factor = mcfg->bit_width - 1; + cfg->endianness = PCM_LITTLE_ENDIAN; + cfg->num_channels = mcfg->num_channels; +- memcpy(cfg->channel_mapping, mcfg->channel_map, mcfg->num_channels); ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < cfg->num_channels; i++) { ++ if (!mcfg->channel_map[i]) ++ continue; ++ cfg->channel_mapping[j++] = mcfg->channel_map[i]; ++ } + } else { + rc = audioreach_set_compr_media_format(header, p, mcfg); + if (rc) +@@ -1279,7 +1390,7 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph, + struct apm_module_sp_vi_ex_mode_cfg *ex_cfg; + int op_sz, cm_sz, ex_sz; + struct apm_module_param_data *param_data; +- int rc, i, payload_size; ++ int rc, i, payload_size, j; + struct gpr_pkt *pkt; + void *p; + +@@ -1320,14 +1431,19 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph, + param_data->param_size = cm_sz - APM_MODULE_PARAM_DATA_SIZE; + + cm_cfg->cfg.num_channels = num_channels * 2; +- for (i = 0; i < num_channels; i++) { ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < num_channels; i++) { ++ if (!mcfg->channel_map[i]) ++ continue; + /* + * Map speakers into Vsense and then Isense of each channel. + * E.g. for PCM_CHANNEL_FL and PCM_CHANNEL_FR to: + * [1, 2, 3, 4] + */ +- cm_cfg->cfg.channel_mapping[2 * i] = (mcfg->channel_map[i] - 1) * 2 + 1; +- cm_cfg->cfg.channel_mapping[2 * i + 1] = (mcfg->channel_map[i] - 1) * 2 + 2; ++ cm_cfg->cfg.channel_mapping[2 * j] = (mcfg->channel_map[i] - 1) * 2 + 1; ++ cm_cfg->cfg.channel_mapping[2 * j + 1] = (mcfg->channel_map[i] - 1) * 2 + 2; ++ ++ ++j; + } + + p += cm_sz; +@@ -1411,6 +1527,10 @@ int audioreach_set_media_format(struct q6apm_graph *graph, + if (!rc) + rc = audioreach_module_enable(graph, module, true); + break; ++ case MODULE_ID_AUDIO_IF_SOURCE: ++ case MODULE_ID_AUDIO_IF_SINK: ++ rc = audioreach_audio_if_set_media_format(graph, module, cfg); ++ break; + + default: + rc = 0; +diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h +--- a/sound/soc/qcom/qdsp6/audioreach.h ++++ b/sound/soc/qcom/qdsp6/audioreach.h +@@ -36,6 +36,8 @@ struct q6apm_graph; + #define MODULE_ID_SPEAKER_PROTECTION 0x070010E2 + #define MODULE_ID_SPEAKER_PROTECTION_VI 0x070010E3 + #define MODULE_ID_OPUS_DEC 0x07001174 ++#define MODULE_ID_AUDIO_IF_SINK 0x0700117C ++#define MODULE_ID_AUDIO_IF_SOURCE 0x0700117D + + #define APM_CMD_GET_SPF_STATE 0x01001021 + #define APM_CMD_RSP_GET_SPF_STATE 0x02001007 +@@ -544,6 +546,74 @@ struct param_id_i2s_intf_cfg { + #define PORT_ID_I2S_OUPUT 1 + #define I2S_STACK_SIZE 2048 + ++#define PARAM_ID_AUDIO_IF_INTF_CFG 0x08001B11 ++ ++/* ++ * struct param_id_audio_if_intf_cfg - Audio interface configuration ++ * @qaif_type: Audio interface type (e.g. QAIF, QAIF_VA) ++ * @intf_idx: Interface instance index ++ * @intf_mode: Interface operating mode (TDM/PCM/I2S) ++ * @ctrl_data_out_enable: Enable sharing of data-out signal with other masters ++ * @active_slot_mask: Bitmask indicating active slots ++ * @nslots_per_frame: Number of slots per audio frame ++ * @slot_width: Width of each slot in bits ++ * @active_lane_mask: Bitmask of active data lanes ++ * @frame_sync_rate: Frame sync rate in Hz ++ * @frame_sync_src: Frame sync source selection ++ * @frame_sync_mode: Frame sync mode configuration ++ * @invert_frame_sync_pulse: Invert frame sync polarity when set ++ * @frame_sync_data_delay: Data delay from frame sync in bit clocks ++ * @bit_clk_type: Bit clock type (internal / external) ++ * @inv_int_bit_clk: Invert internal bit clock when set ++ * @inv_ext_bit_clk: Invert external bit clock when set ++ * ++ * This structure defines configuration parameters for the Qualcomm ++ * Audio Interface (QAIF) block. It is used to program interface ++ * characteristics such as slot configuration, clocking and frame ++ * synchronization behaviour. ++ */ ++struct param_id_audio_if_intf_cfg { ++ uint16_t qaif_type; ++ uint16_t intf_idx; ++ uint16_t intf_mode; ++ uint16_t ctrl_data_out_enable; ++ uint32_t active_slot_mask; ++ uint16_t nslots_per_frame; ++ uint16_t slot_width; ++ uint32_t active_lane_mask; ++ uint32_t frame_sync_rate; ++ uint16_t frame_sync_src; ++ uint16_t frame_sync_mode; ++ uint16_t invert_frame_sync_pulse; ++ uint16_t frame_sync_data_delay; ++ uint16_t bit_clk_type; ++ uint8_t inv_int_bit_clk; ++ uint8_t inv_ext_bit_clk; ++} __packed; ++ ++#define PARAM_ID_HW_EP_FRAME_DURATION 0x08001B2F ++#define AUDIO_IF_FRAME_DURATION_US 1000 ++#define AUDIO_IF_FRAME_DURATION_NORMALIZATION_ENABLE 1 ++#define AUDIO_IF_FRAME_DURATION_MIN_US 1 ++#define AUDIO_IF_FRAME_DURATION_MAX_US 100000 ++ ++/** ++ * struct param_id_hw_ep_frame_duration - Hardware endpoint frame duration ++ * @frame_duration_in_us: Frame duration in microseconds. ++ * @allow_frame_duration_normalization: Permit SPF to normalize frame duration. ++ * @min_normalized_frame_dur_us: Minimum normalized frame duration in microseconds. ++ * @max_normalized_frame_dur_us: Maximum normalized frame duration in microseconds. ++ * ++ * This structure configures the frame duration for the Audio IF hardware ++ * endpoint and, when enabled, the allowed normalization range. ++ */ ++struct param_id_hw_ep_frame_duration { ++ uint32_t frame_duration_in_us; ++ uint32_t allow_frame_duration_normalization; ++ uint32_t min_normalized_frame_dur_us; ++ uint32_t max_normalized_frame_dur_us; ++} __packed; ++ + #define PARAM_ID_DISPLAY_PORT_INTF_CFG 0x08001154 + + struct param_id_display_port_intf_cfg { +@@ -877,6 +947,23 @@ struct audioreach_module { + uint32_t data_format; + uint32_t hw_interface_type; + ++ /* Audio IF module (TDM/PCM/I2S) */ ++ u32 slot_mask; ++ u32 active_lane_mask; ++ u32 frame_sync_rate; ++ u16 qaif_type; ++ u16 sync_src; ++ u16 ctrl_data_out_enable; ++ u16 nslots_per_frame; ++ u16 slot_width; ++ u16 intf_mode; ++ u16 sync_mode; ++ u16 ctrl_invert_sync_pulse; ++ u16 ctrl_sync_data_delay; ++ u16 bit_clk_type; ++ u8 inv_int_bit_clk; ++ u8 inv_ext_bit_clk; ++ + /* PCM module specific */ + uint32_t interleave_type; + +@@ -907,6 +994,9 @@ struct audioreach_module_config { + u32 channel_allocation; + u32 sd_line_mask; + int fmt; ++ u32 slot_mask; ++ u16 nslots_per_frame; ++ u16 slot_width; + struct snd_codec codec; + u8 channel_map[AR_PCM_MAX_NUM_CHANNEL]; + }; +diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c +--- a/sound/soc/qcom/qdsp6/q6afe-dai.c ++++ b/sound/soc/qcom/qdsp6/q6afe-dai.c +@@ -412,6 +412,8 @@ static int q6afe_dai_prepare(struct snd_pcm_substream *substream, + case SENARY_MI2S_RX ... SENARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + rc = q6afe_i2s_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].i2s_cfg); + if (rc < 0) { +@@ -665,6 +667,21 @@ static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { + + /* USB playback AFE port receives data for playback, hence use the RX port */ + {"USB Playback", NULL, "USB_RX"}, ++ ++ {"LPI RX0 MI2S Playback", NULL, "LPI_MI2S_RX_0"}, ++ {"LPI_MI2S_TX_0", NULL, "LPI TX0 MI2S Capture"}, ++ {"LPI RX1 MI2S Playback", NULL, "LPI_MI2S_RX_1"}, ++ {"LPI_MI2S_TX_1", NULL, "LPI TX1 MI2S Capture"}, ++ {"LPI RX2 MI2S Playback", NULL, "LPI_MI2S_RX_2"}, ++ {"LPI_MI2S_TX_2", NULL, "LPI TX2 MI2S Capture"}, ++ {"LPI RX3 MI2S Playback", NULL, "LPI_MI2S_RX_3"}, ++ {"LPI_MI2S_TX_3", NULL, "LPI TX3 MI2S Capture"}, ++ {"LPI RX4 MI2S Playback", NULL, "LPI_MI2S_RX_4"}, ++ {"LPI_MI2S_TX_4", NULL, "LPI TX4 MI2S Capture"}, ++ {"LPI RX5 MI2S Playback", NULL, "LPI_MI2S_RX_5"}, ++ {"LPI_MI2S_TX_5", NULL, "LPI TX5 MI2S Capture"}, ++ {"LPI RX6 MI2S Playback", NULL, "LPI_MI2S_RX_6"}, ++ {"LPI_MI2S_TX_6", NULL, "LPI TX6 MI2S Capture"}, + }; + + static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) +@@ -1011,6 +1028,35 @@ static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { + 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("USB_RX", NULL, 0, SND_SOC_NOPM, 0, 0), ++ ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_0", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_0", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_1", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_1", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_2", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_2", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_3", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_3", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_4", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_4", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_5", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_5", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_6", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_6", NULL, ++ 0, SND_SOC_NOPM, 0, 0), + }; + + static const struct snd_soc_component_driver q6afe_dai_component = { +@@ -1045,6 +1091,8 @@ static void of_q6afe_parse_dai_data(struct device *dev, + case SENARY_MI2S_RX ... SENARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + priv = &data->priv[id]; + ret = of_property_read_variable_u32_array(node, + "qcom,sd-lines", +diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c +--- a/sound/soc/qcom/qdsp6/q6afe.c ++++ b/sound/soc/qcom/qdsp6/q6afe.c +@@ -132,6 +132,20 @@ + #define AFE_PORT_ID_QUINARY_MI2S_TX 0x1017 + #define AFE_PORT_ID_SENARY_MI2S_RX 0x1018 + #define AFE_PORT_ID_SENARY_MI2S_TX 0x1019 ++#define AFE_PORT_ID_INT0_MI2S_RX 0x102e ++#define AFE_PORT_ID_INT0_MI2S_TX 0x102f ++#define AFE_PORT_ID_INT1_MI2S_RX 0x1030 ++#define AFE_PORT_ID_INT1_MI2S_TX 0x1031 ++#define AFE_PORT_ID_INT2_MI2S_RX 0x1032 ++#define AFE_PORT_ID_INT2_MI2S_TX 0x1033 ++#define AFE_PORT_ID_INT3_MI2S_RX 0x1034 ++#define AFE_PORT_ID_INT3_MI2S_TX 0x1035 ++#define AFE_PORT_ID_INT4_MI2S_RX 0x1036 ++#define AFE_PORT_ID_INT4_MI2S_TX 0x1037 ++#define AFE_PORT_ID_INT5_MI2S_RX 0x1038 ++#define AFE_PORT_ID_INT5_MI2S_TX 0x1039 ++#define AFE_PORT_ID_INT6_MI2S_RX 0x103a ++#define AFE_PORT_ID_INT6_MI2S_TX 0x103b + + /* Start of the range of port IDs for TDM devices. */ + #define AFE_PORT_ID_TDM_PORT_RANGE_START 0x9000 +@@ -931,6 +945,34 @@ static struct afe_port_map port_maps[AFE_PORT_MAX] = { + [RX_CODEC_DMA_RX_7] = { AFE_PORT_ID_RX_CODEC_DMA_RX_7, + RX_CODEC_DMA_RX_7, 1, 1}, + [USB_RX] = { AFE_PORT_ID_USB_RX, USB_RX, 1, 1}, ++ [LPI_MI2S_RX_0] = { AFE_PORT_ID_INT0_MI2S_RX, ++ LPI_MI2S_RX_0, 1, 1}, ++ [LPI_MI2S_TX_0] = { AFE_PORT_ID_INT0_MI2S_TX, ++ LPI_MI2S_TX_0, 0, 1}, ++ [LPI_MI2S_RX_1] = { AFE_PORT_ID_INT1_MI2S_RX, ++ LPI_MI2S_RX_1, 1, 1}, ++ [LPI_MI2S_TX_1] = { AFE_PORT_ID_INT1_MI2S_TX, ++ LPI_MI2S_TX_1, 0, 1}, ++ [LPI_MI2S_RX_2] = { AFE_PORT_ID_INT2_MI2S_RX, ++ LPI_MI2S_RX_2, 1, 1}, ++ [LPI_MI2S_TX_2] = { AFE_PORT_ID_INT2_MI2S_TX, ++ LPI_MI2S_TX_2, 0, 1}, ++ [LPI_MI2S_RX_3] = { AFE_PORT_ID_INT3_MI2S_RX, ++ LPI_MI2S_RX_3, 1, 1}, ++ [LPI_MI2S_TX_3] = { AFE_PORT_ID_INT3_MI2S_TX, ++ LPI_MI2S_TX_3, 0, 1}, ++ [LPI_MI2S_RX_4] = { AFE_PORT_ID_INT4_MI2S_RX, ++ LPI_MI2S_RX_4, 1, 1}, ++ [LPI_MI2S_TX_4] = { AFE_PORT_ID_INT4_MI2S_TX, ++ LPI_MI2S_TX_4, 0, 1}, ++ [LPI_MI2S_RX_5] = { AFE_PORT_ID_INT5_MI2S_RX, ++ LPI_MI2S_RX_5, 1, 1}, ++ [LPI_MI2S_TX_5] = { AFE_PORT_ID_INT5_MI2S_TX, ++ LPI_MI2S_TX_5, 0, 1}, ++ [LPI_MI2S_RX_6] = { AFE_PORT_ID_INT6_MI2S_RX, ++ LPI_MI2S_RX_6, 1, 1}, ++ [LPI_MI2S_TX_6] = { AFE_PORT_ID_INT6_MI2S_TX, ++ LPI_MI2S_TX_6, 0, 1}, + }; + + static void q6afe_port_free(struct kref *ref) +@@ -1785,6 +1827,20 @@ struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) + case AFE_PORT_ID_QUINARY_MI2S_TX: + case AFE_PORT_ID_SENARY_MI2S_RX: + case AFE_PORT_ID_SENARY_MI2S_TX: ++ case AFE_PORT_ID_INT0_MI2S_RX: ++ case AFE_PORT_ID_INT0_MI2S_TX: ++ case AFE_PORT_ID_INT1_MI2S_RX: ++ case AFE_PORT_ID_INT1_MI2S_TX: ++ case AFE_PORT_ID_INT2_MI2S_RX: ++ case AFE_PORT_ID_INT2_MI2S_TX: ++ case AFE_PORT_ID_INT3_MI2S_RX: ++ case AFE_PORT_ID_INT3_MI2S_TX: ++ case AFE_PORT_ID_INT4_MI2S_RX: ++ case AFE_PORT_ID_INT4_MI2S_TX: ++ case AFE_PORT_ID_INT5_MI2S_RX: ++ case AFE_PORT_ID_INT5_MI2S_TX: ++ case AFE_PORT_ID_INT6_MI2S_RX: ++ case AFE_PORT_ID_INT6_MI2S_TX: + cfg_type = AFE_PARAM_ID_I2S_CONFIG; + break; + case AFE_PORT_ID_PRIMARY_TDM_RX ... AFE_PORT_ID_QUINARY_TDM_TX_7: +diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +--- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c ++++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +@@ -2,10 +2,12 @@ + // Copyright (c) 2021, Linaro Limited + + #include ++#include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -15,15 +17,54 @@ + #include "q6dsp-common.h" + #include "audioreach.h" + #include "q6apm.h" ++#include "q6prm.h" + + #define AUDIOREACH_BE_PCM_BASE 16 + ++struct q6apm_dai_priv_data { ++ struct clk *mclk; ++ struct clk *bclk; ++ bool mclk_enabled, bclk_enabled; ++}; ++ + struct q6apm_lpass_dai_data { + struct q6apm_graph *graph[APM_PORT_MAX]; + bool is_port_started[APM_PORT_MAX]; + struct audioreach_module_config module_config[APM_PORT_MAX]; ++ struct q6apm_dai_priv_data priv[APM_PORT_MAX]; + }; + ++static void q6apm_lpass_dai_disable_clocks(struct q6apm_lpass_dai_data *dai_data, int id) ++{ ++ if (dai_data->priv[id].mclk_enabled) { ++ clk_disable_unprepare(dai_data->priv[id].mclk); ++ dai_data->priv[id].mclk_enabled = false; ++ } ++ ++ if (dai_data->priv[id].bclk_enabled) { ++ clk_disable_unprepare(dai_data->priv[id].bclk); ++ dai_data->priv[id].bclk_enabled = false; ++ } ++} ++ ++static void q6apm_lpass_dai_put_clocks(struct q6apm_lpass_dai_data *dai_data) ++{ ++ int i; ++ ++ for (i = 0; i < APM_PORT_MAX; i++) { ++ q6apm_lpass_dai_disable_clocks(dai_data, i); ++ ++ if (dai_data->priv[i].mclk) { ++ clk_put(dai_data->priv[i].mclk); ++ dai_data->priv[i].mclk = NULL; ++ } ++ if (dai_data->priv[i].bclk) { ++ clk_put(dai_data->priv[i].bclk); ++ dai_data->priv[i].bclk = NULL; ++ } ++ } ++} ++ + static int q6dma_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, + const unsigned int *tx_ch_mask, +@@ -251,6 +292,62 @@ static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, struct s + return 0; + } + ++static int q6i2s_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) ++{ ++ return q6apm_lpass_dai_startup(substream, dai); ++} ++ ++static void q6i2s_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) ++{ ++ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); ++ ++ q6apm_lpass_dai_shutdown(substream, dai); ++ q6apm_lpass_dai_disable_clocks(dai_data, dai->id); ++} ++ ++static int q6i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) ++{ ++ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); ++ struct clk *sysclk = NULL; ++ bool *enabled = NULL; ++ int ret = 0; ++ ++ switch (clk_id) { ++ case LPAIF_MI2S_MCLK: ++ sysclk = dai_data->priv[dai->id].mclk; ++ enabled = &dai_data->priv[dai->id].mclk_enabled; ++ break; ++ case LPAIF_MI2S_BCLK: ++ sysclk = dai_data->priv[dai->id].bclk; ++ enabled = &dai_data->priv[dai->id].bclk_enabled; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ if (sysclk) { ++ ret = clk_set_rate(sysclk, freq); ++ if (ret) { ++ dev_err(dai->dev, "Error, Unable to set rate (%d) for sysclk %d\n", ++ freq, clk_id); ++ return ret; ++ } ++ ++ if (*enabled) ++ return 0; ++ ++ ret = clk_prepare_enable(sysclk); ++ if (ret) { ++ dev_err(dai->dev, "Error, Unable to prepare (%d) sysclk\n", clk_id); ++ return ret; ++ } ++ ++ *enabled = true; ++ } ++ ++ return ret; ++} ++ + static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + { + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); +@@ -261,6 +358,53 @@ static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + return 0; + } + ++static int q6tdm_set_tdm_slot(struct snd_soc_dai *dai, ++ unsigned int tx_mask, ++ unsigned int rx_mask, ++ int slots, int slot_width) ++{ ++ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); ++ struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; ++ unsigned int cap_mask, slot_mask; ++ ++ if (slot_width != 16 && slot_width != 32) { ++ dev_err(dai->dev, "%s: invalid slot_width %d\n", __func__, slot_width); ++ return -EINVAL; ++ } ++ ++ switch (slots) { ++ case 2: ++ case 4: ++ case 8: ++ case 16: ++ cap_mask = GENMASK(slots - 1, 0); ++ break; ++ default: ++ dev_err(dai->dev, "%s: invalid slots %d\n", __func__, slots); ++ return -EINVAL; ++ } ++ ++ switch (dai->id) { ++ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: ++ slot_mask = (dai->id & 0x1) ? tx_mask : rx_mask; ++ if (slot_mask & ~cap_mask) { ++ dev_err(dai->dev, "%s: invalid slot mask 0x%x for %d slots\n", ++ __func__, slot_mask, slots); ++ return -EINVAL; ++ } ++ ++ cfg->nslots_per_frame = slots; ++ cfg->slot_width = slot_width; ++ cfg->slot_mask = slot_mask; ++ break; ++ default: ++ dev_err(dai->dev, "%s: invalid dai id 0x%x\n", __func__, dai->id); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ + static const struct snd_soc_dai_ops q6dma_ops = { + .prepare = q6apm_lpass_dai_prepare, + .startup = q6apm_lpass_dai_startup, +@@ -272,11 +416,12 @@ static const struct snd_soc_dai_ops q6dma_ops = { + + static const struct snd_soc_dai_ops q6i2s_ops = { + .prepare = q6apm_lpass_dai_prepare, +- .startup = q6apm_lpass_dai_startup, +- .shutdown = q6apm_lpass_dai_shutdown, ++ .startup = q6i2s_dai_startup, ++ .shutdown = q6i2s_lpass_dai_shutdown, + .set_channel_map = q6dma_set_channel_map, + .hw_params = q6dma_hw_params, + .set_fmt = q6i2s_set_fmt, ++ .set_sysclk = q6i2s_set_sysclk, + .trigger = q6apm_lpass_dai_trigger, + }; + +@@ -289,6 +434,17 @@ static const struct snd_soc_dai_ops q6hdmi_ops = { + .trigger = q6apm_lpass_dai_trigger, + }; + ++static const struct snd_soc_dai_ops q6tdm_ops = { ++ .prepare = q6apm_lpass_dai_prepare, ++ .startup = q6apm_lpass_dai_startup, ++ .shutdown = q6i2s_lpass_dai_shutdown, ++ .set_tdm_slot = q6tdm_set_tdm_slot, ++ .hw_params = q6dma_hw_params, ++ .set_fmt = q6i2s_set_fmt, ++ .set_sysclk = q6i2s_set_sysclk, ++ .trigger = q6apm_lpass_dai_trigger, ++}; ++ + static const struct snd_soc_component_driver q6apm_lpass_dai_component = { + .name = "q6apm-be-dai-component", + .of_xlate_dai_name = q6dsp_audio_ports_of_xlate_dai_name, +@@ -297,6 +453,65 @@ static const struct snd_soc_component_driver q6apm_lpass_dai_component = { + .remove_order = SND_SOC_COMP_ORDER_FIRST, + }; + ++static int of_q6apm_parse_dai_data(struct device *dev, ++ struct q6apm_lpass_dai_data *data) ++{ ++ int ret; ++ ++ for_each_child_of_node_scoped(dev->of_node, node) { ++ struct q6apm_dai_priv_data *priv; ++ int id; ++ ++ ret = of_property_read_u32(node, "reg", &id); ++ if (ret || id < 0 || id >= APM_PORT_MAX) { ++ dev_err(dev, "valid dai id not found:%d\n", ret); ++ continue; ++ } ++ ++ switch (id) { ++ /* MI2S specific properties */ ++ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: ++ case SENARY_MI2S_RX ... SENARY_MI2S_TX: ++ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: ++ priv = &data->priv[id]; ++ priv->mclk = of_clk_get_by_name(node, "mclk"); ++ if (IS_ERR(priv->mclk)) { ++ int err = PTR_ERR(priv->mclk); ++ ++ priv->mclk = NULL; ++ if (err == -EPROBE_DEFER) { ++ q6apm_lpass_dai_put_clocks(data); ++ return dev_err_probe(dev, err, ++ "unable to get mi2s mclk\n"); ++ } ++ } ++ ++ priv->bclk = of_clk_get_by_name(node, "bclk"); ++ if (IS_ERR(priv->bclk)) { ++ int err = PTR_ERR(priv->bclk); ++ ++ priv->bclk = NULL; ++ if (err == -EPROBE_DEFER) { ++ q6apm_lpass_dai_put_clocks(data); ++ return dev_err_probe(dev, err, ++ "unable to get mi2s bclk\n"); ++ } ++ } ++ break; ++ default: ++ break; ++ } ++ } ++ ++ return 0; ++} ++ ++static void q6apm_lpass_dai_clocks_action(void *data) ++{ ++ q6apm_lpass_dai_put_clocks(data); ++} ++ + static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) + { + struct q6dsp_audio_port_dai_driver_config cfg; +@@ -304,17 +519,26 @@ static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) + struct snd_soc_dai_driver *dais; + struct device *dev = &pdev->dev; + int num_dais; ++ int ret; + + dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); + if (!dai_data) + return -ENOMEM; + + dev_set_drvdata(dev, dai_data); ++ ret = of_q6apm_parse_dai_data(dev, dai_data); ++ if (ret) ++ return ret; ++ ++ ret = devm_add_action_or_reset(dev, q6apm_lpass_dai_clocks_action, dai_data); ++ if (ret) ++ return ret; + + memset(&cfg, 0, sizeof(cfg)); + cfg.q6i2s_ops = &q6i2s_ops; + cfg.q6dma_ops = &q6dma_ops; + cfg.q6hdmi_ops = &q6hdmi_ops; ++ cfg.q6tdm_ops = &q6tdm_ops; + dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); + + return devm_snd_soc_register_component(dev, &q6apm_lpass_dai_component, dais, num_dais); +diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c +--- a/sound/soc/qcom/qdsp6/q6apm.c ++++ b/sound/soc/qcom/qdsp6/q6apm.c +@@ -802,14 +802,17 @@ EXPORT_SYMBOL_GPL(q6apm_graph_prepare); + int q6apm_graph_start(struct q6apm_graph *graph) + { + struct audioreach_graph *ar_graph = graph->ar_graph; +- int ret = 0; ++ int ret; + +- if (ar_graph->start_count == 0) ++ if (ar_graph->start_count == 0) { + ret = audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_START); ++ if (ret) ++ return ret; ++ } + + ar_graph->start_count++; + +- return ret; ++ return 0; + } + EXPORT_SYMBOL_GPL(q6apm_graph_start); + +@@ -817,6 +820,9 @@ int q6apm_graph_stop(struct q6apm_graph *graph) + { + struct audioreach_graph *ar_graph = graph->ar_graph; + ++ if (ar_graph->start_count == 0) ++ return 0; ++ + if (--ar_graph->start_count > 0) + return 0; + +diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c +--- a/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c ++++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c +@@ -12,7 +12,7 @@ + #include + #include "q6dsp-lpass-clocks.h" + +-#define Q6DSP_MAX_CLK_ID 104 ++#define Q6DSP_MAX_CLK_ID 105 + #define Q6DSP_LPASS_CLK_ROOT_DEFAULT 0 + + +diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +--- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c ++++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +@@ -553,11 +553,15 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { + Q6AFE_MI2S_RX_DAI("LPI RX2", LPI_MI2S_RX_2), + Q6AFE_MI2S_RX_DAI("LPI RX3", LPI_MI2S_RX_3), + Q6AFE_MI2S_RX_DAI("LPI RX4", LPI_MI2S_RX_4), ++ Q6AFE_MI2S_RX_DAI("LPI RX5", LPI_MI2S_RX_5), ++ Q6AFE_MI2S_RX_DAI("LPI RX6", LPI_MI2S_RX_6), + Q6AFE_MI2S_TX_DAI("LPI TX0", LPI_MI2S_TX_0), + Q6AFE_MI2S_TX_DAI("LPI TX1", LPI_MI2S_TX_1), + Q6AFE_MI2S_TX_DAI("LPI TX2", LPI_MI2S_TX_2), + Q6AFE_MI2S_TX_DAI("LPI TX3", LPI_MI2S_TX_3), + Q6AFE_MI2S_TX_DAI("LPI TX4", LPI_MI2S_TX_4), ++ Q6AFE_MI2S_TX_DAI("LPI TX5", LPI_MI2S_TX_5), ++ Q6AFE_MI2S_TX_DAI("LPI TX6", LPI_MI2S_TX_6), + Q6AFE_TDM_PB_DAI("Primary", 0, PRIMARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Primary", 1, PRIMARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Primary", 2, PRIMARY_TDM_RX_2), +@@ -712,6 +716,7 @@ struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + q6dsp_audio_fe_dais[i].ops = cfg->q6i2s_ops; + break; + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: +diff --git a/sound/soc/qcom/qdsp6/q6prm-clocks.c b/sound/soc/qcom/qdsp6/q6prm-clocks.c +--- a/sound/soc/qcom/qdsp6/q6prm-clocks.c ++++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c +@@ -42,6 +42,11 @@ static const struct q6dsp_clk_init q6prm_clks[] = { + Q6PRM_CLK(LPASS_CLK_ID_INT5_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_INT6_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QUI_MI2S_OSR), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_1), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_2), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_3), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_4), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_5), + Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_NPL_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_VA_CORE_MCLK), +@@ -59,10 +64,40 @@ static const struct q6dsp_clk_init q6prm_clks[] = { + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_2X_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_EBIT), + Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6PRM_HW_CORE_ID_LPASS, + "LPASS_HW_MACRO"), + Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6PRM_HW_CORE_ID_DCODEC, + "LPASS_HW_DCODEC"), ++ Q6DSP_VOTE_CLK(LPASS_HW_LPR_VOTE, Q6PRM_HW_LPR_VOTE, ++ "LPASS_HW_LPR_VOTE"), + }; + + static const struct q6dsp_clk_desc q6dsp_clk_q6prm __maybe_unused = { +diff --git a/sound/soc/qcom/qdsp6/q6prm.c b/sound/soc/qcom/qdsp6/q6prm.c +--- a/sound/soc/qcom/qdsp6/q6prm.c ++++ b/sound/soc/qcom/qdsp6/q6prm.c +@@ -31,10 +31,16 @@ struct q6prm { + #define PARAM_ID_RSC_HW_CORE 0x08001032 + #define PARAM_ID_RSC_LPASS_CORE 0x0800102B + #define PARAM_ID_RSC_AUDIO_HW_CLK 0x0800102C ++#define PARAM_ID_RSC_CPU_LPR 0x08001A6E ++ ++#define LPR_CPU_SS_SLEEP_DISABLE 0x1 + + struct prm_cmd_request_hw_core { + struct apm_module_param_data param_data; +- uint32_t hw_clk_id; ++ union { ++ u32 hw_clk_id; ++ u32 lpr_state; ++ }; + } __packed; + + struct prm_cmd_request_rsc { +@@ -62,6 +68,7 @@ static int q6prm_set_hw_core_req(struct device *dev, uint32_t hw_block_id, bool + struct prm_cmd_request_hw_core *req; + gpr_device_t *gdev = prm->gdev; + uint32_t opcode, rsp_opcode; ++ bool lpr_req = (hw_block_id == Q6PRM_HW_LPR_VOTE); + + if (enable) { + opcode = PRM_CMD_REQUEST_HW_RSC; +@@ -82,10 +89,13 @@ static int q6prm_set_hw_core_req(struct device *dev, uint32_t hw_block_id, bool + + param_data->module_instance_id = GPR_PRM_MODULE_IID; + param_data->error_code = 0; +- param_data->param_id = PARAM_ID_RSC_HW_CORE; ++ param_data->param_id = lpr_req ? PARAM_ID_RSC_CPU_LPR : PARAM_ID_RSC_HW_CORE; + param_data->param_size = sizeof(*req) - APM_MODULE_PARAM_DATA_SIZE; + +- req->hw_clk_id = hw_block_id; ++ if (lpr_req) ++ req->lpr_state = LPR_CPU_SS_SLEEP_DISABLE; ++ else ++ req->hw_clk_id = hw_block_id; + + return q6prm_send_cmd_sync(prm, pkt, rsp_opcode); + } +diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h +--- a/sound/soc/qcom/qdsp6/q6prm.h ++++ b/sound/soc/qcom/qdsp6/q6prm.h +@@ -3,6 +3,9 @@ + #ifndef __Q6PRM_H__ + #define __Q6PRM_H__ + ++#define LPAIF_MI2S_MCLK 1 ++#define LPAIF_MI2S_BCLK 2 ++ + /* Clock ID for Primary I2S IBIT */ + #define Q6PRM_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100 + /* Clock ID for Primary I2S EBIT */ +@@ -52,6 +55,17 @@ + /* Clock ID for QUINARY MI2S OSR CLK */ + #define Q6PRM_LPASS_CLK_ID_QUI_MI2S_OSR 0x116 + ++/* Clock ID for MCLK1 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_1 0x300 ++/* Clock ID for MCLK2 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_2 0x301 ++/* Clock ID for MCLK3 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_3 0x302 ++/* Clock ID for MCLK4 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_4 0x303 ++/* Clock ID for MCLK5 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_5 0x304 ++ + #define Q6PRM_LPASS_CLK_ID_WSA_CORE_MCLK 0x305 + #define Q6PRM_LPASS_CLK_ID_WSA_CORE_NPL_MCLK 0x306 + +@@ -83,10 +97,40 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 0x318 + ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_IBIT 0x500 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_EBIT 0x501 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_IBIT 0x502 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_EBIT 0x503 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_IBIT 0x504 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_EBIT 0x505 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_IBIT 0x506 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_EBIT 0x507 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_IBIT 0x508 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_EBIT 0x509 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_IBIT 0x50A ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_EBIT 0x50B ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_IBIT 0x50C ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_EBIT 0x50D ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_IBIT 0x50E ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_EBIT 0x50F ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_IBIT 0x510 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_EBIT 0x511 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_IBIT 0x512 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_EBIT 0x513 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_IBIT 0x514 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_EBIT 0x515 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_IBIT 0x516 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_EBIT 0x517 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_IBIT 0x518 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_EBIT 0x519 ++#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 ++#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 ++ + #define Q6PRM_LPASS_CLK_SRC_INTERNAL 1 + #define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0 + #define Q6PRM_HW_CORE_ID_LPASS 1 + #define Q6PRM_HW_CORE_ID_DCODEC 2 ++#define Q6PRM_HW_LPR_VOTE 3 + + int q6prm_set_lpass_clock(struct device *dev, int clk_id, int clk_attr, + int clk_root, unsigned int freq); +diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c +--- a/sound/soc/qcom/qdsp6/q6routing.c ++++ b/sound/soc/qcom/qdsp6/q6routing.c +@@ -127,7 +127,14 @@ + { mix_name, "TX_CODEC_DMA_TX_2", "TX_CODEC_DMA_TX_2"}, \ + { mix_name, "TX_CODEC_DMA_TX_3", "TX_CODEC_DMA_TX_3"}, \ + { mix_name, "TX_CODEC_DMA_TX_4", "TX_CODEC_DMA_TX_4"}, \ +- { mix_name, "TX_CODEC_DMA_TX_5", "TX_CODEC_DMA_TX_5"} ++ { mix_name, "TX_CODEC_DMA_TX_5", "TX_CODEC_DMA_TX_5"}, \ ++ { mix_name, "LPI_MI2S_TX_0", "LPI_MI2S_TX_0" }, \ ++ { mix_name, "LPI_MI2S_TX_1", "LPI_MI2S_TX_1" }, \ ++ { mix_name, "LPI_MI2S_TX_2", "LPI_MI2S_TX_2" }, \ ++ { mix_name, "LPI_MI2S_TX_3", "LPI_MI2S_TX_3" }, \ ++ { mix_name, "LPI_MI2S_TX_4", "LPI_MI2S_TX_4" }, \ ++ { mix_name, "LPI_MI2S_TX_5", "LPI_MI2S_TX_5" }, \ ++ { mix_name, "LPI_MI2S_TX_6", "LPI_MI2S_TX_6" } + + #define Q6ROUTING_TX_MIXERS(id) \ + SOC_SINGLE_EXT("PRI_MI2S_TX", PRIMARY_MI2S_TX, \ +@@ -320,6 +327,27 @@ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TX_CODEC_DMA_TX_5", TX_CODEC_DMA_TX_5, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_0", LPI_MI2S_TX_0, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_1", LPI_MI2S_TX_1, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_2", LPI_MI2S_TX_2, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_3", LPI_MI2S_TX_3, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_4", LPI_MI2S_TX_4, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_5", LPI_MI2S_TX_5, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_6", LPI_MI2S_TX_6, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), + +@@ -709,6 +737,26 @@ static const struct snd_kcontrol_new rxcodec_dma_rx_6_mixer_controls[] = { + static const struct snd_kcontrol_new rx_codec_dma_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_7) }; + ++static const struct snd_kcontrol_new lpi_mi2s_rx_0_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_0) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_1_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_1) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_2_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_2) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_3_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_3) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_4_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_4) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_5_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_5) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_6_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_6) }; + + static const struct snd_kcontrol_new mmul1_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA1) }; +@@ -938,6 +986,27 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { + SND_SOC_DAPM_MIXER("USB_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + usb_rx_mixer_controls, + ARRAY_SIZE(usb_rx_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_0_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_0_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_1_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_1_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_2_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_2_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_3_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_3_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_4_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_4_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_5_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_5_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_6_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0, + mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0, +@@ -1031,6 +1100,13 @@ static const struct snd_soc_dapm_route intercon[] = { + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_6 Audio Mixer", "RX_CODEC_DMA_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_7 Audio Mixer", "RX_CODEC_DMA_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("USB_RX Audio Mixer", "USB_RX"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_0 Audio Mixer", "LPI_MI2S_RX_0"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_1 Audio Mixer", "LPI_MI2S_RX_1"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_2 Audio Mixer", "LPI_MI2S_RX_2"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_3 Audio Mixer", "LPI_MI2S_RX_3"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_4 Audio Mixer", "LPI_MI2S_RX_4"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_5 Audio Mixer", "LPI_MI2S_RX_5"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_6 Audio Mixer", "LPI_MI2S_RX_6"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia1 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia2 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia3 Mixer"), +diff --git a/sound/soc/qcom/qdsp6/topology.c b/sound/soc/qcom/qdsp6/topology.c +--- a/sound/soc/qcom/qdsp6/topology.c ++++ b/sound/soc/qcom/qdsp6/topology.c +@@ -753,6 +753,103 @@ static int audioreach_widget_i2s_module_load(struct audioreach_module *mod, + return 0; + } + ++static int audioreach_widget_audio_if_module_load(struct audioreach_module *mod, ++ const struct snd_soc_tplg_vendor_array *mod_array) ++{ ++ const struct snd_soc_tplg_vendor_value_elem *mod_elem; ++ int tkn_count = 0; ++ u32 val; ++ ++ mod_elem = mod_array->value; ++ ++ while (tkn_count < le32_to_cpu(mod_array->num_elems)) { ++ val = le32_to_cpu(mod_elem->value); ++ switch (le32_to_cpu(mod_elem->token)) { ++ case AR_TKN_U32_MODULE_HW_IF_IDX: ++ mod->hw_interface_idx = val; ++ break; ++ case AR_TKN_U32_MODULE_FMT_DATA: ++ mod->data_format = val; ++ break; ++ case AR_TKN_U16_MODULE_SYNC_SRC: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->sync_src = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->ctrl_data_out_enable = (u16)val; ++ break; ++ case AR_TKN_U32_MODULE_SLOT_MASK: ++ mod->slot_mask = val; ++ break; ++ case AR_TKN_U16_MODULE_NSLOTS_PER_FRAME: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->nslots_per_frame = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_SLOT_WIDTH: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->slot_width = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_INTF_MODE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->intf_mode = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_SYNC_MODE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->sync_mode = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->ctrl_invert_sync_pulse = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->ctrl_sync_data_delay = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_QAIF_TYPE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->qaif_type = (u16)val; ++ break; ++ case AR_TKN_U32_MODULE_ACTIVE_LANE_MASK: ++ mod->active_lane_mask = val; ++ break; ++ case AR_TKN_U32_MODULE_FRAME_SYNC_RATE: ++ mod->frame_sync_rate = val; ++ break; ++ case AR_TKN_U16_MODULE_BIT_CLK_TYPE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->bit_clk_type = (u16)val; ++ break; ++ case AR_TKN_U8_MODULE_INV_INT_BIT_CLK: ++ if (val > U8_MAX) ++ return -EINVAL; ++ mod->inv_int_bit_clk = (u8)val; ++ break; ++ case AR_TKN_U8_MODULE_INV_EXT_BIT_CLK: ++ if (val > U8_MAX) ++ return -EINVAL; ++ mod->inv_ext_bit_clk = (u8)val; ++ break; ++ default: ++ break; ++ } ++ tkn_count++; ++ mod_elem++; ++ } ++ ++ return 0; ++} ++ + static int audioreach_widget_dp_module_load(struct audioreach_module *mod, + const struct snd_soc_tplg_vendor_array *mod_array) + { +@@ -806,6 +903,12 @@ static int audioreach_widget_load_buffer(struct snd_soc_component *component, + case MODULE_ID_I2S_SOURCE: + audioreach_widget_i2s_module_load(mod, mod_array); + break; ++ case MODULE_ID_AUDIO_IF_SINK: ++ case MODULE_ID_AUDIO_IF_SOURCE: ++ ret = audioreach_widget_audio_if_module_load(mod, mod_array); ++ if (ret) ++ return ret; ++ break; + case MODULE_ID_DISPLAY_PORT_SINK: + audioreach_widget_dp_module_load(mod, mod_array); + break; +@@ -1313,7 +1416,6 @@ int audioreach_tplg_init(struct snd_soc_component *component) + { + struct snd_soc_card *card = component->card; + struct device *dev = component->dev; +- const struct firmware *fw; + int ret; + + /* Inline with Qualcomm UCM configs and linux-firmware path */ +@@ -1323,6 +1425,7 @@ int audioreach_tplg_init(struct snd_soc_component *component) + if (!tplg_fw_name) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, tplg_fw_name, dev); + if (ret < 0) { + dev_err(dev, "tplg firmware loading %s failed %d\n", tplg_fw_name, ret); +@@ -1335,8 +1438,6 @@ int audioreach_tplg_init(struct snd_soc_component *component) + dev_err(dev, "tplg component load failed: %d\n", ret); + } + +- release_firmware(fw); +- + return ret; + } + EXPORT_SYMBOL_GPL(audioreach_tplg_init); +diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c +--- a/sound/soc/qcom/sc8280xp.c ++++ b/sound/soc/qcom/sc8280xp.c +@@ -12,17 +12,157 @@ + #include + #include + #include "qdsp6/q6afe.h" ++#include "qdsp6/q6apm.h" ++#include "qdsp6/q6prm.h" ++#include "qdsp6/q6dsp-common.h" + #include "common.h" + #include "sdw.h" + ++#define I2S_MCLKFS 256 ++ ++#define I2S_MCLK_RATE(rate) \ ++ ((rate) * (I2S_MCLKFS)) ++#define I2S_BIT_RATE(rate, channels, format) \ ++ ((rate) * (channels) * (format)) ++ ++static struct snd_soc_dapm_widget sc8280xp_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone Jack", NULL), ++ SND_SOC_DAPM_MIC("Mic Jack", NULL), ++ SND_SOC_DAPM_SPK("DP0 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP1 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP2 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP3 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP4 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP5 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP6 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP7 Jack", NULL), ++}; ++ ++static const struct snd_kcontrol_new max98090_controls[] = { ++ SOC_DAPM_PIN_SWITCH("Headset Mic12"), ++ SOC_DAPM_PIN_SWITCH("Headphone"), ++ SOC_DAPM_PIN_SWITCH("Headset Mic56"), ++ SOC_DAPM_PIN_SWITCH("Speaker"), ++ SOC_DAPM_PIN_SWITCH("Receiver"), ++ SOC_DAPM_PIN_SWITCH("Int Mic"), ++}; ++ ++static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone Jack", NULL), ++ SND_SOC_DAPM_MIC("Mic Jack", NULL), ++ SND_SOC_DAPM_HP("Headphone", NULL), ++ SND_SOC_DAPM_MIC("Headset Mic12", NULL), ++ SND_SOC_DAPM_MIC("Headset Mic56", NULL), ++ SND_SOC_DAPM_MIC("Int Mic", NULL), ++ SND_SOC_DAPM_SPK("Receiver", NULL), ++ SND_SOC_DAPM_SPK("Speaker", NULL), ++}; ++ ++struct qcom_snd_soc_common { ++ const char *driver_name; ++ const struct snd_soc_dapm_widget *dapm_widgets; ++ int num_dapm_widgets; ++ const struct snd_soc_dapm_route *dapm_routes; ++ int num_dapm_routes; ++ const struct snd_kcontrol_new *controls; ++ int num_controls; ++ unsigned int codec_dai_fmt; ++ bool codec_sysclk_set; ++ bool mi2s_mclk_enable; ++ bool mi2s_bclk_enable; ++ bool wcd_jack; ++ int (*snd_prepare)(struct snd_pcm_substream *substream); ++}; ++ + struct sc8280xp_snd_data { + bool stream_prepared[AFE_PORT_MAX]; + struct snd_soc_card *card; + struct snd_soc_jack jack; + struct snd_soc_jack dp_jack[8]; ++ const struct qcom_snd_soc_common *priv; + bool jack_setup; + }; + ++static inline int sc8280xp_get_mclk_freq(struct snd_pcm_hw_params *params) ++{ ++ int rate = params_rate(params); ++ ++ switch (rate) { ++ case 11025: ++ case 44100: ++ case 88200: ++ return I2S_MCLK_RATE(44100); ++ default: ++ break; ++ } ++ ++ return I2S_MCLK_RATE(rate); ++} ++ ++static inline int sc8280xp_get_bclk_freq(struct snd_pcm_hw_params *params) ++{ ++ return I2S_BIT_RATE(params_rate(params), ++ params_channels(params), ++ snd_pcm_format_width(params_format(params))); ++} ++ ++static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_dai *codec_dai; ++ struct qcom_snd_tdm_slot_cfg cpu_cfg; ++ struct qcom_snd_tdm_slot_cfg codec_cfg; ++ unsigned int bclk_freq; ++ int ret; ++ int i; ++ ++ ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); ++ if (ret) ++ return ret == -EINVAL ? 0 : ret; ++ ++ if (!cpu_cfg.slots) ++ return 0; ++ ++ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); ++ if (ret) ++ return ret; ++ ++ ret = qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); ++ if (ret) ++ return ret; ++ ++ bclk_freq = snd_soc_tdm_params_to_bclk(params, cpu_cfg.slot_width, cpu_cfg.slots, 1); ++ if (!bclk_freq) ++ return -EINVAL; ++ ++ if (data->priv->mi2s_bclk_enable) { ++ ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, ++ SND_SOC_CLOCK_IN); ++ if (ret) { ++ dev_err(rtd->dev, "%s: failed to set cpu sysclk: %d\n", ++ __func__, ret); ++ return ret; ++ } ++ } ++ ++ if (data->priv->codec_sysclk_set) { ++ for_each_rtd_codec_dais(rtd, i, codec_dai) { ++ ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq, ++ SND_SOC_CLOCK_IN); ++ if (ret) { ++ dev_err(rtd->dev, "%s: failed to set codec sysclk on %s: %d\n", ++ __func__, codec_dai->name, ret); ++ return ret; ++ } ++ } ++ } ++ ++ return 0; ++} ++ + static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) + { + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); +@@ -32,10 +172,6 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) + int dp_pcm_id = 0; + + switch (cpu_dai->id) { +- case PRIMARY_MI2S_RX...QUATERNARY_MI2S_TX: +- case QUINARY_MI2S_RX...QUINARY_MI2S_TX: +- snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); +- break; + case WSA_CODEC_DMA_RX_0: + case WSA_CODEC_DMA_RX_1: + /* +@@ -64,7 +200,10 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) + if (dp_jack) + return qcom_snd_dp_jack_setup(rtd, dp_jack, dp_pcm_id); + +- return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); ++ if (data->priv->wcd_jack) ++ return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); ++ ++ return 0; + } + + static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, +@@ -96,12 +235,118 @@ static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + return 0; + } + ++static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); ++ int mclk_freq = sc8280xp_get_mclk_freq(params); ++ int bclk_freq = sc8280xp_get_bclk_freq(params); ++ int ret; ++ ++ switch (cpu_dai->id) { ++ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: ++ case SENARY_MI2S_RX ... SENARY_MI2S_TX: ++ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ ++ if (data->priv->codec_dai_fmt) { ++ ret = snd_soc_dai_set_fmt(codec_dai, ++ data->priv->codec_dai_fmt); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ } ++ ++ if (data->priv->mi2s_mclk_enable) { ++ ret = snd_soc_dai_set_sysclk(cpu_dai, ++ LPAIF_MI2S_MCLK, mclk_freq, ++ SND_SOC_CLOCK_OUT); ++ if (ret) ++ return ret; ++ } ++ ++ if (data->priv->mi2s_bclk_enable) { ++ ret = snd_soc_dai_set_sysclk(cpu_dai, ++ LPAIF_MI2S_BCLK, bclk_freq, ++ SND_SOC_CLOCK_OUT); ++ if (ret) ++ return ret; ++ } ++ ++ if (data->priv->codec_sysclk_set) { ++ ret = snd_soc_dai_set_sysclk(codec_dai, ++ 0, mclk_freq, ++ SND_SOC_CLOCK_IN); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ } ++ break; ++ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: ++ return sc8280xp_tdm_hw_params(substream, params); ++ default: ++ break; ++ } ++ ++ return 0; ++} ++ ++/* ++ * WSA and WSA2 are handled as a single interface with the ++ * following channels mask: ++ * __________________________________________________ ++ * | Bits | 3 | 2 | 1 | 0 | ++ * --------------------------------------------------- ++ * | Line | WSA2 Ch2 | WSA2 Ch1 | WSA Ch2 | WSA Ch1 | ++ * --------------------------------------------------- ++ * ++ * The Ayaneo Pocket S2 speakers are connected only to ++ * the WSA2 interface and the WSA interface is not enabled. ++ * ++ * Set the channel mapping on the WSA2 channels only. ++ */ ++static const unsigned int ayaneo_ps2_channels_mapping[] = { ++ 0, /* WSA Ch1 */ ++ 0, /* WSA Ch2 */ ++ PCM_CHANNEL_FL, /* WSA2 Ch1 */ ++ PCM_CHANNEL_FR /* WSA2 Ch2 */ ++}; ++ ++static int ayaneo_ps2_snd_prepare(struct snd_pcm_substream *substream) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ unsigned int channels = substream->runtime->channels; ++ ++ if (cpu_dai->id != WSA_CODEC_DMA_RX_0) ++ return 0; ++ ++ if (channels != 2) ++ return -EINVAL; ++ ++ return snd_soc_dai_set_channel_map(cpu_dai, 0, NULL, ++ ARRAY_SIZE(ayaneo_ps2_channels_mapping), ++ ayaneo_ps2_channels_mapping); ++} ++ + static int sc8280xp_snd_prepare(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + ++ if (data->priv->snd_prepare) { ++ int ret; ++ ++ ret = data->priv->snd_prepare(substream); ++ if (ret) ++ return ret; ++ } ++ + return qcom_snd_sdw_prepare(substream, &data->stream_prepared[cpu_dai->id]); + } + +@@ -117,6 +362,7 @@ static int sc8280xp_snd_hw_free(struct snd_pcm_substream *substream) + static const struct snd_soc_ops sc8280xp_be_ops = { + .startup = qcom_snd_sdw_startup, + .shutdown = qcom_snd_sdw_shutdown, ++ .hw_params = sc8280xp_snd_hw_params, + .hw_free = sc8280xp_snd_hw_free, + .prepare = sc8280xp_snd_prepare, + }; +@@ -145,38 +391,182 @@ static int sc8280xp_platform_probe(struct platform_device *pdev) + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; +- card->owner = THIS_MODULE; ++ + /* Allocate the private data */ + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + ++ data->priv = of_device_get_match_data(dev); ++ if (!data->priv) ++ return -ENODEV; ++ ++ card->owner = THIS_MODULE; + card->dev = dev; + dev_set_drvdata(dev, card); + snd_soc_card_set_drvdata(card, data); ++ card->dapm_widgets = data->priv->dapm_widgets; ++ card->num_dapm_widgets = data->priv->num_dapm_widgets; ++ card->dapm_routes = data->priv->dapm_routes; ++ card->num_dapm_routes = data->priv->num_dapm_routes; ++ card->controls = data->priv->controls; ++ card->num_controls = data->priv->num_controls; ++ + ret = qcom_snd_parse_of(card); + if (ret) + return ret; + +- card->driver_name = of_device_get_match_data(dev); ++ card->driver_name = data->priv->driver_name; + sc8280xp_add_be_ops(card); + return devm_snd_soc_register_card(dev, card); + } + ++static struct qcom_snd_soc_common ayaneo_ps2_priv_data = { ++ .driver_name = "ayaneo-ps2", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .snd_prepare = ayaneo_ps2_snd_prepare, ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common eliza_priv_data = { ++ .driver_name = "eliza", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common hawi_priv_data = { ++ .driver_name = "hawi", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .codec_sysclk_set = true, ++ .mi2s_bclk_enable = true, ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common kaanapali_priv_data = { ++ .driver_name = "kaanapali", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common qcs9100_priv_data = { ++ .driver_name = "sa8775p", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++}; ++ ++static const struct qcom_snd_soc_common qcs615_priv_data = { ++ .driver_name = "qcs615", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .codec_sysclk_set = true, ++}; ++ ++static const struct qcom_snd_soc_common qcm6490_priv_data = { ++ .driver_name = "qcm6490", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common qcs6490_priv_data = { ++ .driver_name = "qcs6490", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common qcs8275_priv_data = { ++ .driver_name = "qcs8300", ++ .dapm_widgets = max98090_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(max98090_dapm_widgets), ++ .controls = max98090_controls, ++ .num_controls = ARRAY_SIZE(max98090_controls), ++ .codec_sysclk_set = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_BC_FC, ++}; ++ ++static const struct qcom_snd_soc_common sc8280xp_priv_data = { ++ .driver_name = "sc8280xp", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common sm8450_priv_data = { ++ .driver_name = "sm8450", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++ /* I2S Connected to HDMI */ ++ .mi2s_mclk_enable = true, ++ .mi2s_bclk_enable = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | ++ SND_SOC_DAIFMT_NB_NF | ++ SND_SOC_DAIFMT_I2S, ++}; ++ ++static const struct qcom_snd_soc_common sm8475_priv_data = { ++ .driver_name = "sm8475", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common sm8550_priv_data = { ++ .driver_name = "sm8550", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++ /* I2S Connected to HDMI */ ++ .mi2s_mclk_enable = true, ++ .mi2s_bclk_enable = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | ++ SND_SOC_DAIFMT_NB_NF | ++ SND_SOC_DAIFMT_I2S, ++}; ++ ++static const struct qcom_snd_soc_common sm8650_priv_data = { ++ .driver_name = "sm8650", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++ /* I2S Connected to HDMI */ ++ .mi2s_mclk_enable = true, ++ .mi2s_bclk_enable = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | ++ SND_SOC_DAIFMT_NB_NF | ++ SND_SOC_DAIFMT_I2S, ++}; ++ ++static const struct qcom_snd_soc_common sm8750_priv_data = { ++ .driver_name = "sm8750", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ + static const struct of_device_id snd_sc8280xp_dt_match[] = { +- {.compatible = "qcom,eliza-sndcard", "eliza"}, +- {.compatible = "qcom,kaanapali-sndcard", "kaanapali"}, +- {.compatible = "qcom,qcm6490-idp-sndcard", "qcm6490"}, +- {.compatible = "qcom,qcs615-sndcard", "qcs615"}, +- {.compatible = "qcom,qcs6490-rb3gen2-sndcard", "qcs6490"}, +- {.compatible = "qcom,qcs8275-sndcard", "qcs8300"}, +- {.compatible = "qcom,qcs9075-sndcard", "sa8775p"}, +- {.compatible = "qcom,qcs9100-sndcard", "sa8775p"}, +- {.compatible = "qcom,sc8280xp-sndcard", "sc8280xp"}, +- {.compatible = "qcom,sm8450-sndcard", "sm8450"}, +- {.compatible = "qcom,sm8550-sndcard", "sm8550"}, +- {.compatible = "qcom,sm8650-sndcard", "sm8650"}, +- {.compatible = "qcom,sm8750-sndcard", "sm8750"}, ++ { .compatible = "ayaneo,pocket-s2-sndcard", .data = &ayaneo_ps2_priv_data }, ++ { .compatible = "qcom,eliza-sndcard", .data = &eliza_priv_data }, ++ { .compatible = "qcom,hawi-sndcard", .data = &hawi_priv_data }, ++ { .compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data }, ++ { .compatible = "qcom,maili-sndcard", .data = &hawi_priv_data }, ++ { .compatible = "qcom,qcm6490-idp-sndcard", .data = &qcm6490_priv_data }, ++ { .compatible = "qcom,qcs615-sndcard", .data = &qcs615_priv_data }, ++ { .compatible = "qcom,qcs6490-rb3gen2-sndcard", .data = &qcs6490_priv_data }, ++ { .compatible = "qcom,qcs8275-sndcard", .data = &qcs8275_priv_data }, ++ { .compatible = "qcom,qcs9075-sndcard", .data = &qcs9100_priv_data }, ++ { .compatible = "qcom,qcs9100-sndcard", .data = &qcs9100_priv_data }, ++ { .compatible = "qcom,sc8280xp-sndcard", .data = &sc8280xp_priv_data }, ++ { .compatible = "qcom,sm8450-sndcard", .data = &sm8450_priv_data }, ++ { .compatible = "qcom,sm8475-sndcard", .data = &sm8475_priv_data }, ++ { .compatible = "qcom,sm8550-sndcard", .data = &sm8550_priv_data }, ++ { .compatible = "qcom,sm8650-sndcard", .data = &sm8650_priv_data }, ++ { .compatible = "qcom,sm8750-sndcard", .data = &sm8750_priv_data }, + {} + }; + +diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c +--- a/sound/soc/qcom/sdm845.c ++++ b/sound/soc/qcom/sdm845.c +@@ -36,7 +36,6 @@ struct sdm845_snd_data { + bool jack_setup; + bool slim_port_setup; + bool stream_prepared[AFE_PORT_MAX]; +- struct snd_soc_card *card; + uint32_t pri_mi2s_clk_count; + uint32_t sec_mi2s_clk_count; + uint32_t quat_tdm_clk_count; +@@ -564,7 +563,6 @@ static int sdm845_snd_platform_probe(struct platform_device *pdev) + if (ret) + return ret; + +- data->card = card; + snd_soc_card_set_drvdata(card, data); + + sdm845_add_ops(card); +diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c +--- a/sound/soc/qcom/sm8250.c ++++ b/sound/soc/qcom/sm8250.c +@@ -112,6 +112,22 @@ static int sm8250_snd_startup(struct snd_pcm_substream *substream) + snd_soc_dai_set_fmt(cpu_dai, fmt); + snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); + break; ++ case LPI_MI2S_RX_0: ++ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; ++ snd_soc_dai_set_sysclk(cpu_dai, ++ Q6AFE_LPASS_CLK_ID_INT0_MI2S_IBIT, ++ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); ++ snd_soc_dai_set_fmt(cpu_dai, fmt); ++ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); ++ break; ++ case LPI_MI2S_TX_3: ++ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; ++ snd_soc_dai_set_sysclk(cpu_dai, ++ Q6AFE_LPASS_CLK_ID_INT3_MI2S_IBIT, ++ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_CAPTURE); ++ snd_soc_dai_set_fmt(cpu_dai, fmt); ++ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); ++ break; + default: + break; + } +@@ -194,6 +210,7 @@ static const struct of_device_id snd_sm8250_dt_match[] = { + { .compatible = "qcom,qrb2210-sndcard", .data = "qcm2290" }, + { .compatible = "qcom,qrb4210-rb2-sndcard", .data = "sm4250" }, + { .compatible = "qcom,qrb5165-rb5-sndcard", .data = "sm8250" }, ++ { .compatible = "qcom,sdm660-sndcard", .data = "sdm660" }, + { .compatible = "qcom,sm8250-sndcard", .data = "sm8250" }, + {} + }; +diff --git a/sound/soc/qcom/storm.c b/sound/soc/qcom/storm.c +--- a/sound/soc/qcom/storm.c ++++ b/sound/soc/qcom/storm.c +@@ -64,21 +64,21 @@ static struct snd_soc_dai_link storm_dai_link = { + SND_SOC_DAILINK_REG(hifi), + }; + +-static int storm_parse_of(struct snd_soc_card *card) ++static int storm_parse_of(struct device *dev) + { +- struct snd_soc_dai_link *dai_link = card->dai_link; +- struct device_node *np = card->dev->of_node; ++ struct snd_soc_dai_link *dai_link = &storm_dai_link; ++ struct device_node *np = dev->of_node; + + dai_link->cpus->of_node = of_parse_phandle(np, "cpu", 0); + if (!dai_link->cpus->of_node) { +- dev_err(card->dev, "error getting cpu phandle\n"); ++ dev_err(dev, "error getting cpu phandle\n"); + return -EINVAL; + } + dai_link->platforms->of_node = dai_link->cpus->of_node; + + dai_link->codecs->of_node = of_parse_phandle(np, "codec", 0); + if (!dai_link->codecs->of_node) { +- dev_err(card->dev, "error getting codec phandle\n"); ++ dev_err(dev, "error getting codec phandle\n"); + return -EINVAL; + } + +@@ -106,7 +106,7 @@ static int storm_platform_probe(struct platform_device *pdev) + card->dai_link = &storm_dai_link; + card->num_links = 1; + +- ret = storm_parse_of(card); ++ ret = storm_parse_of(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "error resolving dai links: %d\n", ret); + return ret; +diff --git a/sound/soc/renesas/fsi.c b/sound/soc/renesas/fsi.c +--- a/sound/soc/renesas/fsi.c ++++ b/sound/soc/renesas/fsi.c +@@ -1979,8 +1979,8 @@ static const struct of_device_id fsi_of_match[] = { + MODULE_DEVICE_TABLE(of, fsi_of_match); + + static const struct platform_device_id fsi_id_table[] = { +- { "sh_fsi", (kernel_ulong_t)&fsi1_core }, +- {}, ++ /* an array with no valid entry prevents matching on driver name */ ++ { } + }; + MODULE_DEVICE_TABLE(platform, fsi_id_table); + +@@ -1992,7 +1992,7 @@ static int fsi_probe(struct platform_device *pdev) + const struct fsi_core *core; + struct fsi_priv *fsi; + struct resource *res; +- unsigned int irq; ++ int irq; + int ret; + + memset(&info, 0, sizeof(info)); +@@ -2007,12 +2007,15 @@ static int fsi_probe(struct platform_device *pdev) + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- irq = platform_get_irq(pdev, 0); +- if (!res || (int)irq <= 0) { ++ if (!res) { + dev_err(&pdev->dev, "Not enough FSI platform resources.\n"); + return -ENODEV; + } + ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return irq; ++ + master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL); + if (!master) + return -ENOMEM; +diff --git a/sound/soc/renesas/rcar/adg.c b/sound/soc/renesas/rcar/adg.c +--- a/sound/soc/renesas/rcar/adg.c ++++ b/sound/soc/renesas/rcar/adg.c +@@ -565,7 +565,7 @@ static struct clk *rsnd_adg_create_null_clk(struct rsnd_priv *priv, + struct clk *clk; + + clk = clk_register_fixed_rate(dev, name, parent, 0, 0); +- if (IS_ERR_OR_NULL(clk)) { ++ if (IS_ERR(clk)) { + dev_err(dev, "create null clk error\n"); + return ERR_CAST(clk); + } +@@ -618,7 +618,7 @@ static int rsnd_adg_get_clkin(struct rsnd_priv *priv) + * No "adg" is not error + */ + clk = devm_clk_get(dev, "adg"); +- if (IS_ERR_OR_NULL(clk)) ++ if (IS_ERR(clk)) + clk = rsnd_adg_null_clk_get(priv); + adg->adg = clk; + +@@ -626,9 +626,9 @@ static int rsnd_adg_get_clkin(struct rsnd_priv *priv) + for (i = 0; i < clkin_size; i++) { + clk = devm_clk_get(dev, clkin_name[i]); + +- if (IS_ERR_OR_NULL(clk)) ++ if (IS_ERR(clk)) + clk = rsnd_adg_null_clk_get(priv); +- if (IS_ERR_OR_NULL(clk)) ++ if (IS_ERR(clk)) + goto err; + + adg->clkin[i] = clk; +diff --git a/sound/soc/renesas/siu_dai.c b/sound/soc/renesas/siu_dai.c +--- a/sound/soc/renesas/siu_dai.c ++++ b/sound/soc/renesas/siu_dai.c +@@ -715,7 +715,6 @@ static struct snd_soc_dai_driver siu_i2s_dai = { + + static int siu_probe(struct platform_device *pdev) + { +- const struct firmware *fw_entry; + struct resource *res, *region; + struct siu_info *info; + int ret; +@@ -726,6 +725,7 @@ static int siu_probe(struct platform_device *pdev) + siu_i2s_data = info; + info->dev = &pdev->dev; + ++ const struct firmware *fw_entry __free(firmware) = NULL; + ret = request_firmware(&fw_entry, "siu_spb.bin", &pdev->dev); + if (ret) + return ret; +@@ -736,8 +736,6 @@ static int siu_probe(struct platform_device *pdev) + */ + memcpy(&info->fw, fw_entry->data, fw_entry->size); + +- release_firmware(fw_entry); +- + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; +diff --git a/sound/soc/rockchip/rk3288_hdmi_analog.c b/sound/soc/rockchip/rk3288_hdmi_analog.c +--- a/sound/soc/rockchip/rk3288_hdmi_analog.c ++++ b/sound/soc/rockchip/rk3288_hdmi_analog.c +@@ -185,10 +185,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + gpiod_set_consumer_name(machine->gpio_hp_en, "hp_en"); + + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); +- if (ret) { +- dev_err(card->dev, "SoC parse card name failed %d\n", ret); ++ if (ret) + return ret; +- } + + rk_dailink.codecs[0].of_node = of_parse_phandle(np, + "rockchip,audio-codec", +@@ -207,10 +205,9 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + } + + ret = snd_soc_get_dai_name(&args, &rk_dailink.codecs[0].dai_name); +- if (ret) { +- dev_err(&pdev->dev, "Unable to get codec_dai_name\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "Unable to get codec_dai_name\n"); + + rk_dailink.cpus->of_node = of_parse_phandle(np, "rockchip,i2s-controller", + 0); +@@ -223,11 +220,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + rk_dailink.platforms->of_node = rk_dailink.cpus->of_node; + + ret = snd_soc_of_parse_audio_routing(card, "rockchip,routing"); +- if (ret) { +- dev_err(&pdev->dev, +- "Unable to parse 'rockchip,routing' property\n"); ++ if (ret) + return ret; +- } + + snd_soc_card_set_drvdata(card, machine); + +diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c +--- a/sound/soc/rockchip/rockchip_i2s.c ++++ b/sound/soc/rockchip/rockchip_i2s.c +@@ -767,16 +767,14 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + + /* try to prepare related clocks */ + i2s->hclk = devm_clk_get_enabled(&pdev->dev, "i2s_hclk"); +- if (IS_ERR(i2s->hclk)) { +- dev_err(&pdev->dev, "Can't retrieve i2s bus clock\n"); +- return PTR_ERR(i2s->hclk); +- } ++ if (IS_ERR(i2s->hclk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->hclk), ++ "Can't retrieve i2s bus clock\n"); + + i2s->mclk = devm_clk_get(&pdev->dev, "i2s_clk"); +- if (IS_ERR(i2s->mclk)) { +- dev_err(&pdev->dev, "Can't retrieve i2s master clock\n"); +- return PTR_ERR(i2s->mclk); +- } ++ if (IS_ERR(i2s->mclk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->mclk), ++ "Can't retrieve i2s master clock\n"); + + regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(regs)) +@@ -792,7 +790,11 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + + i2s->bclk_ratio = 64; + i2s->pinctrl = devm_pinctrl_get(&pdev->dev); +- if (!IS_ERR(i2s->pinctrl)) { ++ if (IS_ERR(i2s->pinctrl)) { ++ if (PTR_ERR(i2s->pinctrl) == -EPROBE_DEFER) ++ return -EPROBE_DEFER; ++ dev_dbg(&pdev->dev, "failed to find i2s pinctrl\n"); ++ } else { + i2s->bclk_on = pinctrl_lookup_state(i2s->pinctrl, "bclk_on"); + if (!IS_ERR_OR_NULL(i2s->bclk_on)) { + i2s->bclk_off = pinctrl_lookup_state(i2s->pinctrl, "bclk_off"); +@@ -801,8 +803,6 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + return -EINVAL; + } + } +- } else { +- dev_dbg(&pdev->dev, "failed to find i2s pinctrl\n"); + } + + i2s_pinctrl_select_bclk_off(i2s); +@@ -831,16 +831,12 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + &rockchip_i2s_component, + dai, 1); + +- if (ret) { +- dev_err(&pdev->dev, "Could not register DAI\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Could not register PCM\n"); ++ if (ret) + return ret; +- } + + return 0; + } +diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c +--- a/sound/soc/rockchip/rockchip_i2s_tdm.c ++++ b/sound/soc/rockchip/rockchip_i2s_tdm.c +@@ -1263,16 +1263,14 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) + i2s_tdm->tx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, + "tx-m"); + if (IS_ERR(i2s_tdm->tx_reset)) { +- ret = PTR_ERR(i2s_tdm->tx_reset); +- return dev_err_probe(i2s_tdm->dev, ret, ++ return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->tx_reset), + "Error in tx-m reset control\n"); + } + + i2s_tdm->rx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, + "rx-m"); + if (IS_ERR(i2s_tdm->rx_reset)) { +- ret = PTR_ERR(i2s_tdm->rx_reset); +- return dev_err_probe(i2s_tdm->dev, ret, ++ return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->rx_reset), + "Error in rx-m reset control\n"); + } + +@@ -1363,17 +1361,12 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) + ret = devm_snd_soc_register_component(&pdev->dev, + &rockchip_i2s_tdm_component, + i2s_tdm->dai, 1); +- +- if (ret) { +- dev_err(&pdev->dev, "Could not register DAI\n"); ++ if (ret) + goto err_suspend; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Could not register PCM\n"); ++ if (ret) + goto err_suspend; +- } + + return 0; + +diff --git a/sound/soc/rockchip/rockchip_max98090.c b/sound/soc/rockchip/rockchip_max98090.c +--- a/sound/soc/rockchip/rockchip_max98090.c ++++ b/sound/soc/rockchip/rockchip_max98090.c +@@ -428,11 +428,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + + /* Parse card name. */ + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); +- if (ret) { +- dev_err(&pdev->dev, +- "Soc parse card name failed %d\n", ret); ++ if (ret) + return ret; +- } + + /* register the soc card */ + ret = devm_snd_soc_register_card(&pdev->dev, card); +diff --git a/sound/soc/rockchip/rockchip_pdm.c b/sound/soc/rockchip/rockchip_pdm.c +--- a/sound/soc/rockchip/rockchip_pdm.c ++++ b/sound/soc/rockchip/rockchip_pdm.c +@@ -630,10 +630,8 @@ static int rockchip_pdm_probe(struct platform_device *pdev) + &rockchip_pdm_component, + &rockchip_pdm_dai, 1); + +- if (ret) { +- dev_err(&pdev->dev, "could not register dai: %d\n", ret); ++ if (ret) + goto err_suspend; +- } + + rockchip_pdm_rxctrl(pdm, 0); + +@@ -642,10 +640,8 @@ static int rockchip_pdm_probe(struct platform_device *pdev) + goto err_suspend; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "could not register pcm: %d\n", ret); ++ if (ret) + goto err_suspend; +- } + + return 0; + +diff --git a/sound/soc/rockchip/rockchip_rt5645.c b/sound/soc/rockchip/rockchip_rt5645.c +--- a/sound/soc/rockchip/rockchip_rt5645.c ++++ b/sound/soc/rockchip/rockchip_rt5645.c +@@ -191,11 +191,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + rk_dailink.platforms->of_node = rk_dailink.cpus->of_node; + + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); +- if (ret) { +- dev_err(&pdev->dev, +- "Soc parse card name failed %d\n", ret); ++ if (ret) + goto put_cpu_of_node; +- } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { +diff --git a/sound/soc/rockchip/rockchip_sai.c b/sound/soc/rockchip/rockchip_sai.c +--- a/sound/soc/rockchip/rockchip_sai.c ++++ b/sound/soc/rockchip/rockchip_sai.c +@@ -1428,12 +1428,13 @@ static int rockchip_sai_probe(struct platform_device *pdev) + "Failed to initialize regmap\n"); + + irq = platform_get_irq_optional(pdev, 0); ++ if (irq == -EPROBE_DEFER) ++ return irq; + if (irq > 0) { + ret = devm_request_irq(&pdev->dev, irq, rockchip_sai_isr, + IRQF_SHARED, node->name, sai); + if (ret) +- return dev_err_probe(&pdev->dev, ret, +- "Failed to request irq %d\n", irq); ++ return ret; + } else { + dev_dbg(&pdev->dev, "Asked for an IRQ but got %d\n", irq); + } +@@ -1456,7 +1457,7 @@ static int rockchip_sai_probe(struct platform_device *pdev) + + ret = rockchip_sai_parse_paths(sai, node); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "Failed to parse paths\n"); ++ return ret; + + /* + * From here on, all register accesses need to be wrapped in +@@ -1471,18 +1472,14 @@ static int rockchip_sai_probe(struct platform_device *pdev) + return dev_err_probe(&pdev->dev, ret, "Failed to resume device\n"); + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register PCM: %d\n", ret); ++ if (ret) + goto err_runtime_suspend; +- } + + ret = devm_snd_soc_register_component(&pdev->dev, + &rockchip_sai_component, + dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register component: %d\n", ret); ++ if (ret) + goto err_runtime_suspend; +- } + + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_put(&pdev->dev); +diff --git a/sound/soc/rockchip/rockchip_spdif.c b/sound/soc/rockchip/rockchip_spdif.c +--- a/sound/soc/rockchip/rockchip_spdif.c ++++ b/sound/soc/rockchip/rockchip_spdif.c +@@ -396,13 +396,13 @@ static int rk_spdif_probe(struct platform_device *pdev) + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "Could not register PCM\n"); ++ return ret; + + ret = devm_snd_soc_register_component(&pdev->dev, + &rk_spdif_component, + &rk_spdif_dai, 1); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "Could not register DAI\n"); ++ return ret; + + return 0; + } +diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c +--- a/sound/soc/samsung/i2s.c ++++ b/sound/soc/samsung/i2s.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -512,7 +513,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + u32 mod, mask, val = 0; + int ret = 0; + +- pm_runtime_get_sync(dai->dev); ++ guard(pm_runtime_active)(dai->dev); + + scoped_guard(spinlock_irqsave, &priv->lock) + mod = readl(priv->addr + I2SMOD); +@@ -537,8 +538,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + && (mod & cdcon_mask))))) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); +- ret = -EAGAIN; +- goto err; ++ return -EAGAIN; + } + + if (dir == SND_SOC_CLOCK_IN) +@@ -566,7 +566,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + } else { + priv->rclk_srcrate = + clk_get_rate(priv->op_clk); +- goto done; ++ return 0; + } + } + +@@ -580,14 +580,14 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + if (WARN_ON(IS_ERR(priv->op_clk))) { + ret = PTR_ERR(priv->op_clk); + priv->op_clk = NULL; +- goto err; ++ return ret; + } + + ret = clk_prepare_enable(priv->op_clk); + if (ret) { + clk_put(priv->op_clk); + priv->op_clk = NULL; +- goto err; ++ return ret; + } + priv->rclk_srcrate = clk_get_rate(priv->op_clk); + +@@ -595,11 +595,10 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + || (clk_id && !(mod & rsrc_mask))) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); +- ret = -EAGAIN; +- goto err; ++ return -EAGAIN; + } else { + /* Call can't be on the active DAI */ +- goto done; ++ return 0; + } + + if (clk_id == 1) +@@ -607,8 +606,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + break; + default: + dev_err(&i2s->pdev->dev, "We don't serve that!\n"); +- ret = -EINVAL; +- goto err; ++ return -EINVAL; + } + + scoped_guard(spinlock_irqsave, &priv->lock) { +@@ -616,13 +614,8 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + mod = (mod & ~mask) | val; + writel(mod, priv->addr + I2SMOD); + } +-done: +- pm_runtime_put(dai->dev); + + return 0; +-err: +- pm_runtime_put(dai->dev); +- return ret; + } + + static int i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +@@ -1123,15 +1116,31 @@ static const struct snd_soc_dapm_widget samsung_i2s_widgets[] = { + + static const struct snd_soc_dapm_route samsung_i2s_dapm_routes[] = { + { "Playback Mixer", NULL, "Primary Playback" }, +- { "Playback Mixer", NULL, "Secondary Playback" }, +- + { "Mixer DAI TX", NULL, "Playback Mixer" }, + { "Primary Capture", NULL, "Mixer DAI RX" }, + }; + ++static const struct snd_soc_dapm_route samsung_i2s_dapm_routes_sec_play[] = { ++ { "Playback Mixer", NULL, "Secondary Playback" }, ++}; ++ ++static int samsung_i2s_component_probe(struct snd_soc_component *component) ++{ ++ struct samsung_i2s_priv *priv = snd_soc_component_get_drvdata(component); ++ ++ if (priv->quirks & QUIRK_SEC_DAI) ++ snd_soc_dapm_add_routes(snd_soc_component_to_dapm(component), ++ samsung_i2s_dapm_routes_sec_play, ++ ARRAY_SIZE(samsung_i2s_dapm_routes_sec_play)); ++ ++ return 0; ++} ++ + static const struct snd_soc_component_driver samsung_i2s_component = { + .name = "samsung-i2s", + ++ .probe = samsung_i2s_component_probe, ++ + .dapm_widgets = samsung_i2s_widgets, + .num_dapm_widgets = ARRAY_SIZE(samsung_i2s_widgets), + +@@ -1448,10 +1457,10 @@ static int samsung_i2s_probe(struct platform_device *pdev) + regs_base = res->start; + + priv->clk = devm_clk_get(&pdev->dev, "iis"); +- if (IS_ERR(priv->clk)) { +- dev_err(&pdev->dev, "Failed to get iis clock\n"); +- return PTR_ERR(priv->clk); +- } ++ if (IS_ERR(priv->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk), ++ "Failed to get iis clock\n"); ++ + + ret = clk_prepare_enable(priv->clk); + if (ret != 0) { +@@ -1657,8 +1666,7 @@ static const struct samsung_i2s_dai_data i2sv6_dai_type __maybe_unused = { + }; + + static const struct samsung_i2s_dai_data i2sv7_dai_type __maybe_unused = { +- .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | +- QUIRK_SUPPORTS_TDM, ++ .quirks = QUIRK_PRI_6CHAN | QUIRK_NEED_RSTCLR | QUIRK_SUPPORTS_TDM, + .pcm_rates = SNDRV_PCM_RATE_8000_192000, + .i2s_variant_regs = &i2sv7_regs, + }; +@@ -1682,7 +1690,7 @@ static const struct platform_device_id samsung_i2s_driver_ids[] = { + .name = "samsung-i2s", + .driver_data = (kernel_ulong_t)&i2sv3_dai_type, + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, samsung_i2s_driver_ids); + +diff --git a/sound/soc/samsung/smdk_spdif.c b/sound/soc/samsung/smdk_spdif.c +--- a/sound/soc/samsung/smdk_spdif.c ++++ b/sound/soc/samsung/smdk_spdif.c +@@ -130,12 +130,8 @@ static int smdk_hw_params(struct snd_pcm_substream *substream, + return ret; + + /* Set S/PDIF uses internal source clock */ +- ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK, +- rclk_rate, SND_SOC_CLOCK_IN); +- if (ret < 0) +- return ret; +- +- return ret; ++ return snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK, ++ rclk_rate, SND_SOC_CLOCK_IN); + } + + static const struct snd_soc_ops smdk_spdif_ops = { +diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c +--- a/sound/soc/samsung/spdif.c ++++ b/sound/soc/samsung/spdif.c +@@ -380,8 +380,8 @@ static int spdif_probe(struct platform_device *pdev) + + spdif->pclk = devm_clk_get(&pdev->dev, "spdif"); + if (IS_ERR(spdif->pclk)) { +- dev_err(&pdev->dev, "failed to get peri-clock\n"); +- ret = -ENOENT; ++ ret = dev_err_probe(&pdev->dev, PTR_ERR(spdif->pclk), ++ "failed to get peri-clock\n"); + goto err0; + } + ret = clk_prepare_enable(spdif->pclk); +@@ -390,8 +390,8 @@ static int spdif_probe(struct platform_device *pdev) + + spdif->sclk = devm_clk_get(&pdev->dev, "sclk_spdif"); + if (IS_ERR(spdif->sclk)) { +- dev_err(&pdev->dev, "failed to get internal source clock\n"); +- ret = -ENOENT; ++ ret = dev_err_probe(&pdev->dev, PTR_ERR(spdif->sclk), ++ "failed to get internal source clock\n"); + goto err1; + } + ret = clk_prepare_enable(spdif->sclk); +diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c +--- a/sound/soc/sdca/sdca_asoc.c ++++ b/sound/soc/sdca/sdca_asoc.c +@@ -364,7 +364,6 @@ static int entity_parse_ot(struct device *dev, + + /** + * sdca_asoc_pde_poll_actual_ps - Verify PDE power state reached target state +- * @dev: Pointer to the device for error logging. + * @regmap: Register map for reading ACTUAL_PS register. + * @function_id: SDCA function identifier. + * @entity_id: SDCA entity identifier for the power domain. +@@ -389,11 +388,11 @@ static int entity_parse_ot(struct device *dev, + * polling times out before reaching the target state, or a negative error code if + * a register read fails. + */ +-int sdca_asoc_pde_poll_actual_ps(struct device *dev, struct regmap *regmap, ++int sdca_asoc_pde_poll_actual_ps(struct regmap *regmap, + int function_id, int entity_id, +- int from_ps, int to_ps, +- const struct sdca_pde_delay *pde_delays, +- int num_delays) ++ int from_ps, int to_ps, ++ const struct sdca_pde_delay *pde_delays, ++ int num_delays) + { + static const int polls = 100; + static const int default_poll_us = 1000; +@@ -451,14 +450,14 @@ static int entity_pde_event(struct snd_soc_dapm_widget *widget, + return 0; + } + +- ret = sdca_asoc_pde_poll_actual_ps(component->dev, component->regmap, ++ ret = sdca_asoc_pde_poll_actual_ps(component->regmap, + SDW_SDCA_CTL_FUNC(widget->reg), + SDW_SDCA_CTL_ENT(widget->reg), + from, to, + entity->pde.max_delay, + entity->pde.num_max_delay); + if (ret) +- dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n", ++ dev_err(component->dev, "%s: pde transition %x -> %x failed: %d\n", + entity->label, from, to, ret); + + return ret; +@@ -1231,7 +1230,7 @@ static u64 width_find_mask(unsigned int bits) + } + } + +-static int populate_rate_format(struct device *dev, ++int sdca_asoc_populate_rate_format(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct snd_soc_pcm_stream *stream) +@@ -1292,6 +1291,7 @@ static int populate_rate_format(struct device *dev, + + return 0; + } ++EXPORT_SYMBOL_NS(sdca_asoc_populate_rate_format, "SND_SOC_SDCA"); + + /** + * sdca_asoc_populate_dais - fill in an array of DAI drivers for a Function +@@ -1344,7 +1344,7 @@ int sdca_asoc_populate_dais(struct device *dev, struct sdca_function_data *funct + stream->channels_min = 1; + stream->channels_max = SDCA_MAX_CHANNEL_COUNT; + +- ret = populate_rate_format(dev, function, entity, stream); ++ ret = sdca_asoc_populate_rate_format(dev, function, entity, stream); + if (ret) + return ret; + +diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c +--- a/sound/soc/sdca/sdca_class_function.c ++++ b/sound/soc/sdca/sdca_class_function.c +@@ -329,7 +329,7 @@ static int class_function_probe(struct auxiliary_device *auxdev, + drv->core = core; + drv->function = &sdev->function; + +- ret = sdca_parse_function(dev, core->sdw, drv->function); ++ ret = sdca_parse_function(dev, drv->function); + if (ret) + return ret; + +diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c +--- a/sound/soc/sdca/sdca_fdl.c ++++ b/sound/soc/sdca/sdca_fdl.c +@@ -195,7 +195,6 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + { + struct device *dev = interrupt->dev; + struct sdca_fdl_data *fdl_data = &interrupt->function->fdl_data; +- const struct firmware *firmware = NULL; + struct acpi_sw_file *swf = NULL, *tmp; + struct sdca_fdl_file *fdl_file; + char *disk_filename; +@@ -230,6 +229,7 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + + dev_dbg(dev, "FDL disk filename: %s\n", disk_filename); + ++ const struct firmware *firmware __free(firmware) = NULL; + ret = firmware_request_nowarn(&firmware, disk_filename, dev); + kfree(disk_filename); + if (ret) { +@@ -258,8 +258,7 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + + if (!swf) { + dev_err(dev, "failed to locate SWF\n"); +- ret = -ENOENT; +- goto error; ++ return -ENOENT; + } + + dev_info(dev, "loading SWF: %x-%x-%x\n", +@@ -271,9 +270,6 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + SDCA_CTL_XU_FDL_MESSAGEOFFSET, fdl_file->fdl_offset, + SDCA_CTL_XU_FDL_MESSAGELENGTH, swf->data, + swf->file_length - offsetof(struct acpi_sw_file, data)); +- +-error: +- release_firmware(firmware); + return ret; + } + +diff --git a/sound/soc/sdca/sdca_function_device.c b/sound/soc/sdca/sdca_function_device.c +--- a/sound/soc/sdca/sdca_function_device.c ++++ b/sound/soc/sdca/sdca_function_device.c +@@ -32,7 +32,8 @@ static void sdca_dev_release(struct device *dev) + + /* alloc, init and add link devices */ + static struct sdca_dev *sdca_dev_register(struct device *parent, +- struct sdca_function_desc *function_desc) ++ struct sdca_function_desc *function_desc, ++ struct acpi_table_swft *swft) + { + struct sdca_dev *sdev; + struct auxiliary_device *auxdev; +@@ -50,6 +51,7 @@ static struct sdca_dev *sdca_dev_register(struct device *parent, + auxdev->dev.release = sdca_dev_release; + + sdev->function.desc = function_desc; ++ sdev->function.fdl_data.swft = swft; + + rc = ida_alloc(&sdca_function_ida, GFP_KERNEL); + if (rc < 0) { +@@ -99,7 +101,8 @@ int sdca_dev_register_functions(struct sdw_slave *slave) + struct sdca_dev *func_dev; + + func_dev = sdca_dev_register(&slave->dev, +- &sdca_data->function[i]); ++ &sdca_data->function[i], ++ sdca_data->swft); + if (IS_ERR(func_dev)) { + ret = PTR_ERR(func_dev); + /* +diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c +--- a/sound/soc/sdca/sdca_functions.c ++++ b/sound/soc/sdca/sdca_functions.c +@@ -7,6 +7,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -18,7 +19,6 @@ + #include + #include + #include +-#include + + /* + * Should be long enough to encompass all the MIPI DisCo properties. +@@ -1365,90 +1365,76 @@ static int find_sdca_entity_ge(struct device *dev, + return -EINVAL; + } + +-static int +-find_sdca_entity_hide(struct device *dev, struct sdw_slave *sdw, +- struct fwnode_handle *function_node, +- struct fwnode_handle *entity_node, struct sdca_entity *entity) ++static int find_sdca_entity_hide(struct device *dev, ++ struct fwnode_handle *entity_node, ++ struct sdca_entity *entity) + { + struct sdca_entity_hide *hide = &entity->hide; +- unsigned int delay, *af_list = hide->af_number_list; +- int nval, ret; +- unsigned char *report_desc = NULL; ++ int num_reports, ret; ++ unsigned int delay; + + ret = fwnode_property_read_u32(entity_node, +- "mipi-sdca-RxUMP-ownership-transition-max-delay", &delay); ++ "mipi-sdca-RxUMP-ownership-transition-max-delay", ++ &delay); + if (!ret) + hide->max_delay = delay; + +- nval = fwnode_property_count_u32(entity_node, "mipi-sdca-HIDTx-supported-report-ids"); +- if (nval > 0) { +- hide->num_hidtx_ids = nval; ++ num_reports = fwnode_property_count_u32(entity_node, ++ "mipi-sdca-HIDTx-supported-report-ids"); ++ if (num_reports < 0 && num_reports != -EINVAL) { ++ dev_err(dev, "%pfwP: failed to read hid tx ids: %d\n", ++ entity_node, num_reports); ++ return num_reports; ++ } else if (num_reports > 0) { ++ hide->num_hidtx_ids = num_reports; + hide->hidtx_ids = devm_kcalloc(dev, hide->num_hidtx_ids, + sizeof(*hide->hidtx_ids), GFP_KERNEL); + if (!hide->hidtx_ids) + return -ENOMEM; + +- ret = fwnode_property_read_u32_array(entity_node, +- "mipi-sdca-HIDTx-supported-report-ids", +- hide->hidtx_ids, +- hide->num_hidtx_ids); +- if (ret < 0) +- return ret; ++ fwnode_property_read_u32_array(entity_node, ++ "mipi-sdca-HIDTx-supported-report-ids", ++ hide->hidtx_ids, hide->num_hidtx_ids); + } + +- nval = fwnode_property_count_u32(entity_node, "mipi-sdca-HIDRx-supported-report-ids"); +- if (nval > 0) { +- hide->num_hidrx_ids = nval; ++ num_reports = fwnode_property_count_u32(entity_node, ++ "mipi-sdca-HIDRx-supported-report-ids"); ++ if (num_reports < 0 && num_reports != -EINVAL) { ++ dev_err(dev, "%pfwP: failed to read hid rx ids: %d\n", ++ entity_node, num_reports); ++ return num_reports; ++ } else if (num_reports > 0) { ++ hide->num_hidrx_ids = num_reports; + hide->hidrx_ids = devm_kcalloc(dev, hide->num_hidrx_ids, + sizeof(*hide->hidrx_ids), GFP_KERNEL); + if (!hide->hidrx_ids) + return -ENOMEM; + +- ret = fwnode_property_read_u32_array(entity_node, +- "mipi-sdca-HIDRx-supported-report-ids", +- hide->hidrx_ids, +- hide->num_hidrx_ids); +- if (ret < 0) +- return ret; ++ fwnode_property_read_u32_array(entity_node, ++ "mipi-sdca-HIDRx-supported-report-ids", ++ hide->hidrx_ids, hide->num_hidrx_ids); + } + +- nval = fwnode_property_count_u32(entity_node, "mipi-sdca-hide-related-audio-function-list"); +- if (nval <= 0) { ++ /* ++ * FIXME: This should probably link to the actual sdca_function_data pointer, ++ * but updating to do so should probably wait until we have a user. ++ */ ++ num_reports = fwnode_property_count_u32(entity_node, ++ "mipi-sdca-hide-related-audio-function-list"); ++ if (num_reports <= 0) { + dev_err(dev, "%pfwP: audio function numbers list missing: %d\n", +- entity_node, nval); ++ entity_node, num_reports); + return -EINVAL; +- } else if (nval > SDCA_MAX_FUNCTION_COUNT) { +- dev_err(dev, "%pfwP: maximum number of audio function exceeded\n", entity_node); ++ } else if (num_reports > ARRAY_SIZE(hide->af_number_list)) { ++ dev_err(dev, "%pfwP: maximum number of audio function exceeded\n", ++ entity_node); + return -EINVAL; + } + +- hide->hide_reside_function_num = nval; ++ hide->hide_reside_function_num = num_reports; + fwnode_property_read_u32_array(entity_node, +- "mipi-sdca-hide-related-audio-function-list", af_list, nval); +- +- nval = fwnode_property_count_u8(function_node, "mipi-sdca-hid-descriptor"); +- if (nval) +- fwnode_property_read_u8_array(function_node, "mipi-sdca-hid-descriptor", +- (u8 *)&hide->hid_desc, nval); +- +- if (hide->hid_desc.bNumDescriptors) { +- nval = fwnode_property_count_u8(function_node, "mipi-sdca-report-descriptor"); +- if (nval) { +- report_desc = devm_kzalloc(dev, nval, GFP_KERNEL); +- if (!report_desc) +- return -ENOMEM; +- hide->hid_report_desc = report_desc; +- fwnode_property_read_u8_array(function_node, "mipi-sdca-report-descriptor", +- report_desc, nval); +- +- /* add HID device */ +- ret = sdca_add_hid_device(dev, sdw, entity); +- if (ret) { +- dev_err(dev, "%pfwP: failed to add HID device: %d\n", entity_node, ret); +- return ret; +- } +- } +- } ++ "mipi-sdca-hide-related-audio-function-list", ++ hide->af_number_list, num_reports); + + return 0; + } +@@ -1475,8 +1461,7 @@ static int find_sdca_entity_xu(struct device *dev, + return 0; + } + +-static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, +- struct sdca_function_data *function, ++static int find_sdca_entity(struct device *dev, struct sdca_function_data *function, + struct fwnode_handle *function_node, + struct fwnode_handle *entity_node, + struct sdca_entity *entity) +@@ -1528,8 +1513,7 @@ static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, + ret = find_sdca_entity_ge(dev, entity_node, entity); + break; + case SDCA_ENTITY_TYPE_HIDE: +- ret = find_sdca_entity_hide(dev, sdw, function_node, +- entity_node, entity); ++ ret = find_sdca_entity_hide(dev, entity_node, entity); + break; + default: + break; +@@ -1544,8 +1528,7 @@ static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, + return 0; + } + +-static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, +- struct fwnode_handle *function_node, ++static int find_sdca_entities(struct device *dev, struct fwnode_handle *function_node, + struct sdca_function_data *function) + { + struct sdca_entity *entities; +@@ -1596,7 +1579,7 @@ static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, + return -EINVAL; + } + +- ret = find_sdca_entity(dev, sdw, function, function_node, ++ ret = find_sdca_entity(dev, function, function_node, + entity_node, &entities[i]); + fwnode_handle_put(entity_node); + if (ret) +@@ -1619,7 +1602,7 @@ static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, + return 0; + } + +-static struct sdca_entity *find_sdca_entity_by_label(struct sdca_function_data *function, ++struct sdca_entity *sdca_find_entity_by_label(struct sdca_function_data *function, + const char *entity_label) + { + struct sdca_entity *entity = NULL; +@@ -1648,6 +1631,7 @@ static struct sdca_entity *find_sdca_entity_by_label(struct sdca_function_data * + + return NULL; + } ++EXPORT_SYMBOL_NS(sdca_find_entity_by_label, "SND_SOC_SDCA"); + + static struct sdca_entity *find_sdca_entity_by_id(struct sdca_function_data *function, + const int id) +@@ -1688,7 +1672,7 @@ static int find_sdca_entity_connection_iot(struct device *dev, + return ret; + } + +- clock_entity = find_sdca_entity_by_label(function, clock_label); ++ clock_entity = sdca_find_entity_by_label(function, clock_label); + if (!clock_entity) { + dev_err(dev, "%s: failed to find clock with label %s\n", + entity->label, clock_label); +@@ -1873,7 +1857,7 @@ static int find_sdca_entity_connection(struct device *dev, + return ret; + } + +- connected_entity = find_sdca_entity_by_label(function, connected_label); ++ connected_entity = sdca_find_entity_by_label(function, connected_label); + if (!connected_entity) { + dev_err(dev, "%s: failed to find entity with label %s\n", + entity->label, connected_label); +@@ -2088,8 +2072,7 @@ static int find_sdca_clusters(struct device *dev, + return 0; + } + +-static int find_sdca_filesets(struct device *dev, struct sdw_slave *sdw, +- struct fwnode_handle *function_node, ++static int find_sdca_filesets(struct device *dev, struct fwnode_handle *function_node, + struct sdca_function_data *function) + { + static const int mult_fileset = 3; +@@ -2171,23 +2154,65 @@ static int find_sdca_filesets(struct device *dev, struct sdw_slave *sdw, + set->files = files; + } + +- function->fdl_data.swft = sdw->sdca_data.swft; + function->fdl_data.num_sets = num_sets; + function->fdl_data.sets = sets; + + return 0; + } + ++static int find_sdca_hid(struct device *dev, struct fwnode_handle *function_node, ++ struct sdca_function_data *function) ++{ ++ int num_desc; ++ ++ num_desc = fwnode_property_count_u8(function_node, "mipi-sdca-hid-descriptor"); ++ if (!num_desc) { ++ return 0; ++ } else if (num_desc < 0) { ++ dev_err(dev, "%pfwP: failed to read hid descriptor: %d\n", ++ function_node, num_desc); ++ return num_desc; ++ } else if (num_desc > sizeof(function->hid.desc)) { ++ dev_err(dev, "%pfwP: hid descriptor too large: %d\n", ++ function_node, num_desc); ++ return -EINVAL; ++ } ++ ++ fwnode_property_read_u8_array(function_node, "mipi-sdca-hid-descriptor", ++ (u8 *)&function->hid.desc, num_desc); ++ ++ if (!function->hid.desc.bNumDescriptors) ++ return 0; ++ ++ num_desc = fwnode_property_count_u8(function_node, "mipi-sdca-report-descriptor"); ++ if (num_desc <= 0) { ++ dev_err(dev, "%pfwP: failed to read report descriptor: %d\n", ++ function_node, num_desc); ++ ++ if (!num_desc) ++ return -EINVAL; ++ ++ return num_desc; ++ } ++ ++ function->hid.report_desc = devm_kzalloc(dev, num_desc, GFP_KERNEL); ++ if (!function->hid.report_desc) ++ return -ENOMEM; ++ ++ fwnode_property_read_u8_array(function_node, "mipi-sdca-report-descriptor", ++ function->hid.report_desc, num_desc); ++ ++ return 0; ++} ++ + /** + * sdca_parse_function - parse ACPI DisCo for a Function + * @dev: Pointer to device against which function data will be allocated. +- * @sdw: SoundWire slave device to be processed. + * @function: Pointer to the Function information, to be populated. + * + * Return: Returns 0 for success. + */ +-int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, +- struct sdca_function_data *function) ++int sdca_parse_function(struct device *dev, struct sdca_function_data *function) + { + struct fwnode_handle *node = function->desc->node; + u32 tmp; +@@ -2213,7 +2238,7 @@ int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, + if (ret) + return ret; + +- ret = find_sdca_entities(dev, sdw, node, function); ++ ret = find_sdca_entities(dev, node, function); + if (ret) + return ret; + +@@ -2225,10 +2250,20 @@ int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, + if (ret < 0) + return ret; + +- ret = find_sdca_filesets(dev, sdw, node, function); ++ ret = find_sdca_filesets(dev, node, function); + if (ret) + return ret; + ++ switch (function->desc->type) { ++ case SDCA_FUNCTION_TYPE_HID: ++ ret = find_sdca_hid(dev, node, function); ++ if (ret) ++ return ret; ++ break; ++ default: ++ break; ++ } ++ + return 0; + } + EXPORT_SYMBOL_NS(sdca_parse_function, "SND_SOC_SDCA"); +diff --git a/sound/soc/sdca/sdca_hid.c b/sound/soc/sdca/sdca_hid.c +--- a/sound/soc/sdca/sdca_hid.c ++++ b/sound/soc/sdca/sdca_hid.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -23,18 +24,18 @@ + + static int sdwhid_parse(struct hid_device *hid) + { +- struct sdca_entity *entity = hid->driver_data; ++ struct sdca_function_data *function = hid->driver_data; + unsigned int rsize; + int ret; + +- rsize = le16_to_cpu(entity->hide.hid_desc.rpt_desc.wDescriptorLength); ++ rsize = le16_to_cpu(function->hid.desc.rpt_desc.wDescriptorLength); + + if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) { + dev_err(&hid->dev, "invalid size of report descriptor (%u)\n", rsize); + return -EINVAL; + } + +- ret = hid_parse_report(hid, entity->hide.hid_report_desc, rsize); ++ ret = hid_parse_report(hid, function->hid.report_desc, rsize); + + if (!ret) + return 0; +@@ -85,10 +86,16 @@ static const struct hid_ll_driver sdw_hid_driver = { + .raw_request = sdwhid_raw_request, + }; + +-int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, +- struct sdca_entity *entity) ++/** ++ * sdca_add_hid_device - create a new SDCA HID device ++ * @interrupt: Pointer to the SDCA interrupt information structure. ++ * ++ * Return: Zero on success, and a negative error code on failure. ++ */ ++int sdca_add_hid_device(struct sdca_interrupt *interrupt) + { +- struct sdw_bus *bus = sdw->bus; ++ struct device *dev = interrupt->dev; ++ struct sdca_function_data *function = interrupt->function; + struct hid_device *hid; + int ret; + +@@ -100,16 +107,13 @@ int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, + + hid->dev.parent = dev; + hid->bus = BUS_SDW; +- hid->version = le16_to_cpu(entity->hide.hid_desc.bcdHID); ++ hid->version = le16_to_cpu(function->hid.desc.bcdHID); + +- snprintf(hid->name, sizeof(hid->name), +- "HID sdw:%01x:%01x:%04x:%04x:%02x", +- bus->controller_id, bus->link_id, sdw->id.mfg_id, +- sdw->id.part_id, sdw->id.class_id); ++ strscpy(hid->phys, dev_name(dev)); ++ snprintf(hid->name, sizeof(hid->name), "SDCA %s:%02x", ++ function->desc->name, function->desc->adr); + +- snprintf(hid->phys, sizeof(hid->phys), "%s", dev->bus->name); +- +- hid->driver_data = entity; ++ hid->driver_data = function; + + ret = hid_add_device(hid); + if (ret && ret != -ENODEV) { +@@ -118,12 +122,24 @@ int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, + return ret; + } + +- entity->hide.hid = hid; ++ interrupt->priv = hid; + + return 0; + } + EXPORT_SYMBOL_NS(sdca_add_hid_device, "SND_SOC_SDCA"); + ++/** ++ * sdca_destroy_hid_device - destroy the HID device ++ * @interrupt: Pointer to the SDCA interrupt information structure. ++ */ ++void sdca_destroy_hid_device(struct sdca_interrupt *interrupt) ++{ ++ struct hid_device *hid = interrupt->priv; ++ ++ hid_destroy_device(hid); ++} ++EXPORT_SYMBOL_NS(sdca_destroy_hid_device, "SND_SOC_SDCA"); ++ + /** + * sdca_hid_process_report - read a HID event from the device and report + * @interrupt: Pointer to the SDCA interrupt information structure. +@@ -133,7 +149,7 @@ EXPORT_SYMBOL_NS(sdca_add_hid_device, "SND_SOC_SDCA"); + int sdca_hid_process_report(struct sdca_interrupt *interrupt) + { + struct device *dev = interrupt->dev; +- struct hid_device *hid = interrupt->entity->hide.hid; ++ struct hid_device *hid = interrupt->priv; + void *val __free(kfree) = NULL; + int len, ret; + +diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c +--- a/sound/soc/sdca/sdca_interrupts.c ++++ b/sound/soc/sdca/sdca_interrupts.c +@@ -487,6 +487,12 @@ int sdca_irq_populate_early(struct device *dev, struct regmap *regmap, + } + break; + case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER): ++ interrupt->free_priv = sdca_destroy_hid_device; ++ ++ ret = sdca_add_hid_device(interrupt); ++ if (ret) ++ return ret; ++ + interrupt->handler = hid_handler; + break; + default: +diff --git a/sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c b/sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c +--- a/sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c ++++ b/sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c +@@ -99,11 +99,9 @@ static const struct snd_soc_dai_link bridge_dai_template = { + SND_SOC_DAILINK_REG(asoc_sdw_bridge_dai), + }; + +-int asoc_sdw_bridge_cs35l56_count_sidecar(struct snd_soc_card *card, ++int asoc_sdw_bridge_cs35l56_count_sidecar(struct asoc_sdw_mc_private *ctx, + int *num_dais, int *num_devs) + { +- struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); +- + if (ctx->mc_quirk & SOC_SDW_SIDECAR_AMPS) { + (*num_dais)++; + (*num_devs) += ARRAY_SIZE(bridge_cs35l56_name_prefixes); +diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c +--- a/sound/soc/sdw_utils/soc_sdw_utils.c ++++ b/sound/soc/sdw_utils/soc_sdw_utils.c +@@ -834,6 +834,35 @@ struct asoc_sdw_codec_info codec_info_list[] = { + }, + .dai_num = 2, + }, ++ { ++ .vendor_id = 0x01fa, ++ .part_id = 0x3562, ++ .name_prefix = "AMP", ++ .is_amp = true, ++ .dais = { ++ { ++ .direction = {true, false}, ++ .dai_name = "cs35l56-sdw1", ++ .component_name = "cs35l56", ++ .dai_type = SOC_SDW_DAI_TYPE_AMP, ++ .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, ++ .init = asoc_sdw_cs_amp_init, ++ .rtd_init = asoc_sdw_cs_spk_rtd_init, ++ .controls = generic_spk_controls, ++ .num_controls = ARRAY_SIZE(generic_spk_controls), ++ .widgets = generic_spk_widgets, ++ .num_widgets = ARRAY_SIZE(generic_spk_widgets), ++ }, ++ { ++ .direction = {false, true}, ++ .dai_name = "cs35l56-sdw1c", ++ .dai_type = SOC_SDW_DAI_TYPE_AMP, ++ .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, ++ .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, ++ }, ++ }, ++ .dai_num = 2, ++ }, + { + .vendor_id = 0x01fa, + .part_id = 0x3563, +@@ -1789,9 +1818,7 @@ static int is_sdca_aux_dev_present(struct device *dev, + int adr_index) + { + struct sdw_slave *slave; +- struct device *sdw_dev; + const char *sdw_codec_name; +- int ret = 0; + int i; + + if (!aux_codec_name) +@@ -1801,7 +1828,8 @@ static int is_sdca_aux_dev_present(struct device *dev, + if (!sdw_codec_name) + return -ENOMEM; + +- sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); ++ struct device *sdw_dev __free(put_device) = ++ bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); + if (!sdw_dev) { + dev_err(dev, "codec %s not found\n", sdw_codec_name); + return -EINVAL; +@@ -1811,24 +1839,19 @@ static int is_sdca_aux_dev_present(struct device *dev, + + if (!slave->sdca_data.interface_revision) { + dev_warn(dev, "No SDCA properties, assuming aux '%s' present\n", aux_codec_name); +- ret = 1; +- goto put_dev; ++ return 1; + } + + for (i = 0; i < slave->sdca_data.num_functions; i++) { + const char *fname = slave->sdca_data.function[i].name; + +- if (fname && strstr(aux_codec_name, fname)) { +- ret = 1; +- goto put_dev; +- } ++ if (fname && strstr(aux_codec_name, fname)) ++ return 1; + } + + dev_dbg(dev, "SDCA function for aux '%s' NOT FOUND on slave, skipping\n", aux_codec_name); + +-put_dev: +- put_device(sdw_dev); +- return ret; ++ return 0; + } + + int asoc_sdw_count_sdw_endpoints(struct snd_soc_card *card, +@@ -1928,9 +1951,8 @@ static int is_sdca_endpoint_present(struct device *dev, + const struct snd_soc_acpi_endpoint *adr_end; + const struct asoc_sdw_dai_info *dai_info; + struct sdw_slave *slave; +- struct device *sdw_dev; + const char *sdw_codec_name; +- int ret, i; ++ int i; + + adr_end = &adr_dev->endpoints[end_index]; + dai_info = &codec_info->dais[adr_end->num]; +@@ -1939,7 +1961,8 @@ static int is_sdca_endpoint_present(struct device *dev, + if (!sdw_codec_name) + return -ENOMEM; + +- sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); ++ struct device *sdw_dev __free(put_device) = ++ bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); + if (!sdw_dev) { + dev_err(dev, "codec %s not found\n", sdw_codec_name); + return -EINVAL; +@@ -1950,8 +1973,7 @@ static int is_sdca_endpoint_present(struct device *dev, + /* Make sure BIOS provides SDCA properties */ + if (!slave->sdca_data.interface_revision) { + dev_warn(&slave->dev, "SDCA properties not found in the BIOS\n"); +- ret = 1; +- goto put_device; ++ return 1; + } + + for (i = 0; i < slave->sdca_data.num_functions; i++) { +@@ -1960,8 +1982,7 @@ static int is_sdca_endpoint_present(struct device *dev, + if (dai_type == dai_info->dai_type) { + dev_dbg(&slave->dev, "DAI type %d sdca function %s found\n", + dai_type, slave->sdca_data.function[i].name); +- ret = 1; +- goto put_device; ++ return 1; + } + } + +@@ -1969,21 +1990,16 @@ static int is_sdca_endpoint_present(struct device *dev, + "SDCA device function for DAI type %d not supported, skip endpoint\n", + dai_info->dai_type); + +- ret = 0; +- +-put_device: +- put_device(sdw_dev); +- return ret; ++ return 0; + } + +-int asoc_sdw_parse_sdw_endpoints(struct snd_soc_card *card, ++int asoc_sdw_parse_sdw_endpoints(struct device *dev, ++ struct asoc_sdw_mc_private *ctx, + struct snd_soc_aux_dev *soc_aux, + struct asoc_sdw_dailink *soc_dais, + struct asoc_sdw_endpoint *soc_ends, + int *num_devs) + { +- struct device *dev = card->dev; +- struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_acpi_mach *mach = dev_get_platdata(dev); + struct snd_soc_acpi_mach_params *mach_params = &mach->mach_params; + const struct snd_soc_acpi_link_adr *adr_link; +@@ -2045,7 +2061,7 @@ int asoc_sdw_parse_sdw_endpoints(struct snd_soc_card *card, + ctx->ignore_internal_dmic |= codec_info->ignore_internal_dmic; + + if (codec_info->count_sidecar && codec_info->add_sidecar) { +- ret = codec_info->count_sidecar(card, &num_dais, num_devs); ++ ret = codec_info->count_sidecar(ctx, &num_dais, num_devs); + if (ret) + return ret; + +diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c +--- a/sound/soc/soc-component.c ++++ b/sound/soc/soc-component.c +@@ -29,17 +29,42 @@ static inline int _soc_component_ret_reg_rw(struct snd_soc_component *component, + func, component->name, reg); + } + +-static inline int soc_component_field_shift(struct snd_soc_component *component, +- unsigned int mask) ++struct snd_soc_component *snd_soc_component_alloc(struct device *dev) + { +- if (!mask) { +- dev_err(component->dev, "ASoC: error field mask is zero for %s\n", +- component->name); +- return 0; +- } ++ struct snd_soc_component *component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); + +- return (ffs(mask) - 1); ++ if (!component) ++ return NULL; ++ ++ component->dev = dev; ++ ++ return component; + } ++EXPORT_SYMBOL_GPL(snd_soc_component_alloc); ++ ++void snd_soc_component_set_name(struct snd_soc_component *component, const char *name) ++{ ++ component->name = name; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_set_name); ++ ++const char *snd_soc_component_name(struct snd_soc_component *component) ++{ ++ return component->name; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_name); ++ ++void snd_soc_component_set_priv(struct snd_soc_component *component, void *priv) ++{ ++ component->priv = priv; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_set_priv); ++ ++void *snd_soc_component_to_priv(struct snd_soc_component *component) ++{ ++ return component->priv; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_to_priv); + + /* + * We might want to check substream by using list. +@@ -820,6 +845,18 @@ int snd_soc_component_update_bits_async(struct snd_soc_component *component, + } + EXPORT_SYMBOL_GPL(snd_soc_component_update_bits_async); + ++static inline int soc_component_field_shift(struct snd_soc_component *component, ++ unsigned int mask) ++{ ++ if (!mask) { ++ dev_err(component->dev, "ASoC: error field mask is zero for %s\n", ++ component->name); ++ return 0; ++ } ++ ++ return (ffs(mask) - 1); ++} ++ + /** + * snd_soc_component_read_field() - Read register field value + * @component: Component to read from +diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c +--- a/sound/soc/soc-core.c ++++ b/sound/soc/soc-core.c +@@ -2707,10 +2707,11 @@ static void snd_soc_del_component_unlocked(struct snd_soc_component *component) + list_del(&component->list); + } + +-int snd_soc_component_initialize(struct snd_soc_component *component, +- const struct snd_soc_component_driver *driver, +- struct device *dev) ++static int soc_component_initialize(struct snd_soc_component *component, ++ const struct snd_soc_component_driver *driver) + { ++ struct device *dev = component->dev; ++ + component->dapm = snd_soc_dapm_alloc(dev); + if (!component->dapm) + return -ENOMEM; +@@ -2730,16 +2731,14 @@ int snd_soc_component_initialize(struct snd_soc_component *component, + } + } + +- component->dev = dev; + component->driver = driver; + + return 0; + } +-EXPORT_SYMBOL_GPL(snd_soc_component_initialize); + +-int snd_soc_add_component(struct snd_soc_component *component, +- struct snd_soc_dai_driver *dai_drv, +- int num_dai) ++static int soc_component_add(struct snd_soc_component *component, ++ struct snd_soc_dai_driver *dai_drv, ++ int num_dai) + { + struct snd_soc_card *card, *c; + int ret; +@@ -2778,27 +2777,36 @@ int snd_soc_add_component(struct snd_soc_component *component, + + return ret; + } +-EXPORT_SYMBOL_GPL(snd_soc_add_component); + +-int snd_soc_register_component(struct device *dev, ++int snd_soc_register_component_c(struct snd_soc_component *component, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, + int num_dai) + { +- struct snd_soc_component *component; + int ret; + +- component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); +- if (!component) +- return -ENOMEM; +- +- ret = snd_soc_component_initialize(component, component_driver, dev); ++ ret = soc_component_initialize(component, component_driver); + if (ret < 0) + return ret; + +- return snd_soc_add_component(component, dai_drv, num_dai); ++ return soc_component_add(component, dai_drv, num_dai); + } +-EXPORT_SYMBOL_GPL(snd_soc_register_component); ++EXPORT_SYMBOL_GPL(snd_soc_register_component_c); ++ ++int snd_soc_register_component_d(struct device *dev, ++ const struct snd_soc_component_driver *component_driver, ++ struct snd_soc_dai_driver *dai_drv, ++ int num_dai) ++{ ++ struct snd_soc_component *component; ++ ++ component = snd_soc_component_alloc(dev); ++ if (!component) ++ return -ENOMEM; ++ ++ return snd_soc_register_component_c(component, component_driver, dai_drv, num_dai); ++} ++EXPORT_SYMBOL_GPL(snd_soc_register_component_d); + + /** + * snd_soc_unregister_component_by_driver - Unregister component using a given driver +diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c +--- a/sound/soc/soc-dapm.c ++++ b/sound/soc/soc-dapm.c +@@ -1203,6 +1203,8 @@ static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w, + case snd_soc_dapm_pga: + case snd_soc_dapm_effect: + case snd_soc_dapm_out_drv: ++ case snd_soc_dapm_encoder: ++ case snd_soc_dapm_decoder: + wname_in_long_name = true; + kcname_in_long_name = true; + break; +@@ -2785,6 +2787,8 @@ static ssize_t dapm_widget_show_component(struct snd_soc_component *component, + case snd_soc_dapm_pga: + case snd_soc_dapm_effect: + case snd_soc_dapm_out_drv: ++ case snd_soc_dapm_encoder: ++ case snd_soc_dapm_decoder: + case snd_soc_dapm_mixer: + case snd_soc_dapm_mixer_named_ctl: + case snd_soc_dapm_supply: +@@ -3367,6 +3371,8 @@ int snd_soc_dapm_new_widgets(struct snd_soc_card *card) + case snd_soc_dapm_pga: + case snd_soc_dapm_effect: + case snd_soc_dapm_out_drv: ++ case snd_soc_dapm_encoder: ++ case snd_soc_dapm_decoder: + dapm_new_pga(w); + break; + case snd_soc_dapm_dai_link: +diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c +--- a/sound/soc/soc-generic-dmaengine-pcm.c ++++ b/sound/soc/soc-generic-dmaengine-pcm.c +@@ -3,6 +3,7 @@ + // Copyright (C) 2013, Analog Devices Inc. + // Author: Lars-Peter Clausen + ++#include + #include + #include + #include +@@ -77,7 +78,7 @@ static int dmaengine_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream); + struct dma_slave_config slave_config; + int ret; +@@ -99,7 +100,7 @@ dmaengine_pcm_set_runtime_hwparams(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct device *dma_dev = dmaengine_dma_dev(pcm, substream); + struct dma_chan *chan = pcm->chan[substream->stream]; + struct snd_dmaengine_dai_dma_data *dma_data; +@@ -148,7 +149,7 @@ dmaengine_pcm_set_runtime_hwparams(struct snd_soc_component *component, + static int dmaengine_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct dma_chan *chan = pcm->chan[substream->stream]; + int ret; + +@@ -176,7 +177,7 @@ static struct dma_chan *dmaengine_pcm_compat_request_channel( + struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct snd_dmaengine_dai_dma_data *dma_data; + + if (rtd->dai_link->num_cpus > 1) { +@@ -219,7 +220,7 @@ static bool dmaengine_pcm_can_report_residue(struct device *dev, + static int dmaengine_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + const struct snd_dmaengine_pcm_config *config = pcm->config; + struct device *dev = component->dev; + size_t prealloc_buffer_size; +@@ -279,7 +280,7 @@ static snd_pcm_uframes_t dmaengine_pcm_pointer( + struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE) + return snd_dmaengine_pcm_pointer_no_residue(substream); +@@ -293,7 +294,7 @@ static int dmaengine_copy(struct snd_soc_component *component, + struct iov_iter *iter, unsigned long bytes) + { + struct snd_pcm_runtime *runtime = substream->runtime; +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + int (*process)(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + unsigned long bytes) = pcm->config->process; +@@ -395,6 +396,27 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + */ + if (PTR_ERR(chan) == -EPROBE_DEFER) + return -EPROBE_DEFER; ++ ++ bool has_fw_node = dev->of_node || is_acpi_device_node(dev->fwnode); ++ bool name_exists_in_fw = false; ++ ++ if (has_fw_node) ++ name_exists_in_fw = device_property_match_string(dev, ++ "dma-names", ++ name) >= 0; ++ ++ if (has_fw_node && name_exists_in_fw) ++ dev_warn(dev, "DTS/ACPI DMA channel '%s' request failed (%ld)\n", ++ name, PTR_ERR(chan)); ++ ++ if (has_fw_node && !name_exists_in_fw) ++ dev_warn(dev, "DTS/ACPI name '%s' not found, legacy failed (%ld)\n", ++ name, PTR_ERR(chan)); ++ ++ if (!has_fw_node) ++ dev_warn(dev, "Legacy DMA channel '%s' request failed (%ld)\n", ++ name, PTR_ERR(chan)); ++ + pcm->chan[i] = NULL; + } else { + pcm->chan[i] = chan; +@@ -406,6 +428,12 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) + pcm->chan[1] = pcm->chan[0]; + ++ if (!pcm->chan[0] && ++ !pcm->chan[1]) { ++ dev_err(dev, "no DMA channel found for either playback or capture\n"); ++ return -ENODEV; ++ } ++ + return 0; + } + +@@ -435,10 +463,15 @@ static const struct snd_dmaengine_pcm_config snd_dmaengine_pcm_default_config = + int snd_dmaengine_pcm_register(struct device *dev, + const struct snd_dmaengine_pcm_config *config, unsigned int flags) + { ++ struct snd_soc_component *component; + const struct snd_soc_component_driver *driver; + struct dmaengine_pcm *pcm; + int ret; + ++ component = snd_soc_component_alloc(dev); ++ if (!component) ++ return -ENOMEM; ++ + pcm = kzalloc_obj(*pcm); + if (!pcm) + return -ENOMEM; +@@ -449,7 +482,8 @@ int snd_dmaengine_pcm_register(struct device *dev, + pcm->flags = flags; + + if (config->name) +- pcm->component.name = config->name; ++ snd_soc_component_set_name(component, config->name); ++ snd_soc_component_set_priv(component, pcm); + + ret = dmaengine_pcm_request_chan_of(pcm, dev, config); + if (ret) +@@ -460,11 +494,7 @@ int snd_dmaengine_pcm_register(struct device *dev, + else + driver = &dmaengine_pcm_component; + +- ret = snd_soc_component_initialize(&pcm->component, driver, dev); +- if (ret) +- goto err_free_dma; +- +- ret = snd_soc_add_component(&pcm->component, NULL, 0); ++ ret = snd_soc_register_component(component, driver, NULL, 0); + if (ret) + goto err_free_dma; + +@@ -493,7 +523,7 @@ void snd_dmaengine_pcm_unregister(struct device *dev) + if (!component) + return; + +- pcm = soc_component_to_pcm(component); ++ pcm = snd_soc_component_to_priv(component); + + snd_soc_unregister_component_by_driver(dev, component->driver); + dmaengine_pcm_release_chan(pcm); +diff --git a/sound/soc/soc-topology-test.c b/sound/soc/soc-topology-test.c +--- a/sound/soc/soc-topology-test.c ++++ b/sound/soc/soc-topology-test.c +@@ -45,15 +45,13 @@ static void snd_soc_tplg_test_exit(struct kunit *test) + struct kunit_soc_component { + struct kunit *kunit; + int expect; /* what result we expect when loading topology */ +- struct snd_soc_component comp; + struct snd_soc_card card; + struct firmware fw; + }; + + static int d_probe(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + ret = snd_soc_tplg_component_load(component, NULL, &kunit_comp->fw); +@@ -65,8 +63,7 @@ static int d_probe(struct snd_soc_component *component) + + static void d_remove(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + ret = snd_soc_tplg_component_remove(component); +@@ -214,8 +211,7 @@ static struct tplg_tmpl_002 tplg_tmpl_with_pcm = { + */ + static int d_probe_null_comp(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + /* instead of passing component pointer as first argument, pass NULL here */ +@@ -234,6 +230,7 @@ static const struct snd_soc_component_driver test_component_null_comp = { + static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + int ret; + + /* prepare */ +@@ -249,15 +246,17 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component_null_comp, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component_null_comp, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -276,6 +275,7 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) + static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + int ret; + + /* prepare */ +@@ -291,15 +291,17 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -318,8 +320,7 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) + */ + static int d_probe_null_fw(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + /* instead of passing fw pointer as third argument, pass NULL here */ +@@ -338,6 +339,7 @@ static const struct snd_soc_component_driver test_component_null_fw = { + static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + int ret; + + /* prepare */ +@@ -353,15 +355,17 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component_null_fw, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component_null_fw, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -375,6 +379,7 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -401,15 +406,17 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -425,6 +432,7 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -456,15 +464,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -480,6 +490,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -511,15 +522,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -535,6 +548,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -566,15 +580,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -590,6 +606,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -622,15 +639,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -644,6 +663,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes + static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + u8 *data; + int size; + int ret; +@@ -670,15 +690,17 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + snd_soc_unregister_component(test_dev); +@@ -693,6 +715,7 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) + static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + u8 *data; + int size; + int ret; +@@ -720,16 +743,19 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + + for (i = 0; i < 100; i++) { +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); + +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + snd_soc_unregister_component(test_dev); +@@ -745,6 +771,7 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) + static void snd_soc_tplg_test_load_pcm_tplg_reload_card(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + u8 *data; + int size; + int ret; +@@ -772,11 +799,13 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_card(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + +- /* run test */ +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); + +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ snd_soc_component_set_priv(component, kunit_comp); ++ ++ /* run test */ ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + for (i = 0; i < 100; i++) { +diff --git a/sound/soc/sof/amd/Kconfig b/sound/soc/sof/amd/Kconfig +--- a/sound/soc/sof/amd/Kconfig ++++ b/sound/soc/sof/amd/Kconfig +@@ -104,4 +104,15 @@ config SND_SOC_SOF_AMD_ACP70 + AMD ACP7.0/ACP7.1 version based platforms. + Say Y if you want to enable SOF on ACP7.0/ACP7.1 based platforms. + ++config SND_SOC_SOF_AMD_ACP7X ++ tristate "SOF support for ACP7.B/7.F platforms" ++ depends on SND_SOC_SOF_PCI ++ depends on AMD_NODE ++ select SND_SOC_SOF_AMD_COMMON ++ help ++ Select this option for SOF support on AMD ACP7.B and ACP7.F PCI ++ revision based platforms. ++ Say Y if you want to enable SOF on ACP7.B/7.F based platforms. ++ If unsure select "N". ++ + endif +diff --git a/sound/soc/sof/amd/Makefile b/sound/soc/sof/amd/Makefile +--- a/sound/soc/sof/amd/Makefile ++++ b/sound/soc/sof/amd/Makefile +@@ -11,6 +11,7 @@ snd-sof-amd-rembrandt-y := pci-rmb.o rembrandt.o + snd-sof-amd-vangogh-y := pci-vangogh.o vangogh.o + snd-sof-amd-acp63-y := pci-acp63.o acp63.o + snd-sof-amd-acp70-y := pci-acp70.o acp70.o ++snd-sof-amd-acp7x-y := pci-acp7x.o acp7x.o + + obj-$(CONFIG_SND_SOC_SOF_AMD_COMMON) += snd-sof-amd-acp.o + obj-$(CONFIG_SND_SOC_SOF_AMD_RENOIR) += snd-sof-amd-renoir.o +@@ -18,3 +19,4 @@ obj-$(CONFIG_SND_SOC_SOF_AMD_REMBRANDT) += snd-sof-amd-rembrandt.o + obj-$(CONFIG_SND_SOC_SOF_AMD_VANGOGH) += snd-sof-amd-vangogh.o + obj-$(CONFIG_SND_SOC_SOF_AMD_ACP63) += snd-sof-amd-acp63.o + obj-$(CONFIG_SND_SOC_SOF_AMD_ACP70) += snd-sof-amd-acp70.o ++obj-$(CONFIG_SND_SOC_SOF_AMD_ACP7X) += snd-sof-amd-acp7x.o +diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h +--- a/sound/soc/sof/amd/acp-dsp-offset.h ++++ b/sound/soc/sof/amd/acp-dsp-offset.h +@@ -68,12 +68,14 @@ + #define ACP5X_PGFSM_BASE 0x1424 + #define ACP6X_PGFSM_BASE 0x1024 + #define ACP70_PGFSM_BASE ACP6X_PGFSM_BASE ++#define ACP7X_PGFSM_BASE ACP6X_PGFSM_BASE + #define PGFSM_CONTROL_OFFSET 0x0 + #define PGFSM_STATUS_OFFSET 0x4 + #define ACP3X_CLKMUX_SEL 0x1424 + #define ACP5X_CLKMUX_SEL 0x142C + #define ACP6X_CLKMUX_SEL 0x102C + #define ACP70_CLKMUX_SEL ACP6X_CLKMUX_SEL ++#define ACP7X_CLKMUX_SEL ACP6X_CLKMUX_SEL + + /* Registers from ACP_INTR block */ + #define ACP3X_EXT_INTR_STAT 0x1808 +@@ -86,21 +88,31 @@ + #define ACP70_EXTERNAL_INTR_CNTL ACP6X_EXTERNAL_INTR_CNTL + #define ACP70_EXT_INTR_STAT ACP6X_EXT_INTR_STAT + #define ACP70_EXT_INTR_STAT1 ACP6X_EXT_INTR_STAT1 ++#define ACP7X_EXTERNAL_INTR_ENB ACP6X_EXTERNAL_INTR_ENB ++#define ACP7X_EXTERNAL_INTR_CNTL 0x1A04 ++#define ACP7X_EXT_INTR_STAT 0x1A1C ++#define ACP7X_EXTERNAL_INTR_CNTL1 0x1A08 ++#define ACP7X_EXT_INTR_STAT1 0x1A20 + + #define ACP3X_DSP_SW_INTR_BASE 0x1814 + #define ACP5X_DSP_SW_INTR_BASE 0x1814 + #define ACP6X_DSP_SW_INTR_BASE 0x1808 + #define ACP70_DSP_SW_INTR_BASE ACP6X_DSP_SW_INTR_BASE ++#define ACP7X_DSP_SW_INTR_BASE 0x1860 + #define DSP_SW_INTR_CNTL_OFFSET 0x0 + #define DSP_SW_INTR_STAT_OFFSET 0x4 ++#define ACP7X_DSP_SW_INTR_STAT (ACP7X_DSP_SW_INTR_BASE + DSP_SW_INTR_STAT_OFFSET) + #define DSP_SW_INTR_TRIG_OFFSET 0x8 ++#define ACP7X_DSP_SW_INTR_TRIG_OFFSET 0x30 + #define ACP3X_ERROR_STATUS 0x18C4 + #define ACP6X_ERROR_STATUS 0x1A4C + #define ACP70_ERROR_STATUS ACP6X_ERROR_STATUS ++#define ACP7X_ERROR_STATUS 0x1A88 + #define ACP3X_AXI2DAGB_SEM_0 0x1880 + #define ACP5X_AXI2DAGB_SEM_0 0x1884 + #define ACP6X_AXI2DAGB_SEM_0 0x1874 + #define ACP70_AXI2DAGB_SEM_0 ACP6X_AXI2DAGB_SEM_0 ++#define ACP7X_AXI2DAGB_SEM_0 0x18F4 + + /* ACP common registers to report errors related to I2S & SoundWire interfaces */ + #define ACP3X_SW_I2S_ERROR_REASON 0x18C8 +@@ -123,6 +135,7 @@ + #define ACP_SCRATCH_REG_0 0x10000 + #define ACP6X_DSP_FUSION_RUNSTALL 0x0644 + #define ACP70_DSP_FUSION_RUNSTALL ACP6X_DSP_FUSION_RUNSTALL ++#define ACP7X_DSP_FUSION_RUNSTALL ACP6X_DSP_FUSION_RUNSTALL + + /* Cache window registers */ + #define ACP_DSP0_CACHE_OFFSET0 0x0420 +@@ -139,5 +152,9 @@ + #define ACP70_SDW1_HOST_WAKE_STAT BIT(25) + #define ACP70_SDW0_PME_STAT BIT(26) + #define ACP70_SDW1_PME_STAT BIT(27) ++#define ACP7X_DSP0_IDMA_ERROR_MASK 0x4B0 ++#define ACP7X_IDMA_ERROR_MASK 0x1FF9FF ++#define ACP7X_ZSC_DSP_CTRL 0x001014 ++#define ACP7X_PME_EN ACP70_PME_EN + + #endif +diff --git a/sound/soc/sof/amd/acp-ipc.c b/sound/soc/sof/amd/acp-ipc.c +--- a/sound/soc/sof/amd/acp-ipc.c ++++ b/sound/soc/sof/amd/acp-ipc.c +@@ -32,11 +32,20 @@ static void acpbus_trigger_host_to_dsp_swintr(struct acp_dev_data *adata) + struct snd_sof_dev *sdev = adata->dev; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + u32 swintr_trigger; ++ unsigned int swintr_trigger_reg_offset; + ++ switch (adata->pci_rev) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ swintr_trigger_reg_offset = ACP7X_DSP_SW_INTR_TRIG_OFFSET; ++ break; ++ default: ++ swintr_trigger_reg_offset = DSP_SW_INTR_TRIG_OFFSET; ++ } + swintr_trigger = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->dsp_intr_base + +- DSP_SW_INTR_TRIG_OFFSET); ++ swintr_trigger_reg_offset); + swintr_trigger |= 0x01; +- snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->dsp_intr_base + DSP_SW_INTR_TRIG_OFFSET, ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->dsp_intr_base + swintr_trigger_reg_offset, + swintr_trigger); + } + +diff --git a/sound/soc/sof/amd/acp-loader.c b/sound/soc/sof/amd/acp-loader.c +--- a/sound/soc/sof/amd/acp-loader.c ++++ b/sound/soc/sof/amd/acp-loader.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + #include "../ops.h" + #include "acp-dsp-offset.h" +@@ -173,10 +174,31 @@ int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev) + + adata = sdev->pdata->hw_pdata; + +- if (adata->quirks && adata->quirks->signed_fw_image) ++ if (adata->pci_rev >= ACP7B_PCI_ID) { ++ if (adata->acp_sof_signed_firmware_image) { ++ if (adata->fw_bin_size <= ACP_IMAGE_HEADER_SIZE) { ++ dev_err(sdev->dev, "Invalid signed firmware size %u\n", ++ adata->fw_bin_size); ++ return -EINVAL; ++ } ++ size_fw = get_unaligned_le32(adata->bin_buf + ++ ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF); ++ if (!size_fw || ++ size_fw > adata->fw_bin_size - ACP_IMAGE_HEADER_SIZE) { ++ dev_err(sdev->dev, ++ "Invalid signed firmware payload size %u (max %u)\n", ++ size_fw, adata->fw_bin_size - ACP_IMAGE_HEADER_SIZE); ++ return -EINVAL; ++ } ++ size_fw += ACP_IMAGE_HEADER_SIZE; ++ } else { ++ size_fw = adata->fw_bin_size; ++ } ++ } else if (adata->quirks && adata->quirks->signed_fw_image) { + size_fw = adata->fw_bin_size - ACP_FIRMWARE_SIGNATURE; +- else ++ } else { + size_fw = adata->fw_bin_size; ++ } + + page_count = PAGE_ALIGN(size_fw) >> PAGE_SHIFT; + adata->fw_bin_page_count = page_count; +@@ -312,9 +334,14 @@ int acp_sof_load_signed_firmware(struct snd_sof_dev *sdev) + } + kfree(fw_filename); + +- ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_DRAM, 0, +- (void *)adata->fw_dbin->data, +- adata->fw_dbin->size); ++ if (adata->pci_rev >= ACP7B_PCI_ID) ++ ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_SRAM, 0, ++ (void *)adata->fw_dbin->data, ++ adata->fw_dbin->size); ++ else ++ ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_DRAM, 0, ++ (void *)adata->fw_dbin->data, ++ adata->fw_dbin->size); + return ret; + } + EXPORT_SYMBOL_NS(acp_sof_load_signed_firmware, "SND_SOC_SOF_AMD_COMMON"); +diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c +--- a/sound/soc/sof/amd/acp.c ++++ b/sound/soc/sof/amd/acp.c +@@ -12,6 +12,7 @@ + * Hardware interface for generic AMD ACP processor + */ + ++#include + #include + #include + #include +@@ -60,6 +61,8 @@ static void init_dma_descriptor(struct acp_dev_data *adata) + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: + acp_dma_desc_base_addr = ACP70_DMA_DESC_BASE_ADDR; + acp_dma_desc_max_num_dscr = ACP70_DMA_DESC_MAX_NUM_DSCR; + break; +@@ -101,6 +104,8 @@ static int config_dma_channel(struct acp_dev_data *adata, unsigned int ch, + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: + acp_dma_cntl_0 = ACP70_DMA_CNTL_0; + acp_dma_ch_rst_sts = ACP70_DMA_CH_RST_STS; + acp_dma_dscr_err_sts_0 = ACP70_DMA_ERR_STS_0; +@@ -283,7 +288,8 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, + } + } + +- if (adata->quirks && adata->quirks->signed_fw_image) ++ if ((adata->quirks && adata->quirks->signed_fw_image) || ++ adata->acp_sof_signed_firmware_image) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_INCLUDE_HDR, ACP_SHA_HEADER); + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_STRT_ADDR, start_addr); +@@ -342,6 +348,8 @@ int acp_dma_status(struct acp_dev_data *adata, unsigned char ch) + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: + acp_dma_ch_sts = ACP70_DMA_CH_STS; + break; + default: +@@ -534,8 +542,17 @@ static irqreturn_t acp_irq_handler(int irq, void *dev_id) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_ERROR_IRQ_MASK); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_sw0_i2s_err_reason, 0); + /* ACP_SW1_I2S_ERROR_REASON is newly added register from rmb platform onwards */ +- if (adata->pci_rev >= ACP_RMB_PCI_ID) ++ switch (adata->pci_rev) { ++ case ACP_RMB_PCI_ID: ++ case ACP63_PCI_ID: ++ case ACP70_PCI_ID: ++ case ACP71_PCI_ID: ++ case ACP72_PCI_ID: + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SW1_I2S_ERROR_REASON, 0); ++ break; ++ default: ++ break; ++ } + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_error_stat, 0); + irq_flag = 1; + } +@@ -564,6 +581,35 @@ static irqreturn_t acp_irq_handler(int irq, void *dev_id) + return IRQ_NONE; + } + ++static irqreturn_t acp7x_irq_handler(int irq, void *dev_id) ++{ ++ struct snd_sof_dev *sdev = dev_id; ++ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); ++ unsigned int base = desc->dsp_intr_base; ++ unsigned int val; ++ unsigned int ext_intr_stat; ++ int irq_flag = 0; ++ ++ val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET); ++ if (val & ACP_DSP_TO_HOST_IRQ) { ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET, ++ ACP_DSP_TO_HOST_IRQ); ++ return IRQ_WAKE_THREAD; ++ } ++ ++ ext_intr_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat); ++ if (ext_intr_stat & ACP_ERROR_IRQ_MASK) { ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_ERROR_IRQ_MASK); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_error_stat, 0); ++ irq_flag = 1; ++ } ++ ++ if (irq_flag) ++ return IRQ_HANDLED; ++ ++ return IRQ_NONE; ++} ++ + static int acp_power_on(struct snd_sof_dev *sdev) + { + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); +@@ -571,6 +617,7 @@ static int acp_power_on(struct snd_sof_dev *sdev) + unsigned int base = desc->pgfsm_base; + unsigned int val; + unsigned int acp_pgfsm_status_mask, acp_pgfsm_cntl_mask; ++ bool use_masked_status = false; + int ret; + + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + PGFSM_STATUS_OFFSET); +@@ -595,6 +642,12 @@ static int acp_power_on(struct snd_sof_dev *sdev) + acp_pgfsm_status_mask = ACP70_PGFSM_STATUS_MASK; + acp_pgfsm_cntl_mask = ACP70_PGFSM_CNTL_POWER_ON_MASK; + break; ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ acp_pgfsm_status_mask = ACP7X_PGFSM_STATUS_MASK; ++ acp_pgfsm_cntl_mask = ACP7X_PGFSM_CNTL_POWER_ON_MASK; ++ use_masked_status = true; ++ break; + default: + return -EINVAL; + } +@@ -603,8 +656,17 @@ static int acp_power_on(struct snd_sof_dev *sdev) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + PGFSM_CONTROL_OFFSET, + acp_pgfsm_cntl_mask); + +- ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, base + PGFSM_STATUS_OFFSET, val, +- !val, ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US); ++ if (use_masked_status) ++ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ++ base + PGFSM_STATUS_OFFSET, val, ++ !(val & acp_pgfsm_status_mask), ++ ACP_REG_POLL_INTERVAL, ++ ACP_REG_POLL_TIMEOUT_US); ++ else ++ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ++ base + PGFSM_STATUS_OFFSET, val, ++ !val, ACP_REG_POLL_INTERVAL, ++ ACP_REG_POLL_TIMEOUT_US); + if (ret < 0) + dev_err(sdev->dev, "timeout in ACP_PGFSM_STATUS read\n"); + +@@ -703,6 +765,13 @@ static int acp_init(struct snd_sof_dev *sdev) + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP70_PME_EN, 1); + break; ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 0); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_DSP0_IDMA_ERROR_MASK, ++ ACP7X_IDMA_ERROR_MASK); ++ break; + } + return 0; + } +@@ -749,6 +818,8 @@ int amd_sof_acp_suspend(struct snd_sof_dev *sdev, u32 target_state) + case ACP72_PCI_ID: + enable = true; + break; ++ default: ++ break; + } + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); + +@@ -990,6 +1061,194 @@ void amd_sof_acp_remove(struct snd_sof_dev *sdev) + } + EXPORT_SYMBOL_NS(amd_sof_acp_remove, "SND_SOC_SOF_AMD_COMMON"); + ++int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) ++{ ++ struct pci_dev *pci = to_pci_dev(sdev->dev); ++ struct acp_dev_data *adata; ++ const struct sof_amd_acp_desc *chip; ++ const union acpi_object *obj; ++ struct acpi_device *adev; ++ unsigned int addr; ++ unsigned int irqflags; ++ int ret; ++ ++ chip = get_chip_info(sdev->pdata); ++ if (!chip) { ++ dev_err(sdev->dev, "no such device supported, chip id:%x\n", pci->device); ++ return -EIO; ++ } ++ adata = devm_kzalloc(sdev->dev, sizeof(struct acp_dev_data), GFP_KERNEL); ++ if (!adata) ++ return -ENOMEM; ++ ++ adata->dev = sdev; ++ adata->dmic_dev = platform_device_register_data(sdev->dev, "dmic-codec", ++ PLATFORM_DEVID_NONE, NULL, 0); ++ if (IS_ERR(adata->dmic_dev)) { ++ dev_err(sdev->dev, "failed to register platform for dmic codec\n"); ++ return PTR_ERR(adata->dmic_dev); ++ } ++ ++ addr = pci_resource_start(pci, ACP_DSP_BAR); ++ sdev->bar[ACP_DSP_BAR] = devm_ioremap(sdev->dev, addr, pci_resource_len(pci, ACP_DSP_BAR)); ++ if (!sdev->bar[ACP_DSP_BAR]) { ++ dev_err(sdev->dev, "ioremap error\n"); ++ ret = -ENXIO; ++ goto unregister_dev; ++ } ++ ++ pci_set_master(pci); ++ adata->addr = addr; ++ adata->reg_range = chip->reg_end_addr - chip->reg_start_addr; ++ adata->pci_rev = pci->revision; ++ mutex_init(&adata->acp_lock); ++ sdev->pdata->hw_pdata = adata; ++ ++ ret = acp_init(sdev); ++ if (ret < 0) ++ goto unregister_dev; ++ ++ adev = ACPI_COMPANION(&pci->dev); ++ ++ sdev->ipc_irq = pci->irq; ++ irqflags = IRQF_SHARED; ++ ++ ret = request_threaded_irq(pci->irq, acp7x_irq_handler, acp_irq_thread, ++ irqflags, "AudioDSP", sdev); ++ if (ret < 0) { ++ dev_err(sdev->dev, "failed to register IRQ %d\n", sdev->ipc_irq); ++ goto unregister_dev; ++ } ++ ++ if (adev) { ++ if (!acpi_dev_get_property(adev, "acp-sof-signed-firmware-image", ++ ACPI_TYPE_INTEGER, &obj)) ++ adata->acp_sof_signed_firmware_image = obj->integer.value; ++ } ++ ++ sdev->dsp_box.offset = 0; ++ sdev->dsp_box.size = BOX_SIZE_512; ++ ++ sdev->host_box.offset = sdev->dsp_box.offset + sdev->dsp_box.size; ++ sdev->host_box.size = BOX_SIZE_512; ++ ++ sdev->debug_box.offset = sdev->host_box.offset + sdev->host_box.size; ++ sdev->debug_box.size = BOX_SIZE_1024; ++ ++ if (adata->acp_sof_signed_firmware_image) { ++ adata->fw_code_bin = devm_kasprintf(sdev->dev, GFP_KERNEL, ++ "sof-%s-code.bin", chip->name); ++ if (!adata->fw_code_bin) { ++ ret = -ENOMEM; ++ goto free_ipc_irq; ++ } ++ adata->fw_data_bin = devm_kasprintf(sdev->dev, GFP_KERNEL, ++ "sof-%s-data.bin", chip->name); ++ if (!adata->fw_data_bin) { ++ ret = -ENOMEM; ++ goto free_ipc_irq; ++ } ++ } ++ ++ adata->enable_fw_debug = enable_fw_debug; ++ acp_memory_init(sdev); ++ acp_dsp_stream_init(sdev); ++ ++ return 0; ++ ++free_ipc_irq: ++ free_irq(sdev->ipc_irq, sdev); ++unregister_dev: ++ platform_device_unregister(adata->dmic_dev); ++ return ret; ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_probe, "SND_SOC_SOF_AMD_COMMON"); ++ ++void amd_sof_acp7x_remove(struct snd_sof_dev *sdev) ++{ ++ struct acp_dev_data *adata = sdev->pdata->hw_pdata; ++ ++ if (sdev->ipc_irq) ++ free_irq(sdev->ipc_irq, sdev); ++ ++ if (adata->dmic_dev) ++ platform_device_unregister(adata->dmic_dev); ++ ++ acp_reset(sdev); ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_remove, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) ++{ ++ struct acp_dev_data *acp_data; ++ int ret; ++ bool enable = false; ++ ++ acp_data = sdev->pdata->hw_pdata; ++ ++ ret = acp_reset(sdev); ++ if (ret) { ++ dev_err(sdev->dev, "ACP Reset failed\n"); ++ return ret; ++ } ++ switch (acp_data->pci_rev) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ enable = true; ++ break; ++ default: ++ break; ++ } ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) ++{ ++ struct acp_dev_data *acp_data; ++ int ret; ++ ++ acp_data = sdev->pdata->hw_pdata; ++ ++ ret = acp_init(sdev); ++ if (ret) { ++ dev_err(sdev->dev, "ACP Init failed\n"); ++ return ret; ++ } ++ ret = acp_memory_init(sdev); ++ if (ret) { ++ dev_err(sdev->dev, "ACP Memory init failed\n"); ++ return ret; ++ } ++ ++ switch (acp_data->pci_rev) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); ++ break; ++ default: ++ break; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_resume, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev) ++{ ++ return amd_sof_acp7x_suspend(sdev, 0); ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend_runtime, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev) ++{ ++ return amd_sof_acp7x_resume(sdev); ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_resume_runtime, "SND_SOC_SOF_AMD_COMMON"); ++ + MODULE_LICENSE("Dual BSD/GPL"); + MODULE_DESCRIPTION("AMD ACP sof driver"); + MODULE_IMPORT_NS("SOUNDWIRE_AMD_INIT"); +diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h +--- a/sound/soc/sof/amd/acp.h ++++ b/sound/soc/sof/amd/acp.h +@@ -76,6 +76,12 @@ + #define ACP70_PCI_ID 0x70 + #define ACP71_PCI_ID 0x71 + #define ACP72_PCI_ID 0x72 ++#define ACP7B_PCI_ID 0x7B ++#define ACP7F_PCI_ID 0x7F ++ ++#define ACP7X_PGFSM_CNTL_POWER_ON_MASK 0x7F ++#define ACP7X_PGFSM_STATUS_MASK 0xFFF ++#define ACP7X_SRAM_PTE_OFFSET ACP6X_SRAM_PTE_OFFSET + + #define HOST_BRIDGE_CZN 0x1630 + #define HOST_BRIDGE_VGH 0x1645 +@@ -107,6 +113,9 @@ + #define PROBE_STATUS_BIT BIT(31) + + #define ACP_FIRMWARE_SIGNATURE 0x100 ++#define ACP_IMAGE_HEADER_SIZE ACP_FIRMWARE_SIGNATURE ++#define ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF 0x14 ++ + #define ACP_ERROR_IRQ_MASK BIT(29) + #define ACP_SDW0_IRQ_MASK BIT(21) + #define ACP_SDW1_IRQ_MASK BIT(2) +@@ -271,6 +280,7 @@ struct acp_dev_data { + /* acp70_sdw1_wake_event flag set to true when wake irq asserted for SW1 instance */ + bool acp70_sdw1_wake_event; + unsigned int pci_rev; ++ int acp_sof_signed_firmware_image; + }; + + void memcpy_to_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *src, size_t bytes); +@@ -344,6 +354,16 @@ int sof_acp63_ops_init(struct snd_sof_dev *sdev); + extern struct snd_sof_dsp_ops sof_acp70_ops; + int sof_acp70_ops_init(struct snd_sof_dev *sdev); + ++extern struct snd_sof_dsp_ops sof_acp7x_ops; ++int sof_acp7x_ops_init(struct snd_sof_dev *sdev); ++ ++int amd_sof_acp7x_probe(struct snd_sof_dev *sdev); ++void amd_sof_acp7x_remove(struct snd_sof_dev *sdev); ++int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state); ++int amd_sof_acp7x_resume(struct snd_sof_dev *sdev); ++int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev); ++int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev); ++ + struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev); + /* Machine configuration */ + int snd_amd_acp_find_config(struct pci_dev *pci); +diff --git a/sound/soc/sof/amd/acp7x.c b/sound/soc/sof/amd/acp7x.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/sof/amd/acp7x.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) ++// ++// This file is provided under a dual BSD/GPLv2 license. When using or ++// redistributing this file, you may do so under either license. ++// ++// Copyright(c) 2025 Advanced Micro Devices, Inc. ++// ++// Authors: Vijendar Mukunda ++ ++/* ++ * Hardware interface for Audio DSP on ACP7.B/7.F platforms ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "../ops.h" ++#include "../sof-audio.h" ++#include "acp.h" ++#include "acp-dsp-offset.h" ++ ++#define I2S_TDM0_INSTANCE 0 ++#define I2S_TDM1_INSTANCE 1 ++#define I2S_TDM2_INSTANCE 2 ++#define PDM0_DMIC_INSTANCE 3 ++#define PDM1_DMIC_INSTANCE 4 ++ ++static struct snd_soc_dai_driver acp7x_sof_dai[] = { ++ [I2S_TDM0_INSTANCE] = { ++ .id = I2S_TDM0_INSTANCE, ++ .name = "acp-sof-i2s0", ++ .playback = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 8, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ /* Supporting only stereo for I2S HS controller capture */ ++ .channels_min = 2, ++ .channels_max = 2, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [I2S_TDM1_INSTANCE] = { ++ .id = I2S_TDM1_INSTANCE, ++ .name = "acp-sof-i2s1", ++ .playback = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 8, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ /* Supporting only stereo for I2S BT controller capture */ ++ .channels_min = 2, ++ .channels_max = 2, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [I2S_TDM2_INSTANCE] = { ++ .id = I2S_TDM2_INSTANCE, ++ .name = "acp-sof-i2s2", ++ .playback = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 8, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ /* Supporting only stereo for I2S SP controller capture */ ++ .channels_min = 2, ++ .channels_max = 2, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [PDM0_DMIC_INSTANCE] = { ++ .id = PDM0_DMIC_INSTANCE, ++ .name = "acp-sof-dmic0", ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 4, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [PDM1_DMIC_INSTANCE] = { ++ .id = PDM1_DMIC_INSTANCE, ++ .name = "acp-sof-dmic1", ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 4, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ }, ++}; ++ ++static int sof_acp7x_post_fw_run_delay(struct snd_sof_dev *sdev) ++{ ++ /* ++ * Resuming from suspend in some cases may cause the DSP firmware ++ * to enter an unrecoverable faulty state. Delaying a bit any host ++ * to DSP transmission right after firmware boot completion seems ++ * to resolve the issue. ++ */ ++ if (!sdev->first_boot) ++ usleep_range(100, 150); ++ ++ return 0; ++} ++ ++struct snd_sof_dsp_ops sof_acp7x_ops; ++EXPORT_SYMBOL_NS(sof_acp7x_ops, "SND_SOC_SOF_AMD_COMMON"); ++ ++int sof_acp7x_ops_init(struct snd_sof_dev *sdev) ++{ ++ struct acpi_device *adev = ACPI_COMPANION(&to_pci_dev(sdev->dev)->dev); ++ const union acpi_object *obj; ++ int acp_sof_signed_firmware_image = 0; ++ int acp_sof_post_fw_run_delay = 0; ++ ++ /* common defaults */ ++ memcpy(&sof_acp7x_ops, &sof_acp_common_ops, sizeof(struct snd_sof_dsp_ops)); ++ ++ sof_acp7x_ops.drv = acp7x_sof_dai; ++ sof_acp7x_ops.num_drv = ARRAY_SIZE(acp7x_sof_dai); ++ sof_acp7x_ops.probe = amd_sof_acp7x_probe; ++ sof_acp7x_ops.remove = amd_sof_acp7x_remove; ++ ++ if (adev) { ++ if (!acpi_dev_get_property(adev, "acp-sof-signed-firmware-image", ++ ACPI_TYPE_INTEGER, &obj)) ++ acp_sof_signed_firmware_image = obj->integer.value; ++ ++ if (!acpi_dev_get_property(adev, "acp-sof-post_fw_run_delay", ++ ACPI_TYPE_INTEGER, &obj)) ++ acp_sof_post_fw_run_delay = obj->integer.value; ++ } ++ ++ if (acp_sof_signed_firmware_image) ++ sof_acp7x_ops.load_firmware = acp_sof_load_signed_firmware; ++ ++ if (acp_sof_post_fw_run_delay) ++ sof_acp7x_ops.post_fw_run = sof_acp7x_post_fw_run_delay; ++ ++ sof_acp7x_ops.suspend = amd_sof_acp7x_suspend; ++ sof_acp7x_ops.resume = amd_sof_acp7x_resume; ++ sof_acp7x_ops.runtime_suspend = amd_sof_acp7x_suspend_runtime; ++ sof_acp7x_ops.runtime_resume = amd_sof_acp7x_resume_runtime; ++ ++ return 0; ++} +diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/sof/amd/pci-acp7x.c +@@ -0,0 +1,116 @@ ++// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) ++// ++// This file is provided under a dual BSD/GPLv2 license. When using or ++// redistributing this file, you may do so under either license. ++// ++// Copyright(c) 2025 Advanced Micro Devices, Inc. All rights reserved. ++// ++// Authors: Vijendar Mukunda ++ ++/* ++ * PCI interface for ACP7.B/7.F devices ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "../ops.h" ++#include "../sof-pci-dev.h" ++#include "../../amd/mach-config.h" ++#include "acp.h" ++#include "acp-dsp-offset.h" ++ ++#define ACP7X_FUTURE_REG_ACLK_0 0x18e0 ++#define ACP7X_REG_START 0x1240000 ++#define ACP7X_REG_END 0x125C000 ++ ++static const struct sof_amd_acp_desc acp7x_chip_info = { ++ .name = "acp7x", ++ .pgfsm_base = ACP7X_PGFSM_BASE, ++ .ext_intr_enb = ACP6X_EXTERNAL_INTR_ENB, ++ .ext_intr_cntl = ACP7X_EXTERNAL_INTR_CNTL, ++ .ext_intr_stat = ACP7X_EXT_INTR_STAT, ++ .ext_intr_stat1 = ACP7X_EXT_INTR_STAT1, ++ .dsp_intr_base = ACP7X_DSP_SW_INTR_BASE, ++ .acp_error_stat = ACP7X_ERROR_STATUS, ++ .sram_pte_offset = ACP7X_SRAM_PTE_OFFSET, ++ .hw_semaphore_offset = ACP7X_AXI2DAGB_SEM_0, ++ .fusion_dsp_offset = ACP7X_DSP_FUSION_RUNSTALL, ++ .probe_reg_offset = ACP7X_FUTURE_REG_ACLK_0, ++ .reg_start_addr = ACP7X_REG_START, ++ .reg_end_addr = ACP7X_REG_END, ++}; ++ ++static const struct sof_dev_desc acp7x_desc = { ++ .machines = snd_soc_acpi_amd_acp7x_sof_machines, ++ .resindex_lpe_base = 0, ++ .resindex_pcicfg_base = -1, ++ .resindex_imr_base = -1, ++ .irqindex_host_ipc = -1, ++ .chip_info = &acp7x_chip_info, ++ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3), ++ .ipc_default = SOF_IPC_TYPE_3, ++ .default_fw_path = { ++ [SOF_IPC_TYPE_3] = "amd/sof", ++ }, ++ .default_tplg_path = { ++ [SOF_IPC_TYPE_3] = "amd/sof-tplg", ++ }, ++ .default_fw_filename = { ++ [SOF_IPC_TYPE_3] = "sof-acp7x.ri", ++ }, ++ .nocodec_tplg_filename = "sof-acp.tplg", ++ .ops = &sof_acp7x_ops, ++ .ops_init = sof_acp7x_ops_init, ++}; ++ ++static int acp7x_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) ++{ ++ unsigned int flag; ++ ++ switch (pci->revision) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ break; ++ default: ++ return -ENODEV; ++ } ++ ++ flag = snd_amd_acp_find_config(pci); ++ if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) ++ return -ENODEV; ++ ++ return sof_pci_probe(pci, pci_id); ++} ++ ++static void acp7x_pci_remove(struct pci_dev *pci) ++{ ++ sof_pci_remove(pci); ++} ++ ++/* PCI IDs */ ++static const struct pci_device_id acp7x_pci_ids[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), ++ .driver_data = (unsigned long)&acp7x_desc}, ++ { 0, } ++}; ++MODULE_DEVICE_TABLE(pci, acp7x_pci_ids); ++ ++/* pci_driver definition */ ++static struct pci_driver snd_sof_pci_amd_acp7x_driver = { ++ .name = KBUILD_MODNAME, ++ .id_table = acp7x_pci_ids, ++ .probe = acp7x_pci_probe, ++ .remove = acp7x_pci_remove, ++ .driver = { ++ .pm = pm_ptr(&sof_pci_pm), ++ }, ++}; ++module_pci_driver(snd_sof_pci_amd_acp7x_driver); ++ ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_DESCRIPTION("ACP7X SOF Driver"); ++MODULE_IMPORT_NS("SND_SOC_SOF_AMD_COMMON"); ++MODULE_IMPORT_NS("SND_SOC_SOF_PCI_DEV"); +diff --git a/sound/soc/sof/fw-file-profile.c b/sound/soc/sof/fw-file-profile.c +--- a/sound/soc/sof/fw-file-profile.c ++++ b/sound/soc/sof/fw-file-profile.c +@@ -16,20 +16,19 @@ static int sof_test_firmware_file(struct device *dev, + enum sof_ipc_type *ipc_type_to_adjust) + { + enum sof_ipc_type fw_ipc_type; +- const struct firmware *fw; +- const char *fw_filename; + const u32 *magic; + int ret; + +- fw_filename = kasprintf(GFP_KERNEL, "%s/%s", profile->fw_path, +- profile->fw_name); ++ const char *fw_filename __free(kfree) = ++ kasprintf(GFP_KERNEL, "%s/%s", profile->fw_path, ++ profile->fw_name); + if (!fw_filename) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = firmware_request_nowarn(&fw, fw_filename, dev); + if (ret < 0) { + dev_dbg(dev, "Failed to open firmware file: %s\n", fw_filename); +- kfree(fw_filename); + return ret; + } + +@@ -44,8 +43,7 @@ static int sof_test_firmware_file(struct device *dev, + break; + default: + dev_err(dev, "Invalid firmware magic: %#x\n", *magic); +- ret = -EINVAL; +- goto out; ++ return -EINVAL; + } + + if (ipc_type_to_adjust) { +@@ -54,13 +52,10 @@ static int sof_test_firmware_file(struct device *dev, + dev_err(dev, + "ipc type mismatch between %s and expected: %d vs %d\n", + fw_filename, fw_ipc_type, profile->ipc_type); +- ret = -EINVAL; ++ return -EINVAL; + } +-out: +- release_firmware(fw); +- kfree(fw_filename); + +- return ret; ++ return 0; + } + + static int sof_test_topology_file(struct device *dev, +diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c +--- a/sound/soc/sof/intel/hda-ctrl.c ++++ b/sound/soc/sof/intel/hda-ctrl.c +@@ -186,6 +186,7 @@ EXPORT_SYMBOL_NS(hda_dsp_ctrl_clock_power_gating, "SND_SOC_SOF_INTEL_HDA_COMMON" + int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec) + { + struct hdac_bus *bus = sof_to_bus(sdev); ++ struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); + struct hdac_stream *stream; + int sd_offset, ret = 0; + u32 gctl; +@@ -193,6 +194,16 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec) + if (bus->chip_init) + return 0; + ++ /* ++ * The controller reset clears the ACE2+ link DMA stream allocation ++ * constraints; reset the masks to reflect this. ++ */ ++ memset(sof_hda->link_dma_active_sdw_mask, 0, ++ sizeof(sof_hda->link_dma_active_sdw_mask)); ++ memset(sof_hda->link_dma_active_multi_mask, 0, ++ sizeof(sof_hda->link_dma_active_multi_mask)); ++ sof_hda->link_dma_out_hda_used_mask = 0; ++ + hda_codec_set_codec_wakeup(sdev, true); + + hda_dsp_ctrl_misc_clock_gating(sdev, false); +@@ -223,6 +234,14 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec) + /* Accept unsolicited responses */ + snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL); + ++ /* Perform a one-time enumeration of the Multi-Link capability */ ++ ret = hda_bus_ml_init(bus); ++ if (ret < 0) { ++ dev_err(sdev->dev, "%s: failed to enumerate multi-links\n", ++ __func__); ++ goto err; ++ } ++ + if (detect_codec) + hda_codec_detect_mask(sdev); + +diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c +--- a/sound/soc/sof/intel/hda-dai-ops.c ++++ b/sound/soc/sof/intel/hda-dai-ops.c +@@ -20,7 +20,7 @@ + /* These ops are only applicable for the HDA DAI's in their current form */ + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK) + /* +- * This function checks if the host dma channel corresponding ++ * This function checks if the host DMA stream corresponding + * to the link DMA stream_tag argument is assigned to one + * of the FEs connected to the BE DAI. + */ +@@ -42,23 +42,53 @@ static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd, + } + + static struct hdac_ext_stream * +-hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream) ++hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream, ++ enum hda_bus_ml_link_type link_type) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); + struct sof_intel_hda_stream *hda_stream; + const struct sof_intel_dsp_desc *chip; + struct snd_sof_dev *sdev; + struct hdac_ext_stream *res = NULL; + struct hdac_stream *hstream = NULL; +- + int stream_dir = substream->stream; ++ bool is_multi = link_type == HDA_BUS_ML_LINK_HDA || link_type == HDA_BUS_ML_LINK_UAOL; ++ bool is_play = stream_dir == SNDRV_PCM_STREAM_PLAYBACK; ++ bool is_sdw = link_type == HDA_BUS_ML_LINK_SDW; ++ bool is_hda = link_type == HDA_BUS_ML_LINK_HDA; ++ u32 concur_block_mask = 0; ++ u32 seq_block_mask = 0; ++ unsigned int stream_idx; + + if (!bus->ppcap) { + dev_err(bus->dev, "stream type not supported\n"); + return NULL; + } + ++ /* ++ * On ACE2+ the link DMA stream allocator must avoid two HW errata, ++ * see the comment on struct sof_intel_hda_dev. ++ * ++ * - Concurrent cross-direction: SoundWire conflicts with HDA, iDisp ++ * and UAOL on the same physical stream index; SSP and DMIC are safe. ++ * - Sequential playback: a stream index previously used by an HDA/iDisp ++ * link cannot drive any non-HDA/iDisp link in the same direction ++ * until the next controller reset. ++ * ++ * The masks are protected by bus->reg_lock; sample them inside the ++ * lock together with the stream walk to keep the decision atomic ++ * with concurrent allocations and releases. ++ */ + guard(spinlock_irq)(&bus->reg_lock); ++ ++ if (is_sdw) ++ concur_block_mask = sof_hda->link_dma_active_multi_mask[!stream_dir]; ++ else if (is_multi) ++ concur_block_mask = sof_hda->link_dma_active_sdw_mask[!stream_dir]; ++ if (is_play && !is_hda) ++ seq_block_mask = sof_hda->link_dma_out_hda_used_mask; ++ + list_for_each_entry(hstream, &bus->stream_list, list) { + struct hdac_ext_stream *hext_stream = + stream_to_hdac_ext_stream(hstream); +@@ -69,6 +99,12 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream + sdev = hda_stream->sdev; + chip = get_chip_info(sdev->pdata); + ++ stream_idx = hstream->stream_tag - 1; ++ ++ /* skip streams blocked by the ACE2+ allocator constraints */ ++ if ((concur_block_mask | seq_block_mask) & BIT(stream_idx)) ++ continue; ++ + /* check if link is available */ + if (!hext_stream->link_locked) { + /* +@@ -95,7 +131,7 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream + + /* + * This must be a hostless stream. +- * So reserve the host DMA channel. ++ * So reserve the host DMA stream. + */ + hda_stream->host_reserved = 1; + break; +@@ -109,6 +145,16 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream + + res->link_locked = 1; + res->link_substream = substream; ++ ++ stream_idx = res->hstream.stream_tag - 1; ++ if (is_sdw) ++ sof_hda->link_dma_active_sdw_mask[stream_dir] |= BIT(stream_idx); ++ else if (is_multi) ++ sof_hda->link_dma_active_multi_mask[stream_dir] |= BIT(stream_idx); ++ ++ /* persistent OUT HDA/iDisp shadow, cleared only on CRST# */ ++ if (is_hda && is_play) ++ sof_hda->link_dma_out_hda_used_mask |= BIT(stream_idx); + } + + return res; +@@ -143,11 +189,13 @@ static struct hdac_ext_stream *hda_ipc4_get_hext_stream(struct snd_sof_dev *sdev + + static struct hdac_ext_stream *hda_assign_hext_stream(struct snd_sof_dev *sdev, + struct snd_soc_dai *cpu_dai, +- struct snd_pcm_substream *substream) ++ struct snd_pcm_substream *substream, ++ struct hdac_ext_link *hlink) + { + struct hdac_ext_stream *hext_stream; ++ enum hda_bus_ml_link_type link_type = hda_bus_ml_link_get_type(hlink); + +- hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream); ++ hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream, link_type); + if (!hext_stream) + return NULL; + +@@ -160,6 +208,22 @@ static void hda_release_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai + struct snd_pcm_substream *substream) + { + struct hdac_ext_stream *hext_stream = hda_get_hext_stream(sdev, cpu_dai, substream); ++ struct sof_intel_hda_dev *sof_hda = sdev->pdata->hw_pdata; ++ struct hdac_bus *bus = sof_to_bus(sdev); ++ int dir = substream->stream; ++ unsigned int stream_idx = hext_stream->hstream.stream_tag - 1; ++ ++ /* ++ * Drop the stream index from the per-direction active concurrency masks. ++ * The two masks are mutually exclusive for a given stream/direction ++ * (and a stream of the SSP/DMIC kind appears in neither), so a blind ++ * clear of both is safe and lets us avoid having to remember the ++ * link type at allocation time. ++ */ ++ scoped_guard(spinlock_irq, &bus->reg_lock) { ++ sof_hda->link_dma_active_sdw_mask[dir] &= ~BIT(stream_idx); ++ sof_hda->link_dma_active_multi_mask[dir] &= ~BIT(stream_idx); ++ } + + snd_soc_dai_set_dma_data(cpu_dai, substream, NULL); + snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK); +diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c +--- a/sound/soc/sof/intel/hda-dai.c ++++ b/sound/soc/sof/intel/hda-dai.c +@@ -188,7 +188,7 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, + + if (!hext_stream) { + if (ops->assign_hext_stream) +- hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream); ++ hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream, hlink); + } + + if (!hext_stream) +diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c +--- a/sound/soc/sof/intel/hda-dsp.c ++++ b/sound/soc/sof/intel/hda-dsp.c +@@ -1114,11 +1114,7 @@ static int hda_dsp_s5_quirk(struct snd_sof_dev *sdev) + usleep_range(500, 1000); + + /* Restore state for shutdown, back to reset */ +- ret = hda_dsp_ctrl_link_reset(sdev, true); +- if (ret < 0) +- return ret; +- +- return ret; ++ return hda_dsp_ctrl_link_reset(sdev, true); + } + + int hda_dsp_shutdown_dma_flush(struct snd_sof_dev *sdev) +diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c +--- a/sound/soc/sof/intel/hda-loader.c ++++ b/sound/soc/sof/intel/hda-loader.c +@@ -216,9 +216,10 @@ int hda_cl_trigger(struct device *dev, struct hdac_ext_stream *hext_stream, int + EXPORT_SYMBOL_NS(hda_cl_trigger, "SND_SOC_SOF_INTEL_HDA_COMMON"); + + int hda_cl_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream) ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool is_iccmax) + { +- return hda_data_stream_cleanup(dev, dmab, persistent_buffer, hext_stream, false); ++ return hda_data_stream_cleanup(dev, dmab, persistent_buffer, hext_stream, ++ is_iccmax, false); + } + EXPORT_SYMBOL_NS(hda_cl_cleanup, "SND_SOC_SOF_INTEL_HDA_COMMON"); + +@@ -302,7 +303,7 @@ int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev) + * If the cleanup also fails, we return the initial error + */ + ret1 = hda_cl_cleanup(sdev->dev, &hda->iccmax_dmab, +- persistent_cl_buffer, iccmax_stream); ++ persistent_cl_buffer, iccmax_stream, true); + if (ret1 < 0) { + dev_err(sdev->dev, "error: ICCMAX stream cleanup failed\n"); + +@@ -458,7 +459,7 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) + * If the cleanup also fails, we return the initial error + */ + ret1 = hda_cl_cleanup(sdev->dev, &hda->cl_dmab, +- persistent_cl_buffer, hext_stream); ++ persistent_cl_buffer, hext_stream, false); + if (ret1 < 0) { + dev_err(sdev->dev, "error: Code loader DSP cleanup failed\n"); + +@@ -587,7 +588,7 @@ int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev, + cleanup: + /* clean up even in case of error and return the first error */ + ret1 = hda_cl_cleanup(sdev->dev, &hda->cl_dmab, persistent_cl_buffer, +- hext_stream); ++ hext_stream, false); + if (ret1 < 0) { + dev_err(sdev->dev, "%s: Code loader DSP cleanup failed\n", __func__); + +diff --git a/sound/soc/sof/intel/hda-mlink.c b/sound/soc/sof/intel/hda-mlink.c +--- a/sound/soc/sof/intel/hda-mlink.c ++++ b/sound/soc/sof/intel/hda-mlink.c +@@ -432,6 +432,10 @@ int hda_bus_ml_init(struct hdac_bus *bus) + if (!bus->mlcap) + return 0; + ++ /* Enumeration is a one time operation, skip if already done */ ++ if (!list_empty(&bus->hlink_list)) ++ return 0; ++ + link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1; + + dev_dbg(bus->dev, "HDAudio Multi-Link count: %d\n", link_count); +@@ -880,19 +884,6 @@ int hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y, + return 0; + } EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_map_stream_ch, "SND_SOC_SOF_HDA_MLINK"); + +-void hda_bus_ml_put_all(struct hdac_bus *bus) +-{ +- struct hdac_ext_link *hlink; +- +- list_for_each_entry(hlink, &bus->hlink_list, list) { +- struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink); +- +- if (!h2link->alt) +- snd_hdac_ext_bus_link_put(bus, hlink); +- } +-} +-EXPORT_SYMBOL_NS(hda_bus_ml_put_all, "SND_SOC_SOF_HDA_MLINK"); +- + void hda_bus_ml_reset_losidv(struct hdac_bus *bus) + { + struct hdac_ext_link *hlink; +@@ -903,6 +894,24 @@ void hda_bus_ml_reset_losidv(struct hdac_bus *bus) + } + EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, "SND_SOC_SOF_HDA_MLINK"); + ++enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink) ++{ ++ struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink); ++ ++ if (!h2link->alt) ++ return HDA_BUS_ML_LINK_HDA; ++ ++ switch (h2link->elid) { ++ case AZX_REG_ML_LEPTR_ID_SDW: ++ return HDA_BUS_ML_LINK_SDW; ++ case AZX_REG_ML_LEPTR_ID_INTEL_UAOL: ++ return HDA_BUS_ML_LINK_UAOL; ++ default: ++ return HDA_BUS_ML_LINK_OTHER; ++ } ++} ++EXPORT_SYMBOL_NS(hda_bus_ml_link_get_type, "SND_SOC_SOF_HDA_MLINK"); ++ + int hda_bus_ml_resume(struct hdac_bus *bus) + { + struct hdac_ext_link *hlink; +diff --git a/sound/soc/sof/intel/hda-sdw-bpt.c b/sound/soc/sof/intel/hda-sdw-bpt.c +--- a/sound/soc/sof/intel/hda-sdw-bpt.c ++++ b/sound/soc/sof/intel/hda-sdw-bpt.c +@@ -163,7 +163,7 @@ static int hda_sdw_bpt_dma_deprepare(struct device *dev, struct hdac_ext_stream + u32 mask; + int ret; + +- ret = hda_data_stream_cleanup(sdev->dev, dmab_bdl, false, sdw_bpt_stream, true); ++ ret = hda_data_stream_cleanup(sdev->dev, dmab_bdl, false, sdw_bpt_stream, false, true); + if (ret < 0) { + dev_err(sdev->dev, "%s: SDW BPT DMA cleanup failed\n", + __func__); +diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c +--- a/sound/soc/sof/intel/hda-stream.c ++++ b/sound/soc/sof/intel/hda-stream.c +@@ -198,13 +198,18 @@ int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, + + mask = (1 << hstream->index); + ++ /* Reset the spib_addr before disabling SPIB */ ++ if (!enable) ++ sof_io_write(sdev, hstream->spib_addr, 0); ++ + /* enable/disable SPIB for the stream */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_SPIB_BAR, + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL, mask, + enable << hstream->index); + + /* set the SPIB value */ +- sof_io_write(sdev, hstream->spib_addr, size); ++ if (enable) ++ sof_io_write(sdev, hstream->spib_addr, size); + + return 0; + } +@@ -608,6 +613,9 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, + return ret; + } + ++ /* Host DMA is not running */ ++ hstream->running = false; ++ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_SD_STS, + SOF_HDA_CL_DMA_SD_INT_MASK, +@@ -1323,16 +1331,18 @@ hda_data_stream_prepare(struct device *dev, unsigned int format, unsigned int si + EXPORT_SYMBOL_NS(hda_data_stream_prepare, "SND_SOC_SOF_INTEL_HDA_COMMON"); + + int hda_data_stream_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool pair) ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, ++ bool is_iccmax, bool pair) + { + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct hdac_stream *hstream = hdac_stream(hext_stream); + int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + int ret = 0; + +- if (hstream->direction == SNDRV_PCM_STREAM_PLAYBACK) ++ if (!is_iccmax) + ret = hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_DISABLE, 0); +- else ++ ++ if (hstream->direction == SNDRV_PCM_STREAM_CAPTURE) + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, + SOF_HDA_SD_CTL_DMA_START, 0); + +diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c +--- a/sound/soc/sof/intel/hda.c ++++ b/sound/soc/sof/intel/hda.c +@@ -421,6 +421,20 @@ static inline void hda_dsp_sdw_process_mic_privacy(struct snd_sof_dev *sdev) { } + /* pre fw run operations */ + int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev) + { ++ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; ++ const struct sof_intel_dsp_desc *chip = hda->desc; ++ int ret; ++ ++ /* Power down DSP if left enabled to ensure a clean boot state. */ ++ if (hda_dsp_core_is_enabled(sdev, chip->host_managed_cores_mask)) { ++ dev_dbg(sdev->dev, "DSP core enabled, power down DSP first\n"); ++ ++ ret = chip->power_down_dsp(sdev); ++ if (ret < 0) ++ dev_warn(sdev->dev, ++ "%s: failed to power down already-enabled DSP\n", __func__); ++ } ++ + /* disable clock gating and power gating */ + return hda_dsp_ctrl_clock_power_gating(sdev, false); + } +@@ -625,8 +639,6 @@ static int hda_init_caps(struct snd_sof_dev *sdev) + return ret; + } + +- hda_bus_ml_init(bus); +- + /* Skip SoundWire if it is not supported */ + if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH))) + goto skip_soundwire; +@@ -670,8 +682,6 @@ static int hda_init_caps(struct snd_sof_dev *sdev) + if (!HDA_IDISP_CODEC(bus->codec_mask)) + hda_codec_i915_display_power(sdev, false); + +- hda_bus_ml_put_all(bus); +- + return 0; + } + +diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h +--- a/sound/soc/sof/intel/hda.h ++++ b/sound/soc/sof/intel/hda.h +@@ -523,6 +523,29 @@ struct sof_intel_hda_dev { + /* the maximum number of streams (playback + capture) supported */ + u32 stream_max; + ++ /* ++ * ACE2+ link DMA stream allocation constraints (stream index = ++ * stream_tag - 1, shared between input and output directions). All ++ * masks are cleared by hda_dsp_ctrl_init_chip() on controller reset ++ * (CRST#). ++ * ++ * - Concurrent (cross-direction) constraint: a SoundWire stream and ++ * a HDA/iDisp/UAOL stream cannot share a physical stream index ++ * across directions, the resulting LLP/timestamp values are wrong. ++ * link_dma_active_sdw_mask and link_dma_active_multi_mask ++ * (indexed by SNDRV_PCM_STREAM_*) track currently allocated ++ * streams per direction in each of the conflicting groups; SSP ++ * and DMIC do not participate. Bits are cleared on stream release. ++ * ++ * - Sequential (playback only) constraint: once a HDA/iDisp link ++ * has used a playback stream index, that index cannot drive a ++ * non-HDA/iDisp link in the same direction until the next CRST#. ++ * link_dma_out_hda_used_mask records this. ++ */ ++ u32 link_dma_active_sdw_mask[SNDRV_PCM_STREAM_LAST + 1]; ++ u32 link_dma_active_multi_mask[SNDRV_PCM_STREAM_LAST + 1]; ++ u32 link_dma_out_hda_used_mask; ++ + /* PM related */ + bool l1_disabled;/* is DMI link L1 disabled? */ + +@@ -739,7 +762,7 @@ struct hdac_ext_stream *hda_cl_prepare(struct device *dev, unsigned int format, + int hda_cl_trigger(struct device *dev, struct hdac_ext_stream *hext_stream, int cmd); + + int hda_cl_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream); ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool is_iccmax); + int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot); + #define HDA_CL_STREAM_FORMAT 0x40 + +@@ -911,7 +934,8 @@ hda_data_stream_prepare(struct device *dev, unsigned int format, unsigned int si + bool is_iccmax, bool pair); + + int hda_data_stream_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool pair); ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, ++ bool is_iccmax, bool pair); + + /* common dai driver */ + extern struct snd_soc_dai_driver skl_dai[]; +@@ -1030,7 +1054,8 @@ struct hda_dai_widget_dma_ops { + struct snd_pcm_substream *substream); + struct hdac_ext_stream *(*assign_hext_stream)(struct snd_sof_dev *sdev, + struct snd_soc_dai *cpu_dai, +- struct snd_pcm_substream *substream); ++ struct snd_pcm_substream *substream, ++ struct hdac_ext_link *hlink); + void (*release_hext_stream)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream); + void (*setup_hext_stream)(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream, +diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c +--- a/sound/soc/sof/intel/icl.c ++++ b/sound/soc/sof/intel/icl.c +@@ -9,10 +9,14 @@ + * Hardware interface for audio DSP on IceLake. + */ + +-#include +-#include +-#include ++#include + #include ++#include ++#include ++#include ++#include ++#include ++ + #include "../ipc4-priv.h" + #include "../ops.h" + #include "hda.h" +diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c +--- a/sound/soc/sof/intel/mtl.c ++++ b/sound/soc/sof/intel/mtl.c +@@ -236,6 +236,17 @@ int mtl_enable_interrupts(struct snd_sof_dev *sdev, bool enable) + } + EXPORT_SYMBOL_NS(mtl_enable_interrupts, "SND_SOC_SOF_INTEL_MTL"); + ++static bool mtl_dsp_is_enabled(struct snd_sof_dev *sdev) ++{ ++ int val; ++ ++ val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFDSSCS); ++ if (val & MTL_HFDSSCS_CPA_MASK) ++ return true; ++ ++ return false; ++} ++ + /* pre fw run operations */ + static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev) + { +@@ -249,6 +260,18 @@ static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev) + u32 dsppwrsts; + const struct sof_intel_dsp_desc *chip; + ++ /* Power down the DSP if it is left enabled to ensure clean boot state */ ++ if (mtl_dsp_is_enabled(sdev)) { ++ dev_dbg(sdev->dev, "powering down DSP first\n"); ++ ++ ret = mtl_power_down_dsp(sdev); ++ if (ret < 0) { ++ dev_warn(sdev->dev, ++ "%s: failed to power down already-enabled DSP\n", __func__); ++ /* Continue anyway to attempt recovery */ ++ } ++ } ++ + chip = get_chip_info(sdev->pdata); + if (chip->hw_ip_version > SOF_INTEL_ACE_2_0) { + dsppwrctl = PTL_HFPWRCTL2; +diff --git a/sound/soc/sof/ipc3-pcm.c b/sound/soc/sof/ipc3-pcm.c +--- a/sound/soc/sof/ipc3-pcm.c ++++ b/sound/soc/sof/ipc3-pcm.c +@@ -421,6 +421,12 @@ static int sof_ipc3_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, + dev_dbg(component->dev, "AMD_SDW channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; ++ case SOF_DAI_AMD_I2S: ++ rate->min = private->dai_config->acp_i2s.fsync_rate; ++ rate->max = private->dai_config->acp_i2s.fsync_rate; ++ channels->min = private->dai_config->acp_i2s.tdm_slots; ++ channels->max = private->dai_config->acp_i2s.tdm_slots; ++ break; + default: + dev_err(component->dev, "Invalid DAI type %d\n", private->dai_config->type); + break; +diff --git a/sound/soc/sof/ipc3-topology.c b/sound/soc/sof/ipc3-topology.c +--- a/sound/soc/sof/ipc3-topology.c ++++ b/sound/soc/sof/ipc3-topology.c +@@ -281,7 +281,10 @@ static const struct sof_topology_token acpdmic_tokens[] = { + offsetof(struct sof_ipc_dai_acpdmic_params, pdm_ch)}, + }; + +-/* ACPI2S */ ++/* ++ * ACPI2S tokens fill struct sof_ipc_dai_acp_params; SOF_DAI_AMD_I2S (ACPTDM ++ * on ACP7.B/7.F) reuses this tuple group rather than defining a parallel set. ++ */ + static const struct sof_topology_token acpi2s_tokens[] = { + {SOF_TKN_AMD_ACPI2S_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_acp_params, fsync_rate)}, +@@ -289,6 +292,8 @@ static const struct sof_topology_token acpi2s_tokens[] = { + offsetof(struct sof_ipc_dai_acp_params, tdm_slots)}, + {SOF_TKN_AMD_ACPI2S_TDM_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_acp_params, tdm_mode)}, ++ {SOF_TKN_AMD_ACPI2S_FORMAT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, ++ offsetof(struct sof_ipc_dai_acp_params, format)}, + }; + + /* MICFIL PDM */ +@@ -1363,6 +1368,35 @@ static int sof_link_acp_sdw_load(struct snd_soc_component *scomp, struct snd_sof + return 0; + } + ++static int sof_link_acp_i2s_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, ++ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) ++{ ++ struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs; ++ struct sof_dai_private_data *private = dai->private; ++ u32 size = sizeof(*config); ++ int ret; ++ ++ /* handle master/slave and inverted clocks */ ++ sof_dai_set_format(hw_config, config); ++ ++ /* init IPC */ ++ memset(&config->acp_i2s, 0, sizeof(config->acp_i2s)); ++ config->hdr.size = size; ++ ++ ret = sof_update_ipc_object(scomp, &config->acp_i2s, SOF_ACPI2S_TOKENS, slink->tuples, ++ slink->num_tuples, size, slink->num_hw_configs); ++ if (ret < 0) ++ return ret; ++ ++ dai->number_configs = 1; ++ dai->current_config = 0; ++ private->dai_config = kmemdup(config, size, GFP_KERNEL); ++ if (!private->dai_config) ++ return -ENOMEM; ++ ++ return 0; ++} ++ + static int sof_link_afe_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) + { +@@ -1692,6 +1726,9 @@ static int sof_ipc3_widget_setup_comp_dai(struct snd_sof_widget *swidget) + case SOF_DAI_AMD_SDW: + ret = sof_link_acp_sdw_load(scomp, slink, config, dai); + break; ++ case SOF_DAI_AMD_I2S: ++ ret = sof_link_acp_i2s_load(scomp, slink, config, dai); ++ break; + default: + break; + } +diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c +--- a/sound/soc/sof/ipc4-topology.c ++++ b/sound/soc/sof/ipc4-topology.c +@@ -163,11 +163,11 @@ static const struct sof_topology_token comp_ext_tokens[] = { + {SOF_TKN_COMP_SCHED_DOMAIN, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_domain, + offsetof(struct snd_sof_widget, comp_domain)}, + {SOF_TKN_COMP_DOMAIN_ID, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, +- offsetof(struct snd_sof_widget, dp_domain_id)}, ++ offsetof(struct snd_sof_widget, domain_id)}, + {SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, +- offsetof(struct snd_sof_widget, dp_heap_bytes)}, ++ offsetof(struct snd_sof_widget, heap_bytes)}, + {SOF_TKN_COMP_STACK_BYTES_REQUIREMENT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, +- offsetof(struct snd_sof_widget, dp_stack_bytes)}, ++ offsetof(struct snd_sof_widget, stack_bytes)}, + }; + + static const struct sof_topology_token gain_tokens[] = { +@@ -406,6 +406,39 @@ sof_ipc4_get_input_pin_audio_fmt(struct snd_sof_widget *swidget, int pin_index) + return NULL; + } + ++static void ++sof_ipc4_evaluate_params_change(struct sof_ipc4_available_audio_format *available_fmt) ++{ ++ struct sof_ipc4_audio_format *fmt; ++ u32 in_rate, in_channels, in_valid_bits; ++ u32 out_rate, out_channels, out_valid_bits; ++ u32 changed_params = 0; ++ int i, j; ++ ++ for (i = 0; i < available_fmt->num_input_formats; i++) { ++ fmt = &available_fmt->input_pin_fmts[i].audio_fmt; ++ in_rate = fmt->sampling_frequency; ++ in_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); ++ in_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); ++ ++ for (j = 0; j < available_fmt->num_output_formats; j++) { ++ fmt = &available_fmt->output_pin_fmts[j].audio_fmt; ++ out_rate = fmt->sampling_frequency; ++ out_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); ++ out_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); ++ ++ if (in_rate != out_rate) ++ changed_params |= BIT(SNDRV_PCM_HW_PARAM_RATE); ++ if (in_channels != out_channels) ++ changed_params |= BIT(SNDRV_PCM_HW_PARAM_CHANNELS); ++ if (in_valid_bits != out_valid_bits) ++ changed_params |= BIT(SNDRV_PCM_HW_PARAM_FORMAT); ++ } ++ } ++ ++ available_fmt->changed_params = changed_params; ++} ++ + /** + * sof_ipc4_get_audio_fmt - get available audio formats from swidget->tuples + * @scomp: pointer to pointer to SOC component +@@ -497,6 +530,8 @@ static int sof_ipc4_get_audio_fmt(struct snd_soc_component *scomp, + available_fmt->num_output_formats); + } + ++ sof_ipc4_evaluate_params_change(available_fmt); ++ + return 0; + + err_out: +@@ -661,6 +696,9 @@ static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget) + if (ret) + goto free_copier; + ++ /* Copier can only change format */ ++ available_fmt->changed_params &= BIT(SNDRV_PCM_HW_PARAM_FORMAT); ++ + /* + * This callback is used by host copier and module-to-module copier, + * and only host copier needs to set gtw_cfg. +@@ -789,6 +827,9 @@ static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget) + if (ret) + goto free_copier; + ++ /* Copier can only change format */ ++ available_fmt->changed_params &= BIT(SNDRV_PCM_HW_PARAM_FORMAT); ++ + ret = sof_update_ipc_object(scomp, &node_type, + SOF_COPIER_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(node_type), 1); +@@ -1127,6 +1168,7 @@ static int sof_ipc4_widget_setup_comp_src(struct snd_sof_widget *swidget) + "Invalid number of formats: input: %d, output: %d\n", + src->available_fmt.num_input_formats, + src->available_fmt.num_output_formats); ++ ret = -EINVAL; + goto err; + } + +@@ -1179,6 +1221,7 @@ static int sof_ipc4_widget_setup_comp_asrc(struct snd_sof_widget *swidget) + "Invalid number of formats: input: %d, output: %d\n", + asrc->available_fmt.num_input_formats, + asrc->available_fmt.num_output_formats); ++ ret = -EINVAL; + goto err; + } + +@@ -1369,6 +1412,22 @@ sof_ipc4_update_resource_usage(struct snd_sof_dev *sdev, struct snd_sof_widget * + pipeline = pipe_widget->private; + pipeline->mem_usage += total; + ++ /* ++ * If this is not a Data Processing module instance, add the ++ * required heap sizes to the sum of all module instances belonging ++ * to the same pipeline, and find the maximum stack requirement ++ * among all module instances belonging to the same pipeline. ++ */ ++ if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) { ++ pipe_widget->heap_bytes += swidget->heap_bytes; ++ if (pipe_widget->stack_bytes < swidget->stack_bytes) ++ pipe_widget->stack_bytes = swidget->stack_bytes; ++ ++ dev_dbg(sdev->dev, "%s mem reqs to %s heap %u stack %u", ++ swidget->widget->name, pipe_widget->widget->name, ++ pipe_widget->heap_bytes, pipe_widget->stack_bytes); ++ } ++ + /* Update base_config->cpc from the module manifest */ + sof_ipc4_update_cpc_from_manifest(sdev, fw_module, base_config); + +@@ -1686,6 +1745,8 @@ static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget) + pipe_widget = swidget->spipe->pipe_widget; + pipeline = pipe_widget->private; + pipeline->mem_usage = 0; ++ pipe_widget->heap_bytes = 0; ++ pipe_widget->stack_bytes = 0; + + if (WIDGET_IS_AIF(swidget->id) || swidget->id == snd_soc_dapm_buffer) { + if (pipeline->use_chain_dma) { +@@ -2063,10 +2124,57 @@ static void sof_ipc4_host_config(struct snd_sof_dev *sdev, struct snd_sof_widget + } + + static int +-sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, +- struct snd_pcm_hw_params *fe_params, +- struct snd_sof_platform_stream_params *platform_params, +- struct snd_pcm_hw_params *pipeline_params, int dir) ++sof_ipc4_copier_module_update_params(struct snd_sof_widget *swidget, ++ struct snd_pcm_hw_params *pipeline_params) ++{ ++ struct snd_soc_component *scomp = swidget->scomp; ++ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); ++ struct sof_ipc4_copier_data *copier_data; ++ struct sof_ipc4_copier *ipc4_copier; ++ ++ switch (swidget->id) { ++ case snd_soc_dapm_aif_in: ++ case snd_soc_dapm_aif_out: ++ case snd_soc_dapm_buffer: ++ ipc4_copier = swidget->private; ++ copier_data = &ipc4_copier->data; ++ break; ++ case snd_soc_dapm_dai_in: ++ case snd_soc_dapm_dai_out: ++ { ++ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; ++ struct sof_ipc4_pipeline *pipeline = pipe_widget->private; ++ struct snd_sof_dai *dai; ++ ++ if (pipeline->use_chain_dma) ++ return 0; ++ ++ dai = swidget->private; ++ ++ ipc4_copier = (struct sof_ipc4_copier *)dai->private; ++ copier_data = &ipc4_copier->data; ++ ++ break; ++ } ++ default: ++ dev_err(sdev->dev, "unsupported type %d for copier %s", ++ swidget->id, swidget->widget->name); ++ return -EINVAL; ++ } ++ ++ /* modify the input params for the next widget */ ++ return sof_ipc4_update_hw_params(sdev, pipeline_params, ++ &copier_data->out_format, ++ BIT(SNDRV_PCM_HW_PARAM_FORMAT) | ++ BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | ++ BIT(SNDRV_PCM_HW_PARAM_RATE)); ++} ++ ++static int ++_sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, ++ struct snd_pcm_hw_params *fe_params, ++ struct snd_sof_platform_stream_params *platform_params, ++ struct snd_pcm_hw_params *pipeline_params, int dir) + { + struct sof_ipc4_available_audio_format *available_fmt; + struct snd_soc_component *scomp = swidget->scomp; +@@ -2538,6 +2646,21 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, + return 0; + } + ++static int ++sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, ++ struct snd_pcm_hw_params *fe_params, ++ struct snd_sof_platform_stream_params *platform_params, ++ struct snd_pcm_hw_params *pipeline_params, int dir) ++{ ++ if (swidget->prepared) ++ return sof_ipc4_copier_module_update_params(swidget, ++ pipeline_params); ++ ++ return _sof_ipc4_prepare_copier_module(swidget, fe_params, ++ platform_params, pipeline_params, ++ dir); ++} ++ + static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, +@@ -2551,6 +2674,10 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; + int input_fmt_index, output_fmt_index; + ++ /* This cannot happen */ ++ if (unlikely(swidget->prepared)) ++ return 0; ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &gain->data.base_config, + pipeline_params, +@@ -2596,6 +2723,10 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; + int input_fmt_index, output_fmt_index; + ++ /* Already prepared, nothing to do */ ++ if (swidget->prepared) ++ return 0; ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &mixer->base_config, + pipeline_params, +@@ -2642,6 +2773,10 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; + int output_fmt_index, input_fmt_index; + ++ /* This cannot happen */ ++ if (unlikely(swidget->prepared)) ++ return 0; ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &src->data.base_config, + pipeline_params, +@@ -2808,6 +2943,18 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + int ret; + + if (available_fmt->num_input_formats) { ++ if (swidget->prepared) { ++ if (!available_fmt->num_output_formats) ++ return 0; ++ ++ /* modify the pipeline params with the output format */ ++ return sof_ipc4_update_hw_params(sdev, pipeline_params, ++ &process->output_format, ++ BIT(SNDRV_PCM_HW_PARAM_FORMAT) | ++ BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | ++ BIT(SNDRV_PCM_HW_PARAM_RATE)); ++ } ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &process->base_config, + pipeline_params, +@@ -2820,39 +2967,69 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + if (available_fmt->num_output_formats) { + struct sof_ipc4_audio_format *in_fmt; + struct sof_ipc4_pin_format *pin_fmt; +- u32 out_ref_rate, out_ref_channels; +- int out_ref_valid_bits, out_ref_type; ++ u32 ref_rate, ref_channels; ++ int ref_valid_bits, ref_type; + + if (available_fmt->num_input_formats) { ++ /* ++ * The process module can change parameters and their operation ++ * depends on the direction: ++ * Playback: typically they have single output format. This is ++ * to 'force' the conversion from input to output. ++ * Use the input format as reference since the single ++ * format is going to be picked. ++ * Capture: typically they have multiple output formats to ++ * convert from dai (input) to FE (output) parameters. ++ * Use the input format as base and replace the param ++ * which is changed by the module with the FE parameter ++ * Reason: we can have module which changes the ++ * parameters in path, we cannot use the full ++ * FE param set for the module output lookup. ++ */ + in_fmt = &available_fmt->input_pin_fmts[input_fmt_index].audio_fmt; + +- out_ref_rate = in_fmt->sampling_frequency; +- out_ref_channels = ++ ref_rate = in_fmt->sampling_frequency; ++ ref_channels = + SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); +- out_ref_valid_bits = ++ ref_valid_bits = + SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); +- out_ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); ++ ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); + } else { + /* for modules without input formats, use FE params as reference */ +- out_ref_rate = params_rate(fe_params); +- out_ref_channels = params_channels(fe_params); ++ ref_rate = params_rate(fe_params); ++ ref_channels = params_channels(fe_params); + ret = sof_ipc4_get_sample_type(sdev, fe_params); + if (ret < 0) + return ret; +- out_ref_type = (u32)ret; ++ ref_type = (u32)ret; + +- out_ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); +- if (out_ref_valid_bits < 0) +- return out_ref_valid_bits; ++ ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); ++ if (ref_valid_bits < 0) ++ return ref_valid_bits; + } + ++ if (dir == SNDRV_PCM_STREAM_CAPTURE) { ++ if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_RATE)) ++ ref_rate = params_rate(fe_params); ++ if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_CHANNELS)) ++ ref_channels = params_channels(fe_params); ++ if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_FORMAT)) { ++ ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); ++ if (ref_valid_bits < 0) ++ return ref_valid_bits; ++ ++ ref_type = sof_ipc4_get_sample_type(sdev, fe_params); ++ if (ref_type < 0) ++ return ref_type; ++ } ++ } + output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, + &process->base_config, + available_fmt, +- out_ref_rate, +- out_ref_channels, +- out_ref_valid_bits, +- out_ref_type); ++ ref_rate, ++ ref_channels, ++ ref_valid_bits, ++ ref_type); + if (output_fmt_index < 0) + return output_fmt_index; + +@@ -2866,9 +3043,7 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + /* modify the pipeline params with the output format */ + ret = sof_ipc4_update_hw_params(sdev, pipeline_params, + &process->output_format, +- BIT(SNDRV_PCM_HW_PARAM_FORMAT) | +- BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | +- BIT(SNDRV_PCM_HW_PARAM_RATE)); ++ available_fmt->changed_params); + if (ret) + return ret; + } +@@ -3083,27 +3258,47 @@ static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_contr + return 0; + } + +-static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, +- struct snd_sof_widget *swidget, +- struct sof_ipc4_msg *msg, +- void *ipc_data, u32 ipc_size, +- void **new_data) ++static void sof_ipc4_add_init_ext_dp_memory_data(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ u32 *payload, u32 *ext_pos, ++ struct sof_ipc4_module_init_ext_object **hdr) ++{ ++ /* Add memory_data if comp_domain indicates DP */ ++ if (swidget->comp_domain == SOF_COMP_DOMAIN_DP) { ++ struct sof_ipc4_mod_init_ext_dp_memory_data *dp_mem_data; ++ ++ *hdr = (struct sof_ipc4_module_init_ext_object *)&payload[*ext_pos]; ++ (*hdr)->header = ++ SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA) | ++ SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*dp_mem_data), ++ sizeof(u32))); ++ *ext_pos += DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)); ++ dp_mem_data = (struct sof_ipc4_mod_init_ext_dp_memory_data *)&payload[*ext_pos]; ++ dp_mem_data->domain_id = swidget->domain_id; ++ dp_mem_data->stack_bytes = swidget->stack_bytes; ++ dp_mem_data->heap_bytes = swidget->heap_bytes; ++ *ext_pos += DIV_ROUND_UP(sizeof(*dp_mem_data), sizeof(u32)); ++ } ++} ++ ++static int sof_ipc4_widget_mod_init_msg_payload(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ struct sof_ipc4_msg *msg, ++ void *ipc_data, u32 ipc_size, ++ void **new_data) + { +- struct sof_ipc4_mod_init_ext_dp_memory_data *dp_mem_data; + struct sof_ipc4_module_init_ext_init *ext_init; +- struct sof_ipc4_module_init_ext_object *hdr; ++ struct sof_ipc4_module_init_ext_object *hdr = NULL; + int new_size; + u32 *payload; + u32 ext_pos; + +- /* For the moment the only reason for adding init_ext_init payload is DP +- * memory data. If both stack and heap size are 0 (= use default), then +- * there is no need for init_ext_init payload. ++ /* ++ * Only DP widgets currently add init-ext objects here. Avoid allocating ++ * a max-sized payload buffer for widgets that will immediately return 0. + */ +- if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) { +- msg->extension &= ~SOF_IPC4_MOD_EXT_EXTENDED_INIT_MASK; ++ if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) + return 0; +- } + + payload = kzalloc(sdev->ipc->max_payload_size, GFP_KERNEL); + if (!payload) +@@ -3111,27 +3306,26 @@ static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, + + /* Add ext_init first and set objects array flag to 1 */ + ext_init = (struct sof_ipc4_module_init_ext_init *)payload; +- ext_init->word0 |= SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_MASK; + ext_pos = DIV_ROUND_UP(sizeof(*ext_init), sizeof(u32)); + + /* Add object array objects after ext_init */ + +- /* Add dp_memory_data if comp_domain indicates DP */ +- if (swidget->comp_domain == SOF_COMP_DOMAIN_DP) { +- hdr = (struct sof_ipc4_module_init_ext_object *)&payload[ext_pos]; +- hdr->header = SOF_IPC4_MOD_INIT_EXT_OBJ_LAST_MASK | +- SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA) | +- SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*dp_mem_data), +- sizeof(u32))); +- ext_pos += DIV_ROUND_UP(sizeof(*hdr), sizeof(u32)); +- dp_mem_data = (struct sof_ipc4_mod_init_ext_dp_memory_data *)&payload[ext_pos]; +- dp_mem_data->domain_id = swidget->dp_domain_id; +- dp_mem_data->stack_bytes = swidget->dp_stack_bytes; +- dp_mem_data->heap_bytes = swidget->dp_heap_bytes; +- ext_pos += DIV_ROUND_UP(sizeof(*dp_mem_data), sizeof(u32)); ++ sof_ipc4_add_init_ext_dp_memory_data(sdev, swidget, payload, &ext_pos, &hdr); ++ ++ /* Add following object array items here */ ++ ++ if (!hdr) { ++ /* ++ * NOTE: Remove this early bail out, when struct ++ * sof_ipc4_module_init_ext_init alone has some ++ * function. ++ */ ++ kfree(payload); ++ return 0; + } + +- /* If another array object is added, remember clear previous OBJ_LAST bit */ ++ ext_init->word0 |= SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_MASK; ++ hdr->header |= SOF_IPC4_MOD_INIT_EXT_OBJ_LAST_MASK; + + /* Calculate final size and check that it fits to max payload size */ + new_size = ext_pos * sizeof(u32) + ipc_size; +@@ -3154,6 +3348,69 @@ static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, + return new_size; + } + ++static void sof_ipc4_widget_pipe_ext_obj_memory_data(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ u32 *payload, u32 *ext_pos, ++ struct sof_ipc4_glb_pipe_ext_object **hdr) ++{ ++ struct sof_ipc4_glb_pipe_ext_obj_memory_data *mem_data; ++ ++ *hdr = (struct sof_ipc4_glb_pipe_ext_object *)&payload[*ext_pos]; ++ (*hdr)->header = ++ SOF_IPC4_GLB_PIPE_EXT_OBJ_ID(SOF_IPC4_GLB_PIPE_DATA_ID_MEM_DATA) | ++ SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*mem_data), ++ sizeof(u32))); ++ *ext_pos += DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)); ++ mem_data = (struct sof_ipc4_glb_pipe_ext_obj_memory_data *)&payload[*ext_pos]; ++ mem_data->domain_id = swidget->domain_id; ++ mem_data->stack_bytes = swidget->stack_bytes; ++ mem_data->heap_bytes = swidget->heap_bytes; ++ *ext_pos += DIV_ROUND_UP(sizeof(*mem_data), sizeof(u32)); ++ ++ dev_dbg(sdev->dev, ++ "%s; domain_id %u stack %u heap %u bytes", ++ swidget->widget->name, mem_data->domain_id, mem_data->stack_bytes, ++ mem_data->heap_bytes); ++} ++ ++static int sof_ipc4_widget_pipe_create_msg_payload(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ struct sof_ipc4_msg *msg, ++ void **new_data) ++{ ++ struct sof_ipc4_glb_pipe_payload *payload_hdr; ++ struct sof_ipc4_glb_pipe_ext_object *hdr = NULL; ++ u32 *payload; ++ u32 ext_pos; ++ ++ payload = kzalloc(sdev->ipc->max_payload_size, GFP_KERNEL); ++ if (!payload) ++ return -ENOMEM; ++ ++ /* Add sof_ipc4_glb_pipe_payload and set array bit to 1 */ ++ payload_hdr = (struct sof_ipc4_glb_pipe_payload *)payload; ++ payload_hdr->word0 |= SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_MASK; ++ ext_pos = DIV_ROUND_UP(sizeof(*payload_hdr), sizeof(u32)); ++ ++ sof_ipc4_widget_pipe_ext_obj_memory_data(sdev, swidget, payload, &ext_pos, &hdr); ++ /* Add following array objects here */ ++ ++ /* Mark end of object array */ ++ hdr->header |= SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_MASK; ++ ++ /* Put total payload size in words to the payload header */ ++ payload_hdr->word0 |= SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS(ext_pos); ++ *new_data = payload; ++ ++ /* Update msg extension bits according to the payload changes */ ++ msg->extension |= SOF_IPC4_GLB_PIPE_PAYLOAD_MASK; ++ ++ dev_dbg(sdev->dev, "%s: payload word0 %#x", swidget->widget->name, ++ payload_hdr->word0); ++ ++ return ext_pos * sizeof(int32_t); ++} ++ + static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) + { + struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; +@@ -3340,8 +3597,8 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget + swidget->widget->name, swidget->pipeline_id, module_id, + swidget->instance_id, swidget->core); + +- ret = sof_ipc4_widget_setup_msg_payload(sdev, swidget, msg, ipc_data, ipc_size, +- &ext_data); ++ ret = sof_ipc4_widget_mod_init_msg_payload(sdev, swidget, msg, ipc_data, ipc_size, ++ &ext_data); + if (ret < 0) + goto fail; + +@@ -3353,6 +3610,17 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget + dev_dbg(sdev->dev, "Create pipeline %s (pipe %d) - instance %d, core %d\n", + swidget->widget->name, swidget->pipeline_id, + swidget->instance_id, swidget->core); ++ ++ msg->extension &= ~SOF_IPC4_GLB_PIPE_PAYLOAD_MASK; ++ ret = sof_ipc4_widget_pipe_create_msg_payload(sdev, swidget, msg, ++ &ext_data); ++ if (ret < 0) ++ goto fail; ++ ++ if (ret > 0) { ++ ipc_size = ret; ++ ipc_data = ext_data; ++ } + } + + msg->data_size = ipc_size; +@@ -3410,6 +3678,8 @@ static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget + swidget->widget->name); + + pipeline->mem_usage = 0; ++ swidget->heap_bytes = 0; ++ swidget->stack_bytes = 0; + pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED; + ida_free(&pipeline_ida, swidget->instance_id); + swidget->instance_id = -EINVAL; +diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h +--- a/sound/soc/sof/ipc4-topology.h ++++ b/sound/soc/sof/ipc4-topology.h +@@ -167,7 +167,7 @@ struct sof_ipc4_pipeline { + }; + + /** +- * struct sof_ipc4_multi_pipeline_data - multi pipeline trigger IPC data ++ * struct ipc4_pipeline_set_state_data - multi pipeline trigger IPC data + * @count: Number of pipelines to be triggered + * @pipeline_instance_ids: Flexible array of IDs of the pipelines to be triggered + */ +@@ -197,12 +197,15 @@ struct sof_ipc4_pin_format { + * @input_pin_fmts: Available input pin formats + * @num_input_formats: Number of input pin formats + * @num_output_formats: Number of output pin formats ++ * @changed_params: Mask of changed params by the module instance between it's ++ * input and output formts (rate, channels, depth) + */ + struct sof_ipc4_available_audio_format { + struct sof_ipc4_pin_format *output_pin_fmts; + struct sof_ipc4_pin_format *input_pin_fmts; + u32 num_input_formats; + u32 num_output_formats; ++ u32 changed_params; + }; + + /** +@@ -210,7 +213,7 @@ struct sof_ipc4_available_audio_format { + * @node_id: ID of Gateway Node + * @dma_buffer_size: Preferred Gateway DMA buffer size (in bytes) + * @config_length: Length of gateway node configuration blob specified in #config_data +- * config_data: Gateway node configuration blob ++ * @config_data: Gateway node configuration blob + */ + struct sof_copier_gateway_cfg { + uint32_t node_id; +@@ -285,7 +288,9 @@ struct sof_ipc4_dma_stream_ch_map { + struct sof_ipc4_dma_config { + uint8_t dma_method; + uint8_t pre_allocated_by_host; ++ /* private: */ + uint16_t rsvd; ++ /* public: */ + uint32_t dma_channel_id; + uint32_t stream_id; + struct sof_ipc4_dma_stream_ch_map dma_stream_channel_map; +@@ -296,7 +301,7 @@ struct sof_ipc4_dma_config { + #define SOF_IPC4_GTW_DMA_CONFIG_ID 0x1000 + + /** +- * struct sof_ipc4_dma_config: DMA configuration ++ * struct sof_ipc4_dma_config_tlv - DMA configuration + * @type: set to SOF_IPC4_GTW_DMA_CONFIG_ID + * @length: sizeof(struct sof_ipc4_dma_config) + dma_config.dma_priv_config_size + * @dma_config: actual DMA configuration +@@ -321,6 +326,7 @@ struct sof_ipc4_alh_configuration_blob { + * @data: IPC copier data + * @copier_config: Copier + blob + * @ipc_config_size: Size of copier_config ++ * @ipc_config_data: Copier module config data + * @available_fmt: Available audio format + * @frame_fmt: frame format + * @msg: message structure for copier +diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c +--- a/sound/soc/sof/ipc4.c ++++ b/sound/soc/sof/ipc4.c +@@ -289,6 +289,89 @@ static void sof_ipc4_dump_payload(struct snd_sof_dev *sdev, + 16, 4, ipc_data, size, false); + } + ++static const char *sof_ipc4_resource_type_str(u32 type) ++{ ++ switch (type) { ++ case SOF_IPC4_MODULE_INSTANCE: ++ return "resource: MODULE_INSTANCE"; ++ case SOF_IPC4_PIPELINE: ++ return "resource: PIPELINE"; ++ case SOF_IPC4_GATEWAY: ++ return "resource: GATEWAY"; ++ case SOF_IPC4_EDF_TASK: ++ return "resource: EDF_TASK"; ++ case SOF_IPC4_INVALID_RESOURCE_TYPE: ++ return "Resource is invalid"; ++ default: ++ return "Unknown resource type"; ++ } ++} ++ ++static const char *sof_ipc4_resource_event_type_str(u32 event_type) ++{ ++ switch (event_type) { ++ case SOF_IPC4_MIXER_UNDERRUN_DETECTED: ++ return "event: MIXER_UNDERRUN_DETECTED"; ++ case SOF_IPC4_PROCESS_DATA_ERROR: ++ return "event: PROCESS_DATA_ERROR"; ++ case SOF_IPC4_GATEWAY_UNDERRUN_DETECTED: ++ return "event: GATEWAY_UNDERRUN_DETECTED"; ++ case SOF_IPC4_GATEWAY_OVERRUN_DETECTED: ++ return "event: GATEWAY_OVERRUN_DETECTED"; ++ default: ++ return "Unknown event type"; ++ } ++} ++ ++static void sof_ipc4_resource_event_handler(struct snd_sof_dev *sdev, ++ struct sof_ipc4_msg *ipc4_msg) ++{ ++ struct sof_ipc4_notify_resource_data *data = ipc4_msg->data_ptr; ++ ++ /* Print event details */ ++ switch (data->event_type) { ++ case SOF_IPC4_MIXER_UNDERRUN_DETECTED: ++ dev_dbg(sdev->dev, "%s (%u): eos %u, mixed %u, expected %u\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type, data->data.mixer_underrun.eos_flag, ++ data->data.mixer_underrun.data_mixed, ++ data->data.mixer_underrun.expected_data_mixed); ++ break; ++ case SOF_IPC4_PROCESS_DATA_ERROR: ++ dev_dbg(sdev->dev, "%s (%u): error_code %#x\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type, data->data.process_data_error.error_code); ++ break; ++ case SOF_IPC4_GATEWAY_UNDERRUN_DETECTED: ++ case SOF_IPC4_GATEWAY_OVERRUN_DETECTED: ++ dev_dbg(sdev->dev, "%s (%u)\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type); ++ break; ++ default: ++ dev_dbg(sdev->dev, "%s (%u): raw dws %#x %#x %#x %#x %#x %#x\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type, ++ data->data.dws[0], data->data.dws[1], data->data.dws[2], ++ data->data.dws[3], data->data.dws[4], data->data.dws[5]); ++ break; ++ } ++ ++ /* Print resource details */ ++ if (data->resource_type == SOF_IPC4_MODULE_INSTANCE) { ++ u32 module_id = SOF_IPC4_MOD_ID_GET(data->resource_id); ++ u32 instance_id = SOF_IPC4_MOD_INSTANCE_GET(data->resource_id); ++ ++ dev_dbg(sdev->dev, "%s (%u), module_id %u, instance_id %u\n", ++ sof_ipc4_resource_type_str(data->resource_type), ++ data->resource_type, module_id, instance_id); ++ } else if (data->resource_type != SOF_IPC4_INVALID_RESOURCE_TYPE) { ++ dev_dbg(sdev->dev, "%s (%u), id %u\n", ++ sof_ipc4_resource_type_str(data->resource_type), ++ data->resource_type, data->resource_id); ++ } ++} ++ + static int sof_ipc4_get_reply(struct snd_sof_dev *sdev) + { + struct snd_sof_ipc_msg *msg = sdev->msg; +@@ -734,6 +817,7 @@ static void sof_ipc4_rx_msg(struct snd_sof_dev *sdev) + break; + case SOF_IPC4_NOTIFY_RESOURCE_EVENT: + data_size = sizeof(struct sof_ipc4_notify_resource_data); ++ handler_func = sof_ipc4_resource_event_handler; + break; + case SOF_IPC4_NOTIFY_LOG_BUFFER_STATUS: + sof_ipc4_mtrace_update_pos(sdev, SOF_IPC4_LOG_CORE_GET(ipc4_msg->primary)); +diff --git a/sound/soc/sof/nocodec.c b/sound/soc/sof/nocodec.c +--- a/sound/soc/sof/nocodec.c ++++ b/sound/soc/sof/nocodec.c +@@ -21,12 +21,13 @@ static struct snd_soc_card sof_nocodec_card = { + static int sof_nocodec_bes_setup(struct device *dev, + struct snd_soc_dai_driver *drv, + struct snd_soc_dai_link *links, +- int link_num, struct snd_soc_card *card) ++ int link_num) + { ++ struct snd_soc_card *card = &sof_nocodec_card; + struct snd_soc_dai_link_component *dlc; + int i; + +- if (!drv || !links || !card) ++ if (!drv || !links) + return -EINVAL; + + /* set up BE dai_links */ +@@ -67,10 +68,10 @@ static int sof_nocodec_bes_setup(struct device *dev, + return 0; + } + +-static int sof_nocodec_setup(struct device *dev, +- u32 num_dai_drivers, +- struct snd_soc_dai_driver *dai_drivers) ++static int sof_nocodec_setup(struct device *dev, struct snd_soc_acpi_mach *mach) + { ++ u32 num_dai_drivers = mach->mach_params.num_dai_drivers; ++ struct snd_soc_dai_driver *dai_drivers = mach->mach_params.dai_drivers; + struct snd_soc_dai_link *links; + + /* create dummy BE dai_links */ +@@ -78,7 +79,7 @@ static int sof_nocodec_setup(struct device *dev, + if (!links) + return -ENOMEM; + +- return sof_nocodec_bes_setup(dev, dai_drivers, links, num_dai_drivers, &sof_nocodec_card); ++ return sof_nocodec_bes_setup(dev, dai_drivers, links, num_dai_drivers); + } + + static int sof_nocodec_probe(struct platform_device *pdev) +@@ -92,8 +93,7 @@ static int sof_nocodec_probe(struct platform_device *pdev) + + snd_soc_card_set_topology_name(card, "sof"); + +- ret = sof_nocodec_setup(card->dev, mach->mach_params.num_dai_drivers, +- mach->mach_params.dai_drivers); ++ ret = sof_nocodec_setup(card->dev, mach); + if (ret < 0) + return ret; + +diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c +--- a/sound/soc/sof/pcm.c ++++ b/sound/soc/sof/pcm.c +@@ -62,7 +62,7 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) + * To avoid sending IPC before the previous IPC is handled, we + * schedule delayed work here to call the snd_pcm_period_elapsed(). + */ +- schedule_work(&spcm->stream[substream->stream].period_elapsed_work); ++ queue_work(system_highpri_wq, &spcm->stream[substream->stream].period_elapsed_work); + } + EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); + +diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c +--- a/sound/soc/sof/sof-audio.c ++++ b/sound/soc/sof/sof-audio.c +@@ -501,9 +501,8 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget + !sof_widget_in_same_direction(swidget, dir)) + return 0; + +- /* skip widgets already prepared or aggregated DAI widgets*/ +- if (!widget_ops[widget->id].ipc_prepare || swidget->prepared || +- is_aggregated_dai(swidget)) ++ /* skip widgets aggregated DAI widgets */ ++ if (!widget_ops[widget->id].ipc_prepare || is_aggregated_dai(swidget)) + goto sink_prepare; + + /* prepare the source widget */ +diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h +--- a/sound/soc/sof/sof-audio.h ++++ b/sound/soc/sof/sof-audio.h +@@ -459,10 +459,10 @@ struct snd_sof_widget { + /* Scheduling domain (enum sof_comp_domain), unset, Low Latency, or Data Processing */ + u32 comp_domain; + +- /* The values below are added to mod_init pay load if comp_domain indicates DP component */ +- u32 dp_domain_id; /* DP process userspace domain ID */ +- u32 dp_stack_bytes; /* DP process stack size requirement in bytes */ +- u32 dp_heap_bytes; /* DP process heap size requirement in bytes */ ++ /* Module instance's memory configuration. */ ++ u32 domain_id; /* Module instance's userspace domain ID */ ++ u32 stack_bytes; /* Module instance's stack size requirement */ ++ u32 heap_bytes; /* Module instance's heap size requirement */ + + struct snd_soc_dapm_widget *widget; + struct list_head list; /* list in sdev widget list */ +diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c +--- a/sound/soc/sof/topology.c ++++ b/sound/soc/sof/topology.c +@@ -309,7 +309,7 @@ static const struct sof_dai_types sof_dais[] = { + {"ACPHS_VIRTUAL", SOF_DAI_AMD_HS_VIRTUAL}, + {"MICFIL", SOF_DAI_IMX_MICFIL}, + {"ACP_SDW", SOF_DAI_AMD_SDW}, +- ++ {"ACPTDM", SOF_DAI_AMD_I2S}, + }; + + static enum sof_ipc_dai_type find_dai(const char *name) +@@ -846,6 +846,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + struct snd_soc_tplg_mixer_control *mc = + container_of(hdr, struct snd_soc_tplg_mixer_control, hdr); + int tlv[SOF_TLV_ITEMS]; ++ u32 min, max; + unsigned int mask; + int ret; + +@@ -853,6 +854,11 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN) + return -EINVAL; + ++ min = le32_to_cpu(mc->min); ++ max = le32_to_cpu(mc->max); ++ if (min > max || max >= INT_MAX) ++ return -EINVAL; ++ + /* + * If control has more than 2 channels we need to override the info. This is because even if + * ASoC layer has defined topology's max channel count to SND_SOC_TPLG_MAX_CHAN = 8, the +@@ -863,12 +869,12 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + kc->info = snd_sof_volume_info; + + scontrol->comp_id = sdev->next_comp_id; +- scontrol->min_volume_step = le32_to_cpu(mc->min); +- scontrol->max_volume_step = le32_to_cpu(mc->max); ++ scontrol->min_volume_step = min; ++ scontrol->max_volume_step = max; + scontrol->num_channels = le32_to_cpu(mc->num_channels); + +- scontrol->max = le32_to_cpu(mc->max); +- if (le32_to_cpu(mc->max) == 1) ++ scontrol->max = max; ++ if (max == 1) + goto skip; + + /* extract tlv data */ +@@ -878,7 +884,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + } + + /* set up volume table */ +- ret = set_up_volume_table(scontrol, tlv, le32_to_cpu(mc->max) + 1); ++ ret = set_up_volume_table(scontrol, tlv, max + 1); + if (ret < 0) { + dev_err(scomp->dev, "error: setting up volume table\n"); + return ret; +@@ -911,7 +917,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + return 0; + + err: +- if (le32_to_cpu(mc->max) > 1) ++ if (max > 1) + kfree(scontrol->volume_table); + + return ret; +@@ -1102,7 +1108,7 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp, + + full = NULL; + partial = NULL; +- list_for_each_entry(rtd, &card->rtd_list, list) { ++ for_each_card_rtds(card, rtd) { + /* does stream match DAI link ? */ + if (rtd->dai_link->stream_name) { + if (!strcmp(rtd->dai_link->stream_name, w->sname)) { +@@ -1167,7 +1173,7 @@ static void sof_disconnect_dai_widget(struct snd_soc_component *scomp, + else + return; + +- list_for_each_entry(rtd, &card->rtd_list, list) { ++ for_each_card_rtds(card, rtd) { + /* does stream match DAI link ? */ + if (!rtd->dai_link->stream_name || + !strstr(rtd->dai_link->stream_name, sname)) +@@ -1943,8 +1949,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + private->array, le32_to_cpu(private->size)); + if (ret < 0) { + dev_err(scomp->dev, "Failed tp parse common DAI link tokens\n"); +- kfree(slink); +- return ret; ++ goto free_slink; + } + + token_list = tplg_ops ? tplg_ops->token_list : NULL; +@@ -1995,6 +2000,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + case SOF_DAI_AMD_HS: + case SOF_DAI_AMD_SP_VIRTUAL: + case SOF_DAI_AMD_HS_VIRTUAL: ++ case SOF_DAI_AMD_I2S: + token_id = SOF_ACPI2S_TOKENS; + num_tuples += token_list[SOF_ACPI2S_TOKENS].count; + break; +@@ -2013,8 +2019,8 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + /* allocate memory for tuples array */ + slink->tuples = kzalloc_objs(*slink->tuples, num_tuples); + if (!slink->tuples) { +- kfree(slink); +- return -ENOMEM; ++ ret = -ENOMEM; ++ goto free_slink; + } + + if (token_list[SOF_DAI_LINK_TOKENS].tokens) { +@@ -2070,6 +2076,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + + err: + kfree(slink->tuples); ++free_slink: + kfree(slink); + + return ret; +@@ -2505,13 +2512,12 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_pdata *sof_pdata = sdev->pdata; + const char *tplg_filename_prefix = sof_pdata->tplg_filename_prefix; +- const struct firmware *fw; +- const char **tplg_files; + int tplg_cnt = 0; + int ret; + int i; + +- tplg_files = kcalloc(scomp->card->num_links, sizeof(char *), GFP_KERNEL); ++ const char **tplg_files __free(kfree) = ++ kcalloc(scomp->card->num_links, sizeof(char *), GFP_KERNEL); + if (!tplg_files) + return -ENOMEM; + +@@ -2537,10 +2543,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + tplg_filename_prefix, + &tplg_files, + no_fallback); +- if (tplg_cnt < 0) { +- kfree(tplg_files); ++ if (tplg_cnt < 0) + return tplg_cnt; +- } + } + + /* +@@ -2551,8 +2555,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + if (strstr(file, "dummy")) { + dev_err(scomp->dev, + "Function topology is required, please upgrade sof-firmware\n"); +- +- kfree(tplg_files); + return -EINVAL; + } + tplg_files[0] = file; +@@ -2567,6 +2569,7 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + if (tplg_files[0] != file) + dev_info(scomp->dev, "loading topology %d: %s\n", i, tplg_files[i]); + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, tplg_files[i], scomp->dev); + if (ret < 0) { + /* +@@ -2585,8 +2588,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + else + ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); + +- release_firmware(fw); +- + if (ret < 0) { + dev_err(scomp->dev, "tplg %s component load failed %d\n", + tplg_files[i], ret); +@@ -2605,6 +2606,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + goto out; + } + dev_info(scomp->dev, "loading feature topology %d: %s\n", i, feature_topology); ++ ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, feature_topology, scomp->dev); + if (ret < 0) { + /* +@@ -2629,8 +2632,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + else + ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); + +- release_firmware(fw); +- + if (ret < 0) { + dev_err(scomp->dev, "feature tplg %s component load failed %d\n", + feature_topologies[i], ret); +@@ -2649,8 +2650,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + if (ret >= 0 && sdev->led_present) + ret = snd_ctl_led_request(); + +- kfree(tplg_files); +- + return ret; + } + EXPORT_SYMBOL(snd_sof_load_topology); +diff --git a/sound/soc/sophgo/cv1800b-tdm.c b/sound/soc/sophgo/cv1800b-tdm.c +--- a/sound/soc/sophgo/cv1800b-tdm.c ++++ b/sound/soc/sophgo/cv1800b-tdm.c +@@ -558,7 +558,7 @@ static const struct snd_soc_dai_ops cv1800b_i2s_dai_ops = { + .set_sysclk = cv1800b_i2s_dai_set_sysclk, + }; + +-static const struct snd_soc_dai_driver cv1800b_i2s_dai_template = { ++static struct snd_soc_dai_driver cv1800b_i2s_dai_template = { + .name = "cv1800b-i2s", + .playback = { + .stream_name = "Playback", +@@ -636,7 +636,6 @@ static int cv1800b_i2s_probe(struct platform_device *pdev) + struct cv1800b_i2s *i2s; + struct resource *res; + void __iomem *regs; +- struct snd_soc_dai_driver *dai; + int ret; + + i2s = devm_kzalloc(dev, sizeof(*i2s), GFP_KERNEL); +@@ -666,21 +665,14 @@ static int cv1800b_i2s_probe(struct platform_device *pdev) + platform_set_drvdata(pdev, i2s); + cv1800b_i2s_setup_tdm(i2s); + +- dai = devm_kmemdup(dev, &cv1800b_i2s_dai_template, sizeof(*dai), +- GFP_KERNEL); +- if (!dai) +- return -ENOMEM; +- +- ret = devm_snd_soc_register_component(dev, &cv1800b_i2s_component, dai, +- 1); ++ ret = devm_snd_soc_register_component(dev, &cv1800b_i2s_component, ++ &cv1800b_i2s_dai_template, 1); + if (ret) + return ret; + + ret = devm_snd_dmaengine_pcm_register(dev, &cv1800b_i2s_pcm_config, 0); +- if (ret) { +- dev_err(dev, "dmaengine_pcm_register failed: %d\n", ret); ++ if (ret) + return ret; +- } + + return 0; + } +diff --git a/sound/soc/spacemit/k1_i2s.c b/sound/soc/spacemit/k1_i2s.c +--- a/sound/soc/spacemit/k1_i2s.c ++++ b/sound/soc/spacemit/k1_i2s.c +@@ -52,7 +52,7 @@ struct spacemit_i2s_dev { + + struct clk *sysclk; + struct clk *bclk; +- struct clk *sspa_clk; ++ struct clk *func_clk; + struct clk *sysclk_div; + struct clk *c_sysclk; + struct clk *c_bclk; +@@ -221,7 +221,7 @@ static int spacemit_i2s_hw_params(struct snd_pcm_substream *substream, + if (ret) + return ret; + +- return clk_set_rate(i2s->sspa_clk, bclk_rate); ++ return clk_set_rate(i2s->func_clk, bclk_rate); + } + + static int spacemit_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, +@@ -335,6 +335,11 @@ static int spacemit_i2s_dai_remove(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 spacemit_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B; ++ + static const struct snd_soc_dai_ops spacemit_i2s_dai_ops = { + .probe = spacemit_i2s_dai_probe, + .remove = spacemit_i2s_dai_remove, +@@ -343,26 +348,12 @@ static const struct snd_soc_dai_ops spacemit_i2s_dai_ops = { + .set_sysclk = spacemit_i2s_set_sysclk, + .set_fmt = spacemit_i2s_set_fmt, + .trigger = spacemit_i2s_trigger, ++ .auto_selectable_formats = &spacemit_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver spacemit_i2s_dai = { + .ops = &spacemit_i2s_dai_ops, +- .playback = { +- .channels_min = 1, +- .channels_max = 2, +- .rates = SPACEMIT_PCM_RATES, +- .rate_min = SNDRV_PCM_RATE_8000, +- .rate_max = SNDRV_PCM_RATE_48000, +- .formats = SPACEMIT_PCM_FORMATS, +- }, +- .capture = { +- .channels_min = 1, +- .channels_max = 2, +- .rates = SPACEMIT_PCM_RATES, +- .rate_min = SNDRV_PCM_RATE_8000, +- .rate_max = SNDRV_PCM_RATE_48000, +- .formats = SPACEMIT_PCM_FORMATS, +- }, + .symmetric_rate = 1, + }; + +@@ -392,6 +383,8 @@ static int spacemit_i2s_init_dai(struct spacemit_i2s_dev *i2s, + dai->playback.channels_min = 1; + dai->playback.channels_max = 2; + dai->playback.rates = SPACEMIT_PCM_RATES; ++ dai->playback.rate_min = SNDRV_PCM_RATE_8000; ++ dai->playback.rate_max = SNDRV_PCM_RATE_48000; + dai->playback.formats = SPACEMIT_PCM_FORMATS; + + i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; +@@ -404,6 +397,8 @@ static int spacemit_i2s_init_dai(struct spacemit_i2s_dev *i2s, + dai->capture.channels_min = 1; + dai->capture.channels_max = 2; + dai->capture.rates = SPACEMIT_PCM_RATES; ++ dai->capture.rate_min = SNDRV_PCM_RATE_8000; ++ dai->capture.rate_max = SNDRV_PCM_RATE_48000; + dai->capture.formats = SPACEMIT_PCM_FORMATS; + + i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; +@@ -445,14 +440,14 @@ static int spacemit_i2s_probe(struct platform_device *pdev) + if (IS_ERR(i2s->bclk)) + return dev_err_probe(i2s->dev, PTR_ERR(i2s->bclk), "failed to enable bit clock\n"); + +- clk = devm_clk_get_enabled(i2s->dev, "sspa_bus"); ++ clk = devm_clk_get_enabled(i2s->dev, "bus"); + if (IS_ERR(clk)) +- return dev_err_probe(i2s->dev, PTR_ERR(clk), "failed to enable sspa_bus clock\n"); ++ return dev_err_probe(i2s->dev, PTR_ERR(clk), "failed to enable bus clock\n"); + +- i2s->sspa_clk = devm_clk_get_enabled(i2s->dev, "sspa"); +- if (IS_ERR(i2s->sspa_clk)) +- return dev_err_probe(i2s->dev, PTR_ERR(i2s->sspa_clk), +- "failed to enable sspa clock\n"); ++ i2s->func_clk = devm_clk_get_enabled(i2s->dev, "func"); ++ if (IS_ERR(i2s->func_clk)) ++ return dev_err_probe(i2s->dev, PTR_ERR(i2s->func_clk), ++ "failed to enable func clock\n"); + + i2s->sysclk_div = devm_clk_get_optional_enabled(i2s->dev, "sysclk_div"); + if (IS_ERR(i2s->sysclk_div)) +@@ -471,7 +466,7 @@ static int spacemit_i2s_probe(struct platform_device *pdev) + + i2s->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(i2s->base)) +- return dev_err_probe(i2s->dev, PTR_ERR(i2s->base), "failed to map registers\n"); ++ return PTR_ERR(i2s->base); + + i2s->reset = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(i2s->reset)) +@@ -488,7 +483,7 @@ static int spacemit_i2s_probe(struct platform_device *pdev) + &spacemit_i2s_component, + dai, 1); + if (ret) +- return dev_err_probe(i2s->dev, ret, "failed to register component"); ++ return ret; + + return devm_snd_dmaengine_pcm_register(&pdev->dev, &spacemit_dmaengine_pcm_config, 0); + } +diff --git a/sound/soc/spear/spdif_in.c b/sound/soc/spear/spdif_in.c +--- a/sound/soc/spear/spdif_in.c ++++ b/sound/soc/spear/spdif_in.c +@@ -55,7 +55,7 @@ static int spdif_in_dai_probe(struct snd_soc_dai *dai) + struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); + + host->dma_params_rx.filter_data = &host->dma_params; +- dai->capture_dma_data = &host->dma_params_rx; ++ snd_soc_dai_dma_data_set_capture(dai, &host->dma_params_rx); + + return 0; + } +@@ -150,12 +150,12 @@ static int spdif_in_trigger(struct snd_pcm_substream *substream, int cmd, + + static const struct snd_soc_dai_ops spdif_in_dai_ops = { + .shutdown = spdif_in_shutdown, ++ .probe = spdif_in_dai_probe, + .trigger = spdif_in_trigger, + .hw_params = spdif_in_hw_params, + }; + + static struct snd_soc_dai_driver spdif_in_dai = { +- .probe = spdif_in_dai_probe, + .capture = { + .channels_min = 2, + .channels_max = 2, +@@ -218,10 +218,8 @@ static int spdif_in_probe(struct platform_device *pdev) + + host->io_base = io_base; + host->irq = platform_get_irq(pdev, 0); +- if (host->irq < 0) { +- dev_warn(&pdev->dev, "failed to get IRQ: %d\n", host->irq); ++ if (host->irq < 0) + return host->irq; +- } + + host->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) +@@ -243,10 +241,8 @@ static int spdif_in_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, host->irq, spdif_in_irq, 0, + "spdif-in", host); +- if (ret) { +- dev_warn(&pdev->dev, "request_irq failed\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_soc_register_component(&pdev->dev, &spdif_in_component, + &spdif_in_dai, 1); +diff --git a/sound/soc/spear/spdif_out.c b/sound/soc/spear/spdif_out.c +--- a/sound/soc/spear/spdif_out.c ++++ b/sound/soc/spear/spdif_out.c +@@ -249,6 +249,7 @@ static int spdif_soc_dai_probe(struct snd_soc_dai *dai) + } + + static const struct snd_soc_dai_ops spdif_out_dai_ops = { ++ .probe = spdif_soc_dai_probe, + .mute_stream = spdif_mute, + .startup = spdif_out_startup, + .shutdown = spdif_out_shutdown, +@@ -266,7 +267,6 @@ static struct snd_soc_dai_driver spdif_out_dai = { + SNDRV_PCM_RATE_192000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +- .probe = spdif_soc_dai_probe, + .ops = &spdif_out_dai_ops, + }; + +diff --git a/sound/soc/sprd/sprd-mcdt.c b/sound/soc/sprd/sprd-mcdt.c +--- a/sound/soc/sprd/sprd-mcdt.c ++++ b/sound/soc/sprd/sprd-mcdt.c +@@ -931,10 +931,8 @@ static int sprd_mcdt_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, irq, sprd_mcdt_irq_handler, + 0, "sprd-mcdt", mcdt); +- if (ret) { +- dev_err(&pdev->dev, "Failed to request MCDT IRQ\n"); ++ if (ret) + return ret; +- } + + sprd_mcdt_init_chans(mcdt, res); + +diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c +--- a/sound/soc/sprd/sprd-pcm-dma.c ++++ b/sound/soc/sprd/sprd-pcm-dma.c +@@ -469,8 +469,6 @@ static int sprd_soc_platform_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, &sprd_soc_component, + NULL, 0); +- if (ret) +- dev_err(&pdev->dev, "could not register platform:%d\n", ret); + + return ret; + } +diff --git a/sound/soc/starfive/jh7110_pwmdac.c b/sound/soc/starfive/jh7110_pwmdac.c +--- a/sound/soc/starfive/jh7110_pwmdac.c ++++ b/sound/soc/starfive/jh7110_pwmdac.c +@@ -477,25 +477,22 @@ static int jh7110_pwmdac_probe(struct platform_device *pdev) + &jh7110_pwmdac_component, + &jh7110_pwmdac_dai, 1); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "failed to register dai\n"); ++ return ret; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "failed to register pcm\n"); ++ return ret; + + pm_runtime_enable(dev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = jh7110_pwmdac_runtime_resume(&pdev->dev); +- if (ret) +- goto err_pm_disable; ++ if (ret) { ++ pm_runtime_disable(&pdev->dev); ++ return ret; ++ } + } + + return 0; +- +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +- +- return ret; + } + + static void jh7110_pwmdac_remove(struct platform_device *pdev) +diff --git a/sound/soc/starfive/jh7110_tdm.c b/sound/soc/starfive/jh7110_tdm.c +--- a/sound/soc/starfive/jh7110_tdm.c ++++ b/sound/soc/starfive/jh7110_tdm.c +@@ -559,10 +559,8 @@ static int jh7110_tdm_clk_reset_get(struct platform_device *pdev, + tdm->clks[5].id = "tdm"; + + ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(tdm->clks), tdm->clks); +- if (ret) { +- dev_err(&pdev->dev, "Failed to get tdm clocks\n"); ++ if (ret) + return ret; +- } + + tdm->resets = devm_reset_control_array_get_exclusive(&pdev->dev); + if (IS_ERR(tdm->resets)) { +@@ -589,42 +587,33 @@ static int jh7110_tdm_probe(struct platform_device *pdev) + tdm->dev = &pdev->dev; + + ret = jh7110_tdm_clk_reset_get(pdev, tdm); +- if (ret) { +- dev_err(&pdev->dev, "Failed to enable audio-tdm clock\n"); ++ if (ret) + return ret; +- } + + jh7110_tdm_init_params(tdm); + + dev_set_drvdata(&pdev->dev, tdm); + ret = devm_snd_soc_register_component(&pdev->dev, &jh7110_tdm_component, + &jh7110_tdm_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register dai\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, + &jh7110_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_COMPAT); +- if (ret) { +- dev_err(&pdev->dev, "Could not register pcm: %d\n", ret); ++ if (ret) + return ret; +- } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = jh7110_tdm_runtime_resume(&pdev->dev); +- if (ret) +- goto err_pm_disable; ++ if (ret) { ++ pm_runtime_disable(&pdev->dev); ++ return ret; ++ } + } + + return 0; +- +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +- +- return ret; + } + + static void jh7110_tdm_dev_remove(struct platform_device *pdev) +diff --git a/sound/soc/stm/stm32_adfsdm.c b/sound/soc/stm/stm32_adfsdm.c +--- a/sound/soc/stm/stm32_adfsdm.c ++++ b/sound/soc/stm/stm32_adfsdm.c +@@ -360,11 +360,8 @@ static int stm32_adfsdm_probe(struct platform_device *pdev) + ret = devm_snd_soc_register_component(&pdev->dev, + &stm32_adfsdm_soc_platform, + NULL, 0); +- if (ret < 0) { +- dev_err(&pdev->dev, "%s: Failed to register PCM platform\n", +- __func__); ++ if (ret < 0) + return ret; +- } + + pm_runtime_enable(&pdev->dev); + +diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c +--- a/sound/soc/stm/stm32_i2s.c ++++ b/sound/soc/stm/stm32_i2s.c +@@ -1238,10 +1238,8 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev, + + ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, 0, + dev_name(&pdev->dev), i2s); +- if (ret) { +- dev_err(&pdev->dev, "irq request returned %d\n", ret); ++ if (ret) + return ret; +- } + + /* Reset */ + rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); +@@ -1295,7 +1293,7 @@ static int stm32_i2s_probe(struct platform_device *pdev) + + ret = snd_dmaengine_pcm_register(&pdev->dev, &stm32_i2s_pcm_config, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "PCM DMA register error\n"); ++ return ret; + + ret = snd_soc_register_component(&pdev->dev, &stm32_i2s_component, + i2s->dai_drv, 1); +diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c +--- a/sound/soc/stm/stm32_sai_sub.c ++++ b/sound/soc/stm/stm32_sai_sub.c +@@ -1696,19 +1696,15 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, sai->pdata->irq, stm32_sai_isr, + IRQF_SHARED, dev_name(&pdev->dev), sai); +- if (ret) { +- dev_err(&pdev->dev, "IRQ request returned %d\n", ret); ++ if (ret) + goto err_unprepare_pclk; +- } + + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) + conf = &stm32_sai_pcm_config_spdif; + + ret = snd_dmaengine_pcm_register(&pdev->dev, conf, 0); +- if (ret) { +- ret = dev_err_probe(&pdev->dev, ret, "Could not register pcm dma\n"); ++ if (ret) + goto err_unprepare_pclk; +- } + + ret = snd_soc_register_component(&pdev->dev, &stm32_component, + &sai->cpu_dai_drv, 1); +diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c +--- a/sound/soc/stm/stm32_spdifrx.c ++++ b/sound/soc/stm/stm32_spdifrx.c +@@ -970,10 +970,8 @@ static int stm32_spdifrx_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, spdifrx->irq, stm32_spdifrx_isr, 0, + dev_name(&pdev->dev), spdifrx); +- if (ret) { +- dev_err(&pdev->dev, "IRQ request returned %d\n", ret); ++ if (ret) + return ret; +- } + + rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); + if (IS_ERR(rst)) +@@ -987,7 +985,7 @@ static int stm32_spdifrx_probe(struct platform_device *pdev) + pcm_config = &stm32_spdifrx_pcm_config; + ret = snd_dmaengine_pcm_register(&pdev->dev, pcm_config, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "PCM DMA register error\n"); ++ return ret; + + ret = snd_soc_register_component(&pdev->dev, + &stm32_spdifrx_component, +diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c +--- a/sound/soc/sunxi/sun4i-codec.c ++++ b/sound/soc/sunxi/sun4i-codec.c +@@ -22,13 +22,13 @@ + #include + + #include ++#include ++#include + #include + #include + #include + #include + #include +-#include +-#include + + /* Codec DAC digital controls and FIFO registers */ + #define SUN4I_CODEC_DAC_DPC (0x00) +@@ -2317,67 +2317,54 @@ static int sun4i_codec_probe(struct platform_device *pdev) + + scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base, + quirks->regmap_config); +- if (IS_ERR(scodec->regmap)) { +- dev_err(&pdev->dev, "Failed to create our regmap\n"); +- return PTR_ERR(scodec->regmap); +- } ++ if (IS_ERR(scodec->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->regmap), ++ "Failed to create our regmap\n"); + + /* Get the clocks from the DT */ + scodec->clk_apb = devm_clk_get_enabled(&pdev->dev, "apb"); +- if (IS_ERR(scodec->clk_apb)) { +- dev_err(&pdev->dev, "Failed to get the APB clock\n"); +- return PTR_ERR(scodec->clk_apb); +- } ++ if (IS_ERR(scodec->clk_apb)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->clk_apb), ++ "Failed to get the APB clock\n"); + + scodec->clk_module = devm_clk_get(&pdev->dev, "codec"); +- if (IS_ERR(scodec->clk_module)) { +- dev_err(&pdev->dev, "Failed to get the module clock\n"); +- return PTR_ERR(scodec->clk_module); +- } ++ if (IS_ERR(scodec->clk_module)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->clk_module), ++ "Failed to get the module clock\n"); + + if (quirks->has_reset) { + scodec->rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); +- if (IS_ERR(scodec->rst)) { +- dev_err(&pdev->dev, "Failed to get reset control\n"); +- return PTR_ERR(scodec->rst); +- } ++ if (IS_ERR(scodec->rst)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->rst), ++ "Failed to get reset control\n"); + } + + scodec->gpio_pa = devm_gpiod_get_optional(&pdev->dev, "allwinner,pa", + GPIOD_OUT_LOW); +- if (IS_ERR(scodec->gpio_pa)) { +- ret = PTR_ERR(scodec->gpio_pa); +- dev_err_probe(&pdev->dev, ret, "Failed to get pa gpio\n"); +- return ret; +- } ++ if (IS_ERR(scodec->gpio_pa)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->gpio_pa), ++ "Failed to get pa gpio\n"); ++ + + scodec->gpio_hp = devm_gpiod_get_optional(&pdev->dev, "hp-det", GPIOD_IN); +- if (IS_ERR(scodec->gpio_hp)) { +- ret = PTR_ERR(scodec->gpio_hp); +- dev_err_probe(&pdev->dev, ret, "Failed to get hp-det gpio\n"); +- return ret; +- } ++ if (IS_ERR(scodec->gpio_hp)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->gpio_hp), ++ "Failed to get hp-det gpio\n"); + + /* reg_field setup */ + scodec->reg_adc_fifoc = devm_regmap_field_alloc(&pdev->dev, + scodec->regmap, + quirks->reg_adc_fifoc); +- if (IS_ERR(scodec->reg_adc_fifoc)) { +- ret = PTR_ERR(scodec->reg_adc_fifoc); +- dev_err(&pdev->dev, "Failed to create regmap fields: %d\n", +- ret); +- return ret; +- } ++ if (IS_ERR(scodec->reg_adc_fifoc)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->reg_adc_fifoc), ++ "Failed to create regmap fields\n"); + + scodec->reg_dac_fifoc = devm_regmap_field_alloc(&pdev->dev, + scodec->regmap, + quirks->reg_dac_fifoc); +- if (IS_ERR(scodec->reg_dac_fifoc)) { +- ret = PTR_ERR(scodec->reg_dac_fifoc); +- dev_err(&pdev->dev, "Failed to create regmap fields: %d\n", +- ret); +- return ret; +- } ++ if (IS_ERR(scodec->reg_dac_fifoc)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->reg_dac_fifoc), ++ "Failed to create regmap fields\n"); + + /* DMA configuration for TX FIFO */ + scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata; +@@ -2394,39 +2381,29 @@ static int sun4i_codec_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, quirks->codec, + &sun4i_codec_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register our codec\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_soc_register_component(&pdev->dev, + &sun4i_codec_component, + &dummy_cpu_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register our DAI\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register against DMAEngine\n"); ++ if (ret) + return ret; +- } + + card = quirks->create_card(&pdev->dev); +- if (IS_ERR(card)) { +- ret = PTR_ERR(card); +- dev_err(&pdev->dev, "Failed to create our card\n"); +- return ret; +- } ++ if (IS_ERR(card)) ++ return PTR_ERR(card); + + snd_soc_card_set_drvdata(card, scodec); + + ret = snd_soc_register_card(card); +- if (ret) { +- dev_err_probe(&pdev->dev, ret, "Failed to register our card\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "Failed to register our card\n"); + + return 0; + } +diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c +--- a/sound/soc/sunxi/sun4i-i2s.c ++++ b/sound/soc/sunxi/sun4i-i2s.c +@@ -1550,30 +1550,26 @@ static int sun4i_i2s_probe(struct platform_device *pdev) + } + + i2s->bus_clk = devm_clk_get(&pdev->dev, "apb"); +- if (IS_ERR(i2s->bus_clk)) { +- dev_err(&pdev->dev, "Can't get our bus clock\n"); +- return PTR_ERR(i2s->bus_clk); +- } ++ if (IS_ERR(i2s->bus_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->bus_clk), ++ "Can't get our bus clock\n"); + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + i2s->variant->sun4i_i2s_regmap); +- if (IS_ERR(i2s->regmap)) { +- dev_err(&pdev->dev, "Regmap initialisation failed\n"); +- return PTR_ERR(i2s->regmap); +- } ++ if (IS_ERR(i2s->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->regmap), ++ "Regmap initialisation failed\n"); + + i2s->mod_clk = devm_clk_get(&pdev->dev, "mod"); +- if (IS_ERR(i2s->mod_clk)) { +- dev_err(&pdev->dev, "Can't get our mod clock\n"); +- return PTR_ERR(i2s->mod_clk); +- } ++ if (IS_ERR(i2s->mod_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->mod_clk), ++ "Can't get our mod clock\n"); + + if (i2s->variant->has_reset) { + i2s->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); +- if (IS_ERR(i2s->rst)) { +- dev_err(&pdev->dev, "Failed to get reset control\n"); +- return PTR_ERR(i2s->rst); +- } ++ if (IS_ERR(i2s->rst)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->rst), ++ "Failed to get reset control\n"); + } + + if (!IS_ERR(i2s->rst)) { +diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c +--- a/sound/soc/sunxi/sun4i-spdif.c ++++ b/sound/soc/sunxi/sun4i-spdif.c +@@ -684,26 +684,23 @@ static int sun4i_spdif_probe(struct platform_device *pdev) + + host->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &sun4i_spdif_regmap_config); +- if (IS_ERR(host->regmap)) { +- dev_err(&pdev->dev, "failed to initialise regmap.\n"); +- return PTR_ERR(host->regmap); +- } ++ if (IS_ERR(host->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(host->regmap), ++ "failed to initialise regmap.\n"); + + /* Clocks */ + host->apb_clk = devm_clk_get(&pdev->dev, "apb"); +- if (IS_ERR(host->apb_clk)) { +- dev_err(&pdev->dev, "failed to get a apb clock.\n"); +- return PTR_ERR(host->apb_clk); +- } ++ if (IS_ERR(host->apb_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(host->apb_clk), ++ "failed to get a apb clock.\n"); + + if (quirks->tx_clk_name) + tx_clk_name = quirks->tx_clk_name; + host->spdif_clk = devm_clk_get(&pdev->dev, tx_clk_name); +- if (IS_ERR(host->spdif_clk)) { +- dev_err(&pdev->dev, "failed to get the \"%s\" clock.\n", +- tx_clk_name); +- return PTR_ERR(host->spdif_clk); +- } ++ if (IS_ERR(host->spdif_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(host->spdif_clk), ++ "failed to get the \"%s\" clock.\n", ++ tx_clk_name); + + host->dma_params_tx.addr = res->start + quirks->reg_dac_txdata; + host->dma_params_tx.maxburst = 8; +diff --git a/sound/soc/sunxi/sun50i-codec-analog.c b/sound/soc/sunxi/sun50i-codec-analog.c +--- a/sound/soc/sunxi/sun50i-codec-analog.c ++++ b/sound/soc/sunxi/sun50i-codec-analog.c +@@ -551,16 +551,13 @@ static int sun50i_codec_analog_probe(struct platform_device *pdev) + bool enable; + + base = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(base)) { +- dev_err(&pdev->dev, "Failed to map the registers\n"); ++ if (IS_ERR(base)) + return PTR_ERR(base); +- } + + regmap = sun8i_adda_pr_regmap_init(&pdev->dev, base); +- if (IS_ERR(regmap)) { +- dev_err(&pdev->dev, "Failed to create regmap\n"); +- return PTR_ERR(regmap); +- } ++ if (IS_ERR(regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(regmap), ++ "Failed to create regmap\n"); + + enable = device_property_read_bool(&pdev->dev, + "allwinner,internal-bias-resistor"); +diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c +--- a/sound/soc/sunxi/sun8i-codec-analog.c ++++ b/sound/soc/sunxi/sun8i-codec-analog.c +@@ -822,16 +822,13 @@ static int sun8i_codec_analog_probe(struct platform_device *pdev) + void __iomem *base; + + base = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(base)) { +- dev_err(&pdev->dev, "Failed to map the registers\n"); ++ if (IS_ERR(base)) + return PTR_ERR(base); +- } + + regmap = sun8i_adda_pr_regmap_init(&pdev->dev, base); +- if (IS_ERR(regmap)) { +- dev_err(&pdev->dev, "Failed to create regmap\n"); +- return PTR_ERR(regmap); +- } ++ if (IS_ERR(regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(regmap), ++ "Failed to create regmap\n"); + + return devm_snd_soc_register_component(&pdev->dev, + &sun8i_codec_analog_cmpnt_drv, +diff --git a/sound/soc/tegra/tegra20_das.c b/sound/soc/tegra/tegra20_das.c +--- a/sound/soc/tegra/tegra20_das.c ++++ b/sound/soc/tegra/tegra20_das.c +@@ -167,10 +167,9 @@ static int tegra20_das_probe(struct platform_device *pdev) + + das->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra20_das_regmap_config); +- if (IS_ERR(das->regmap)) { +- dev_err(&pdev->dev, "regmap init failed\n"); +- return PTR_ERR(das->regmap); +- } ++ if (IS_ERR(das->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(das->regmap), ++ "regmap init failed\n"); + + tegra20_das_connect_dap_to_dac(das, TEGRA20_DAS_DAP_ID_1, + TEGRA20_DAS_DAP_SEL_DAC1); +diff --git a/sound/soc/tegra/tegra210_adx.c b/sound/soc/tegra/tegra210_adx.c +--- a/sound/soc/tegra/tegra210_adx.c ++++ b/sound/soc/tegra/tegra210_adx.c +@@ -496,16 +496,12 @@ static struct snd_kcontrol_new tegra264_adx_controls[] = { + static int tegra210_adx_component_probe(struct snd_soc_component *component) + { + struct tegra210_adx *adx = snd_soc_component_get_drvdata(component); +- int err = 0; + +- if (adx->soc_data->num_controls) { +- err = snd_soc_add_component_controls(component, adx->soc_data->controls, +- adx->soc_data->num_controls); +- if (err) +- dev_err(component->dev, "can't add ADX controls, err: %d\n", err); +- } ++ if (adx->soc_data->num_controls) ++ return snd_soc_add_component_controls(component, adx->soc_data->controls, ++ adx->soc_data->num_controls); + +- return err; ++ return 0; + } + + static const struct snd_soc_component_driver tegra210_adx_cmpnt = { +diff --git a/sound/soc/tegra/tegra210_amx.c b/sound/soc/tegra/tegra210_amx.c +--- a/sound/soc/tegra/tegra210_amx.c ++++ b/sound/soc/tegra/tegra210_amx.c +@@ -504,16 +504,12 @@ static struct snd_kcontrol_new tegra264_amx_controls[] = { + static int tegra210_amx_component_probe(struct snd_soc_component *component) + { + struct tegra210_amx *amx = snd_soc_component_get_drvdata(component); +- int err = 0; + +- if (amx->soc_data->num_controls) { +- err = snd_soc_add_component_controls(component, amx->soc_data->controls, +- amx->soc_data->num_controls); +- if (err) +- dev_err(component->dev, "can't add AMX controls, err: %d\n", err); +- } ++ if (amx->soc_data->num_controls) ++ return snd_soc_add_component_controls(component, amx->soc_data->controls, ++ amx->soc_data->num_controls); + +- return err; ++ return 0; + } + + static const struct snd_soc_component_driver tegra210_amx_cmpnt = { +diff --git a/sound/soc/tegra/tegra210_i2s.c b/sound/soc/tegra/tegra210_i2s.c +--- a/sound/soc/tegra/tegra210_i2s.c ++++ b/sound/soc/tegra/tegra210_i2s.c +@@ -1090,13 +1090,13 @@ static int tegra210_i2s_probe(struct platform_device *pdev) + "can't retrieve I2S bit clock\n"); + + /* +- * Not an error, as this clock is needed only when some other I/O +- * requires input clock from current I2S instance, which is +- * configurable from DT. ++ * This clock is optional and is only needed when another I/O uses ++ * the current I2S instance as its input clock, as configured in DT. + */ +- i2s->clk_sync_input = devm_clk_get(dev, "sync_input"); ++ i2s->clk_sync_input = devm_clk_get_optional(dev, "sync_input"); + if (IS_ERR(i2s->clk_sync_input)) +- dev_dbg(dev, "can't retrieve I2S sync input clock\n"); ++ return dev_err_probe(dev, PTR_ERR(i2s->clk_sync_input), ++ "can't retrieve I2S sync input clock\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) +diff --git a/sound/soc/ti/ams-delta.c b/sound/soc/ti/ams-delta.c +--- a/sound/soc/ti/ams-delta.c ++++ b/sound/soc/ti/ams-delta.c +@@ -562,21 +562,21 @@ static int ams_delta_probe(struct platform_device *pdev) + + card->dev = &pdev->dev; + +- handset_mute = devm_gpiod_get(card->dev, "handset_mute", ++ handset_mute = devm_gpiod_get(&pdev->dev, "handset_mute", + GPIOD_OUT_HIGH); + if (IS_ERR(handset_mute)) + return PTR_ERR(handset_mute); + +- handsfree_mute = devm_gpiod_get(card->dev, "handsfree_mute", ++ handsfree_mute = devm_gpiod_get(&pdev->dev, "handsfree_mute", + GPIOD_OUT_HIGH); + if (IS_ERR(handsfree_mute)) + return PTR_ERR(handsfree_mute); + + ret = snd_soc_register_card(card); + if (ret) { +- dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + card->dev = NULL; +- return ret; ++ return dev_err_probe(&pdev->dev, ret, ++ "snd_soc_register_card() failed\n"); + } + return 0; + } +diff --git a/sound/soc/ti/davinci-evm.c b/sound/soc/ti/davinci-evm.c +--- a/sound/soc/ti/davinci-evm.c ++++ b/sound/soc/ti/davinci-evm.c +@@ -245,9 +245,8 @@ static int davinci_evm_probe(struct platform_device *pdev) + + snd_soc_card_set_drvdata(&evm_soc_card, drvdata); + ret = devm_snd_soc_register_card(&pdev->dev, &evm_soc_card); +- + if (ret) { +- dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n"); + goto err_put; + } + +diff --git a/sound/soc/ti/j721e-evm.c b/sound/soc/ti/j721e-evm.c +--- a/sound/soc/ti/j721e-evm.c ++++ b/sound/soc/ti/j721e-evm.c +@@ -4,6 +4,7 @@ + * Author: Peter Ujfalusi + */ + ++#include + #include + #include + #include +@@ -263,7 +264,7 @@ static int j721e_audio_startup(struct snd_pcm_substream *substream) + int ret = 0; + int i; + +- guard(mutex)(&priv->mutex); ++ mutex_lock(&priv->mutex); + + domain->active++; + +@@ -303,6 +304,7 @@ static int j721e_audio_startup(struct snd_pcm_substream *substream) + out: + if (ret) + domain->active--; ++ mutex_unlock(&priv->mutex); + + return ret; + } +@@ -868,10 +870,9 @@ static int j721e_soc_probe(struct platform_device *pdev) + card->num_dapm_routes = ARRAY_SIZE(j721e_cpb_dapm_routes); + card->fully_routed = 1; + +- if (snd_soc_of_parse_card_name(card, "model")) { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } ++ ret = snd_soc_of_parse_card_name(card, "model"); ++ if (ret) ++ return ret; + + link_cnt = 0; + conf_cnt = 0; +diff --git a/sound/soc/ti/omap-abe-twl6040.c b/sound/soc/ti/omap-abe-twl6040.c +--- a/sound/soc/ti/omap-abe-twl6040.c ++++ b/sound/soc/ti/omap-abe-twl6040.c +@@ -234,16 +234,13 @@ static int omap_abe_probe(struct platform_device *pdev) + card->dapm_routes = audio_map; + card->num_dapm_routes = ARRAY_SIZE(audio_map); + +- if (snd_soc_of_parse_card_name(card, "ti,model")) { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } ++ ret = snd_soc_of_parse_card_name(card, "ti,model"); ++ if (ret) ++ return ret; + + ret = snd_soc_of_parse_audio_routing(card, "ti,audio-routing"); +- if (ret) { +- dev_err(&pdev->dev, "Error while parsing DAPM routing\n"); ++ if (ret) + return ret; +- } + + dai_node = of_parse_phandle(node, "ti,mcpdm", 0); + if (!dai_node) { +@@ -299,8 +296,8 @@ static int omap_abe_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) +- dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n", +- ret); ++ dev_err_probe(&pdev->dev, ret, ++ "devm_snd_soc_register_card() failed\n"); + + return ret; + } +diff --git a/sound/soc/ti/omap-dmic.c b/sound/soc/ti/omap-dmic.c +--- a/sound/soc/ti/omap-dmic.c ++++ b/sound/soc/ti/omap-dmic.c +@@ -465,10 +465,9 @@ static int asoc_dmic_probe(struct platform_device *pdev) + mutex_init(&dmic->mutex); + + dmic->fclk = devm_clk_get(dmic->dev, "fck"); +- if (IS_ERR(dmic->fclk)) { +- dev_err(dmic->dev, "can't get fck\n"); +- return -ENODEV; +- } ++ if (IS_ERR(dmic->fclk)) ++ return dev_err_probe(dmic->dev, PTR_ERR(dmic->fclk), ++ "can't get fck\n"); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma"); + if (!res) { +diff --git a/sound/soc/ti/omap-hdmi.c b/sound/soc/ti/omap-hdmi.c +--- a/sound/soc/ti/omap-hdmi.c ++++ b/sound/soc/ti/omap-hdmi.c +@@ -24,8 +24,6 @@ + #define DRV_NAME "omap-hdmi-audio" + + struct hdmi_audio_data { +- struct snd_soc_card *card; +- + const struct omap_hdmi_audio_ops *ops; + struct device *dssdev; + struct snd_dmaengine_dai_dma_data dma_data; +@@ -375,12 +373,9 @@ static int omap_hdmi_audio_probe(struct platform_device *pdev) + card->dev = dev; + + ret = devm_snd_soc_register_card(dev, card); +- if (ret) { +- dev_err(dev, "snd_soc_register_card failed (%d)\n", ret); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "snd_soc_register_card() failed\n"); + +- ad->card = card; + snd_soc_card_set_drvdata(card, ad); + + dev_set_drvdata(dev, ad); +diff --git a/sound/soc/ti/omap-twl4030.c b/sound/soc/ti/omap-twl4030.c +--- a/sound/soc/ti/omap-twl4030.c ++++ b/sound/soc/ti/omap-twl4030.c +@@ -17,7 +17,6 @@ + */ + + #include +-#include + #include + #include + +@@ -29,6 +28,7 @@ + #include "omap-mcbsp.h" + + struct omap_twl4030 { ++ struct snd_soc_jack_gpio hs_jack_gpio; + struct snd_soc_jack hs_jack; + }; + +@@ -124,38 +124,18 @@ static struct snd_soc_jack_pin hs_jack_pins[] = { + }, + }; + +-/* Headset jack detection gpios */ +-static struct snd_soc_jack_gpio hs_jack_gpios[] = { +- { +- .name = "ti,jack-det", +- .report = SND_JACK_HEADSET, +- .debounce_time = 200, +- }, +-}; +- +-static inline void twl4030_disconnect_pin(struct snd_soc_dapm_context *dapm, +- int connected, char *pin) +-{ +- if (!connected) +- snd_soc_dapm_disable_pin(dapm, pin); +-} +- + static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd) + { + struct snd_soc_card *card = rtd->card; +- struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); +- struct omap_tw4030_pdata *pdata = dev_get_platdata(card->dev); ++ + struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card); +- int ret = 0; ++ int ret; + + /* + * This is a bit of a hack, but the GPIO is optional so we + * only want to add the jack detection if the GPIO is there. + */ + if (of_property_present(card->dev->of_node, "ti,jack-det-gpio")) { +- hs_jack_gpios[0].gpiod_dev = card->dev; +- hs_jack_gpios[0].idx = 0; +- + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET, + &priv->hs_jack, hs_jack_pins, +@@ -163,36 +143,19 @@ static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd) + if (ret) + return ret; + +- ret = snd_soc_jack_add_gpios(&priv->hs_jack, +- ARRAY_SIZE(hs_jack_gpios), +- hs_jack_gpios); ++ priv->hs_jack_gpio.name = "ti,jack-det"; ++ priv->hs_jack_gpio.report = SND_JACK_HEADSET; ++ priv->hs_jack_gpio.debounce_time = 200; ++ priv->hs_jack_gpio.gpiod_dev = card->dev; ++ priv->hs_jack_gpio.idx = 0; ++ ++ ret = snd_soc_jack_add_gpios(&priv->hs_jack, 1, ++ &priv->hs_jack_gpio); + if (ret) + return ret; + } + +- /* +- * NULL pdata means we booted with DT. In this case the routing is +- * provided and the card is fully routed, no need to mark pins. +- */ +- if (!pdata || !pdata->custom_routing) +- return ret; +- +- /* Disable not connected paths if not used */ +- twl4030_disconnect_pin(dapm, pdata->has_ear, "Earpiece Spk"); +- twl4030_disconnect_pin(dapm, pdata->has_hf, "Handsfree Spk"); +- twl4030_disconnect_pin(dapm, pdata->has_hs, "Headset Stereophone"); +- twl4030_disconnect_pin(dapm, pdata->has_predriv, "Ext Spk"); +- twl4030_disconnect_pin(dapm, pdata->has_carkit, "Carkit Spk"); +- +- twl4030_disconnect_pin(dapm, pdata->has_mainmic, "Main Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_submic, "Sub Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_hsmic, "Headset Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_carkitmic, "Carkit Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_digimic0, "Digital0 Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_digimic1, "Digital1 Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_linein, "Line In"); +- +- return ret; ++ return 0; + } + + /* Digital audio interface glue - connects codec <--> CPU */ +@@ -237,11 +200,15 @@ static struct snd_soc_card omap_twl4030_card = { + + static int omap_twl4030_probe(struct platform_device *pdev) + { +- struct omap_tw4030_pdata *pdata = dev_get_platdata(&pdev->dev); +- struct device_node *node = pdev->dev.of_node; + struct snd_soc_card *card = &omap_twl4030_card; ++ struct device_node *node, *dai_node; + struct omap_twl4030 *priv; +- int ret = 0; ++ struct property *prop; ++ int ret; ++ ++ node = pdev->dev.of_node; ++ if (!node) ++ return -ENODEV; + + card->dev = &pdev->dev; + +@@ -249,69 +216,52 @@ static int omap_twl4030_probe(struct platform_device *pdev) + if (priv == NULL) + return -ENOMEM; + +- if (node) { +- struct device_node *dai_node; +- struct property *prop; ++ ret = snd_soc_of_parse_card_name(card, "ti,model"); ++ if (ret) ++ return ret; + +- if (snd_soc_of_parse_card_name(card, "ti,model")) { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } +- +- dai_node = of_parse_phandle(node, "ti,mcbsp", 0); +- if (!dai_node) { +- dev_err(&pdev->dev, "McBSP node is not provided\n"); +- return -EINVAL; +- } +- omap_twl4030_dai_links[0].cpus->dai_name = NULL; +- omap_twl4030_dai_links[0].cpus->of_node = dai_node; +- +- omap_twl4030_dai_links[0].platforms->name = NULL; +- omap_twl4030_dai_links[0].platforms->of_node = dai_node; +- +- dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0); +- if (!dai_node) { +- card->num_links = 1; +- } else { +- omap_twl4030_dai_links[1].cpus->dai_name = NULL; +- omap_twl4030_dai_links[1].cpus->of_node = dai_node; +- +- omap_twl4030_dai_links[1].platforms->name = NULL; +- omap_twl4030_dai_links[1].platforms->of_node = dai_node; +- } +- +- /* Optional: audio routing can be provided */ +- prop = of_find_property(node, "ti,audio-routing", NULL); +- if (prop) { +- ret = snd_soc_of_parse_audio_routing(card, +- "ti,audio-routing"); +- if (ret) +- return ret; +- +- card->fully_routed = 1; +- } +- } else if (pdata) { +- if (pdata->card_name) { +- card->name = pdata->card_name; +- } else { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } +- +- if (!pdata->voice_connected) +- card->num_links = 1; +- } else { +- dev_err(&pdev->dev, "Missing pdata\n"); ++ if (!card->name) { ++ dev_err(&pdev->dev, "Card name is not provided\n"); + return -ENODEV; + } + ++ dai_node = of_parse_phandle(node, "ti,mcbsp", 0); ++ if (!dai_node) { ++ dev_err(&pdev->dev, "McBSP node is not provided\n"); ++ return -EINVAL; ++ } ++ omap_twl4030_dai_links[0].cpus->dai_name = NULL; ++ omap_twl4030_dai_links[0].cpus->of_node = dai_node; ++ ++ omap_twl4030_dai_links[0].platforms->name = NULL; ++ omap_twl4030_dai_links[0].platforms->of_node = dai_node; ++ ++ dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0); ++ if (!dai_node) { ++ card->num_links = 1; ++ } else { ++ omap_twl4030_dai_links[1].cpus->dai_name = NULL; ++ omap_twl4030_dai_links[1].cpus->of_node = dai_node; ++ ++ omap_twl4030_dai_links[1].platforms->name = NULL; ++ omap_twl4030_dai_links[1].platforms->of_node = dai_node; ++ } ++ ++ /* Optional: audio routing can be provided */ ++ prop = of_find_property(node, "ti,audio-routing", NULL); ++ if (prop) { ++ ret = snd_soc_of_parse_audio_routing(card, "ti,audio-routing"); ++ if (ret) ++ return ret; ++ ++ card->fully_routed = 1; ++ } ++ + snd_soc_card_set_drvdata(card, priv); + ret = devm_snd_soc_register_card(&pdev->dev, card); +- if (ret) { +- dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n", +- ret); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "devm_snd_soc_register_card() failed\n"); + + return 0; + } +diff --git a/sound/soc/ti/rx51.c b/sound/soc/ti/rx51.c +--- a/sound/soc/ti/rx51.c ++++ b/sound/soc/ti/rx51.c +@@ -358,21 +358,22 @@ static struct snd_soc_card rx51_sound_card = { + static int rx51_soc_probe(struct platform_device *pdev) + { + struct rx51_audio_pdata *pdata; +- struct device_node *np = pdev->dev.of_node; ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node; + struct snd_soc_card *card = &rx51_sound_card; + int err; + + if (!of_machine_is_compatible("nokia,omap3-n900")) + return -ENODEV; + +- card->dev = &pdev->dev; ++ card->dev = dev; + + if (np) { + struct device_node *dai_node; + + dai_node = of_parse_phandle(np, "nokia,cpu-dai", 0); + if (!dai_node) { +- dev_err(card->dev, "McBSP node is not provided\n"); ++ dev_err(dev, "McBSP node is not provided\n"); + return -EINVAL; + } + rx51_dai[0].cpus->dai_name = NULL; +@@ -382,7 +383,7 @@ static int rx51_soc_probe(struct platform_device *pdev) + + dai_node = of_parse_phandle(np, "nokia,audio-codec", 0); + if (!dai_node) { +- dev_err(card->dev, "Codec node is not provided\n"); ++ dev_err(dev, "Codec node is not provided\n"); + return -EINVAL; + } + rx51_dai[0].codecs->name = NULL; +@@ -390,7 +391,7 @@ static int rx51_soc_probe(struct platform_device *pdev) + + dai_node = of_parse_phandle(np, "nokia,audio-codec", 1); + if (!dai_node) { +- dev_err(card->dev, "Auxiliary Codec node is not provided\n"); ++ dev_err(dev, "Auxiliary Codec node is not provided\n"); + return -EINVAL; + } + rx51_aux_dev[0].dlc.name = NULL; +@@ -400,7 +401,7 @@ static int rx51_soc_probe(struct platform_device *pdev) + + dai_node = of_parse_phandle(np, "nokia,headphone-amplifier", 0); + if (!dai_node) { +- dev_err(card->dev, "Headphone amplifier node is not provided\n"); ++ dev_err(dev, "Headphone amplifier node is not provided\n"); + return -EINVAL; + } + rx51_aux_dev[1].dlc.name = NULL; +@@ -409,40 +410,36 @@ static int rx51_soc_probe(struct platform_device *pdev) + rx51_codec_conf[1].dlc.of_node = dai_node; + } + +- pdata = devm_kzalloc(card->dev, sizeof(*pdata), GFP_KERNEL); ++ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (pdata == NULL) + return -ENOMEM; + + snd_soc_card_set_drvdata(card, pdata); + +- pdata->tvout_selection_gpio = devm_gpiod_get(card->dev, ++ pdata->tvout_selection_gpio = devm_gpiod_get(dev, + "tvout-selection", + GPIOD_OUT_LOW); +- if (IS_ERR(pdata->tvout_selection_gpio)) { +- dev_err(card->dev, "could not get tvout selection gpio\n"); +- return PTR_ERR(pdata->tvout_selection_gpio); +- } ++ if (IS_ERR(pdata->tvout_selection_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pdata->tvout_selection_gpio), ++ "could not get tvout selection gpio\n"); + +- pdata->eci_sw_gpio = devm_gpiod_get(card->dev, "eci-switch", ++ pdata->eci_sw_gpio = devm_gpiod_get(dev, "eci-switch", + GPIOD_OUT_HIGH); +- if (IS_ERR(pdata->eci_sw_gpio)) { +- dev_err(card->dev, "could not get eci switch gpio\n"); +- return PTR_ERR(pdata->eci_sw_gpio); +- } ++ if (IS_ERR(pdata->eci_sw_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pdata->eci_sw_gpio), ++ "could not get eci switch gpio\n"); + +- pdata->speaker_amp_gpio = devm_gpiod_get(card->dev, ++ pdata->speaker_amp_gpio = devm_gpiod_get(dev, + "speaker-amplifier", + GPIOD_OUT_LOW); +- if (IS_ERR(pdata->speaker_amp_gpio)) { +- dev_err(card->dev, "could not get speaker enable gpio\n"); +- return PTR_ERR(pdata->speaker_amp_gpio); +- } ++ if (IS_ERR(pdata->speaker_amp_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pdata->speaker_amp_gpio), ++ "could not get speaker enable gpio\n"); + +- err = devm_snd_soc_register_card(card->dev, card); +- if (err) { +- dev_err(card->dev, "snd_soc_register_card failed (%d)\n", err); +- return err; +- } ++ err = devm_snd_soc_register_card(dev, card); ++ if (err) ++ return dev_err_probe(dev, err, ++ "snd_soc_register_card() failed\n"); + + return 0; + } +diff --git a/sound/soc/uniphier/aio-cpu.c b/sound/soc/uniphier/aio-cpu.c +--- a/sound/soc/uniphier/aio-cpu.c ++++ b/sound/soc/uniphier/aio-cpu.c +@@ -795,16 +795,12 @@ int uniphier_aio_probe(struct platform_device *pdev) + ret = devm_snd_soc_register_component(dev, &uniphier_aio_component, + chip->chip_spec->dais, + chip->chip_spec->num_dais); +- if (ret) { +- dev_err(dev, "Register component failed.\n"); ++ if (ret) + goto err_out_reset; +- } + + ret = uniphier_aiodma_soc_register_platform(pdev); +- if (ret) { +- dev_err(dev, "Register platform failed.\n"); ++ if (ret) + goto err_out_reset; +- } + + return 0; + +diff --git a/sound/soc/uniphier/aio.h b/sound/soc/uniphier/aio.h +--- a/sound/soc/uniphier/aio.h ++++ b/sound/soc/uniphier/aio.h +@@ -156,7 +156,7 @@ struct uniphier_aio_selector { + int hw; + }; + +-/** ++/* + * 'SoftWare MAPping' setting of UniPhier AIO registers. + * + * We have to setup 'virtual' register maps to access 'real' registers of AIO. +diff --git a/sound/soc/ux500/mop500.c b/sound/soc/ux500/mop500.c +--- a/sound/soc/ux500/mop500.c ++++ b/sound/soc/ux500/mop500.c +@@ -68,10 +68,11 @@ static void mop500_of_node_put(void) + of_node_put(mop500_dai_links[0].codecs->of_node); + } + +-static int mop500_of_probe(struct platform_device *pdev, +- struct device_node *np) ++static int mop500_of_probe(struct snd_soc_card *card) + { ++ struct device *dev = card->dev; + struct device_node *codec_np, *msp_np[2]; ++ struct device_node *np = dev->of_node; + int i; + + msp_np[0] = of_parse_phandle(np, "stericsson,cpu-dai", 0); +@@ -79,7 +80,7 @@ static int mop500_of_probe(struct platform_device *pdev, + codec_np = of_parse_phandle(np, "stericsson,audio-codec", 0); + + if (!(msp_np[0] && msp_np[1] && codec_np)) { +- dev_err(&pdev->dev, "Phandle missing or invalid\n"); ++ dev_err(dev, "Phandle missing or invalid\n"); + for (i = 0; i < 2; i++) + of_node_put(msp_np[i]); + of_node_put(codec_np); +@@ -95,21 +96,20 @@ static int mop500_of_probe(struct platform_device *pdev, + mop500_dai_links[i].codecs->name = NULL; + } + +- snd_soc_of_parse_card_name(&mop500_card, "stericsson,card-name"); ++ snd_soc_of_parse_card_name(card, "stericsson,card-name"); + + return 0; + } + + static int mop500_probe(struct platform_device *pdev) + { +- struct device_node *np = pdev->dev.of_node; + int ret; + + dev_dbg(&pdev->dev, "%s: Enter.\n", __func__); + + mop500_card.dev = &pdev->dev; + +- ret = mop500_of_probe(pdev, np); ++ ret = mop500_of_probe(&mop500_card); + if (ret) + return ret; + +diff --git a/sound/soc/ux500/mop500_ab8500.c b/sound/soc/ux500/mop500_ab8500.c +--- a/sound/soc/ux500/mop500_ab8500.c ++++ b/sound/soc/ux500/mop500_ab8500.c +@@ -433,5 +433,5 @@ void mop500_ab8500_remove(struct snd_soc_card *card) + clk_put(drvdata->clk_ptr_ulpclk); + clk_put(drvdata->clk_ptr_intclk); + +- snd_soc_card_set_drvdata(card, drvdata); ++ snd_soc_card_set_drvdata(card, NULL); + } +diff --git a/sound/soc/xilinx/xlnx_formatter_pcm.c b/sound/soc/xilinx/xlnx_formatter_pcm.c +--- a/sound/soc/xilinx/xlnx_formatter_pcm.c ++++ b/sound/soc/xilinx/xlnx_formatter_pcm.c +@@ -9,8 +9,6 @@ + #include + #include + #include +-#include +-#include + #include + + #include +@@ -384,7 +382,7 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + if (err) { + dev_err(component->dev, + "Unable to set constraint on period bytes\n"); +- return err; ++ goto error; + } + + /* Resize the buffer bytes as divisible by 64 */ +@@ -394,7 +392,7 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + if (err) { + dev_err(component->dev, + "Unable to set constraint on buffer bytes\n"); +- return err; ++ goto error; + } + + /* Set periods as integer multiple */ +@@ -403,7 +401,7 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + if (err < 0) { + dev_err(component->dev, + "Unable to set constraint on periods to be integer\n"); +- return err; ++ goto error; + } + + /* enable DMA IOC irq */ +@@ -412,6 +410,14 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + writel(val, stream_data->mmio + XLNX_AUD_CTRL); + + return 0; ++ ++error: ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ++ adata->play_stream = NULL; ++ else ++ adata->capture_stream = NULL; ++ kfree(stream_data); ++ return err; + } + + static int xlnx_formatter_pcm_close(struct snd_soc_component *component, +diff --git a/sound/soc/xilinx/xlnx_i2s.c b/sound/soc/xilinx/xlnx_i2s.c +--- a/sound/soc/xilinx/xlnx_i2s.c ++++ b/sound/soc/xilinx/xlnx_i2s.c +@@ -9,9 +9,8 @@ + + #include + #include +-#include +-#include + #include ++#include + #include + #include + +@@ -174,7 +173,6 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + int ret; + u32 format; + struct device *dev = &pdev->dev; +- struct device_node *node = dev->of_node; + + drv_data = devm_kzalloc(&pdev->dev, sizeof(*drv_data), GFP_KERNEL); + if (!drv_data) +@@ -184,18 +182,16 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + if (IS_ERR(drv_data->base)) + return PTR_ERR(drv_data->base); + +- ret = of_property_read_u32(node, "xlnx,num-channels", &drv_data->channels); +- if (ret < 0) { +- dev_err(dev, "cannot get supported channels\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,num-channels", &drv_data->channels); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get supported channels\n"); ++ + drv_data->channels *= 2; + +- ret = of_property_read_u32(node, "xlnx,dwidth", &drv_data->data_width); +- if (ret < 0) { +- dev_err(dev, "cannot get data width\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,dwidth", &drv_data->data_width); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get data width\n"); ++ + switch (drv_data->data_width) { + case 16: + format = SNDRV_PCM_FMTBIT_S16_LE; +@@ -207,7 +203,7 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + return -EINVAL; + } + +- if (of_device_is_compatible(node, "xlnx,i2s-transmitter-1.0")) { ++ if (device_is_compatible(dev, "xlnx,i2s-transmitter-1.0")) { + drv_data->dai_drv.name = "xlnx_i2s_playback"; + drv_data->dai_drv.playback.stream_name = "Playback"; + drv_data->dai_drv.playback.formats = format; +@@ -215,7 +211,7 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + drv_data->dai_drv.playback.channels_max = drv_data->channels; + drv_data->dai_drv.playback.rates = SNDRV_PCM_RATE_8000_192000; + drv_data->dai_drv.ops = &xlnx_i2s_dai_ops; +- } else if (of_device_is_compatible(node, "xlnx,i2s-receiver-1.0")) { ++ } else if (device_is_compatible(dev, "xlnx,i2s-receiver-1.0")) { + drv_data->dai_drv.name = "xlnx_i2s_capture"; + drv_data->dai_drv.capture.stream_name = "Capture"; + drv_data->dai_drv.capture.formats = format; +@@ -233,10 +229,8 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, &xlnx_i2s_component, + &drv_data->dai_drv, 1); +- if (ret) { +- dev_err(&pdev->dev, "i2s component registration failed\n"); ++ if (ret) + return ret; +- } + + dev_info(&pdev->dev, "%s DAI registered\n", drv_data->dai_drv.name); + +diff --git a/sound/soc/xilinx/xlnx_spdif.c b/sound/soc/xilinx/xlnx_spdif.c +--- a/sound/soc/xilinx/xlnx_spdif.c ++++ b/sound/soc/xilinx/xlnx_spdif.c +@@ -10,8 +10,7 @@ + #include + #include + #include +-#include +-#include ++#include + #include + #include + #include +@@ -242,28 +241,24 @@ static int xlnx_spdif_probe(struct platform_device *pdev) + struct spdif_dev_data *ctx; + + struct device *dev = &pdev->dev; +- struct device_node *node = dev->of_node; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->axi_clk = devm_clk_get_enabled(dev, "s_axi_aclk"); +- if (IS_ERR(ctx->axi_clk)) { +- ret = PTR_ERR(ctx->axi_clk); +- dev_err(dev, "failed to get s_axi_aclk(%d)\n", ret); +- return ret; +- } ++ if (IS_ERR(ctx->axi_clk)) ++ return dev_err_probe(dev, PTR_ERR(ctx->axi_clk), ++ "failed to get s_axi_aclk\n"); + + ctx->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ctx->base)) + return PTR_ERR(ctx->base); + +- ret = of_property_read_u32(node, "xlnx,spdif-mode", &ctx->mode); +- if (ret < 0) { +- dev_err(dev, "cannot get SPDIF mode\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,spdif-mode", &ctx->mode); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get SPDIF mode\n"); ++ + if (ctx->mode) { + dai_drv = &xlnx_spdif_tx_dai; + } else { +@@ -274,29 +269,23 @@ static int xlnx_spdif_probe(struct platform_device *pdev) + ret = devm_request_irq(dev, ret, + xlnx_spdifrx_irq_handler, + 0, "XLNX_SPDIF_RX", ctx); +- if (ret) { +- dev_err(dev, "spdif rx irq request failed\n"); +- return -ENODEV; +- } ++ if (ret) ++ return ret; + + init_waitqueue_head(&ctx->chsts_q); + dai_drv = &xlnx_spdif_rx_dai; + } + +- ret = of_property_read_u32(node, "xlnx,aud_clk_i", &ctx->aclk); +- if (ret < 0) { +- dev_err(dev, "cannot get aud_clk_i value\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,aud_clk_i", &ctx->aclk); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get aud_clk_i value\n"); + + dev_set_drvdata(dev, ctx); + + ret = devm_snd_soc_register_component(dev, &xlnx_spdif_component, + dai_drv, 1); +- if (ret) { +- dev_err(dev, "SPDIF component registration failed\n"); ++ if (ret) + return ret; +- } + + writel(XSPDIF_SOFT_RESET_VALUE, ctx->base + XSPDIF_SOFT_RESET_REG); + dev_info(dev, "%s DAI registered\n", dai_drv->name); +diff --git a/sound/soc/xtensa/xtfpga-i2s.c b/sound/soc/xtensa/xtfpga-i2s.c +--- a/sound/soc/xtensa/xtfpga-i2s.c ++++ b/sound/soc/xtensa/xtfpga-i2s.c +@@ -533,34 +533,25 @@ static int xtfpga_i2s_probe(struct platform_device *pdev) + int err, irq; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); +- if (!i2s) { +- err = -ENOMEM; +- goto err; +- } ++ if (!i2s) ++ return -ENOMEM; ++ + platform_set_drvdata(pdev, i2s); + i2s->dev = &pdev->dev; + dev_dbg(&pdev->dev, "dev: %p, i2s: %p\n", &pdev->dev, i2s); + + i2s->regs = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(i2s->regs)) { +- err = PTR_ERR(i2s->regs); +- goto err; +- } ++ if (IS_ERR(i2s->regs)) ++ return PTR_ERR(i2s->regs); + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->regs, + &xtfpga_i2s_regmap_config); +- if (IS_ERR(i2s->regmap)) { +- dev_err(&pdev->dev, "regmap init failed\n"); +- err = PTR_ERR(i2s->regmap); +- goto err; +- } ++ if (IS_ERR(i2s->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->regmap), "regmap init failed\n"); + + i2s->clk = devm_clk_get(&pdev->dev, NULL); +- if (IS_ERR(i2s->clk)) { +- dev_err(&pdev->dev, "couldn't get clock\n"); +- err = PTR_ERR(i2s->clk); +- goto err; +- } ++ if (IS_ERR(i2s->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->clk), "couldn't get clock\n"); + + regmap_write(i2s->regmap, XTFPGA_I2S_CONFIG, + (0x1 << XTFPGA_I2S_CONFIG_CHANNEL_BASE)); +@@ -568,41 +559,34 @@ static int xtfpga_i2s_probe(struct platform_device *pdev) + regmap_write(i2s->regmap, XTFPGA_I2S_INT_MASK, XTFPGA_I2S_INT_UNDERRUN); + + irq = platform_get_irq(pdev, 0); +- if (irq < 0) { +- err = irq; +- goto err; +- } ++ if (irq < 0) ++ return irq; ++ + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + xtfpga_i2s_threaded_irq_handler, + IRQF_SHARED | IRQF_ONESHOT, + pdev->name, i2s); +- if (err < 0) { +- dev_err(&pdev->dev, "request_irq failed\n"); +- goto err; +- } ++ if (err < 0) ++ return err; + + err = devm_snd_soc_register_component(&pdev->dev, + &xtfpga_i2s_component, + xtfpga_i2s_dai, + ARRAY_SIZE(xtfpga_i2s_dai)); +- if (err < 0) { +- dev_err(&pdev->dev, "couldn't register component\n"); +- goto err; +- } ++ if (err < 0) ++ return err; ++ + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + err = xtfpga_i2s_runtime_resume(&pdev->dev); +- if (err) +- goto err_pm_disable; ++ if (err) { ++ pm_runtime_disable(&pdev->dev); ++ return err; ++ } + } +- return 0; + +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +-err: +- dev_err(&pdev->dev, "%s: err = %d\n", __func__, err); +- return err; ++ return 0; + } + + static void xtfpga_i2s_remove(struct platform_device *pdev) +diff --git a/sound/sparc/dbri.c b/sound/sparc/dbri.c +--- a/sound/sparc/dbri.c ++++ b/sound/sparc/dbri.c +@@ -1840,6 +1840,7 @@ static void dbri_process_one_interrupt(struct snd_dbri *dbri, int x) + int rval = D_INTR_GETRVAL(x); + #endif + ++#ifdef DBRI_DEBUG + if (channel == D_INTR_CMD) { + dprintk(D_CMD, "INTR: Command: %-5s Value:%d\n", + cmds[command], val); +@@ -1847,6 +1848,7 @@ static void dbri_process_one_interrupt(struct snd_dbri *dbri, int x) + dprintk(D_INT, "INTR: Chan:%d Code:%d Val:%#x\n", + channel, code, rval); + } ++#endif + + switch (code) { + case D_INTR_CMDI: +diff --git a/sound/usb/6fire/firmware.c b/sound/usb/6fire/firmware.c +--- a/sound/usb/6fire/firmware.c ++++ b/sound/usb/6fire/firmware.c +@@ -194,23 +194,20 @@ static int usb6fire_fw_ezusb_upload( + int ret; + u8 data; + struct usb_device *device = interface_to_usbdev(intf); +- const struct firmware *fw = NULL; +- struct ihex_record *rec = kmalloc_obj(struct ihex_record); ++ struct ihex_record *rec __free(kfree) = kmalloc_obj(struct ihex_record); + + if (!rec) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, fwname, &device->dev); + if (ret < 0) { +- kfree(rec); + dev_err(&intf->dev, + "error requesting ezusb firmware %s.\n", fwname); + return ret; + } + ret = usb6fire_fw_ihex_init(fw, rec); + if (ret < 0) { +- kfree(rec); +- release_firmware(fw); + dev_err(&intf->dev, + "error validating ezusb firmware %s.\n", fwname); + return ret; +@@ -219,8 +216,6 @@ static int usb6fire_fw_ezusb_upload( + data = 0x01; /* stop ezusb cpu */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1); + if (ret) { +- kfree(rec); +- release_firmware(fw); + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: begin message.\n", + fwname); +@@ -231,8 +226,6 @@ static int usb6fire_fw_ezusb_upload( + ret = usb6fire_fw_ezusb_write(device, 0xa0, rec->address, + rec->data, rec->len); + if (ret) { +- kfree(rec); +- release_firmware(fw); + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: data urb.\n", + fwname); +@@ -240,8 +233,6 @@ static int usb6fire_fw_ezusb_upload( + } + } + +- release_firmware(fw); +- kfree(rec); + if (postdata) { /* write data after firmware has been uploaded */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, postaddr, + postdata, postlen); +@@ -270,19 +261,18 @@ static int usb6fire_fw_fpga_upload( + int ret; + int i; + struct usb_device *device = interface_to_usbdev(intf); +- u8 *buffer = kmalloc(FPGA_BUFSIZE, GFP_KERNEL); ++ u8 *buffer __free(kfree) = kmalloc(FPGA_BUFSIZE, GFP_KERNEL); + const char *c; + const char *end; +- const struct firmware *fw; + + if (!buffer) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, fwname, &device->dev); + if (ret < 0) { + dev_err(&intf->dev, "unable to get fpga firmware %s.\n", + fwname); +- kfree(buffer); + return -EIO; + } + +@@ -291,8 +281,6 @@ static int usb6fire_fw_fpga_upload( + + ret = usb6fire_fw_ezusb_write(device, 8, 0, NULL, 0); + if (ret) { +- kfree(buffer); +- release_firmware(fw); + dev_err(&intf->dev, + "unable to upload fpga firmware: begin urb.\n"); + return ret; +@@ -304,15 +292,11 @@ static int usb6fire_fw_fpga_upload( + + ret = usb6fire_fw_fpga_write(device, buffer, i); + if (ret < 0) { +- release_firmware(fw); +- kfree(buffer); + dev_err(&intf->dev, + "unable to upload fpga firmware: fw urb.\n"); + return ret; + } + } +- release_firmware(fw); +- kfree(buffer); + + ret = usb6fire_fw_ezusb_write(device, 9, 0, NULL, 0); + if (ret) { +diff --git a/sound/usb/card.c b/sound/usb/card.c +--- a/sound/usb/card.c ++++ b/sound/usb/card.c +@@ -715,7 +715,7 @@ static void snd_usb_init_quirk_flags(int idx, struct snd_usb_audio *chip) + + /* old style option found: the position-based integer value */ + if (quirk_flags[idx] && +- !kstrtou32(quirk_flags[idx], 0, &chip->quirk_flags)) { ++ !kstrtou64(quirk_flags[idx], 0, &chip->quirk_flags)) { + snd_usb_apply_flag_dbg("module param", chip, chip->quirk_flags); + return; + } +@@ -905,6 +905,40 @@ static int try_to_register_card(struct snd_usb_audio *chip, int ifnum) + return 0; + } + ++static void usb_audio_disconnect_components(struct snd_usb_audio *chip) ++{ ++ struct snd_usb_stream *as; ++ struct snd_usb_endpoint *ep; ++ struct usb_mixer_interface *mixer; ++ struct list_head *p; ++ ++ /* release the pcm resources */ ++ list_for_each_entry(as, &chip->pcm_list, list) { ++ snd_usb_stream_disconnect(as); ++ } ++ /* release the endpoint resources */ ++ list_for_each_entry(ep, &chip->ep_list, list) { ++ snd_usb_endpoint_release(ep); ++ } ++ /* release the midi resources */ ++ list_for_each(p, &chip->midi_list) { ++ snd_usbmidi_disconnect(p); ++ } ++ snd_usb_midi_v2_disconnect_all(chip); ++ /* ++ * Nice to check quirk && quirk->shares_media_device and ++ * then call the snd_media_device_delete(). Don't have ++ * access to the quirk here. snd_media_device_delete() ++ * accesses mixer_list ++ */ ++ snd_media_device_delete(chip); ++ ++ /* release mixer resources */ ++ list_for_each_entry(mixer, &chip->mixer_list, list) { ++ snd_usb_mixer_disconnect(mixer); ++ } ++} ++ + /* + * probe the active usb device + * +@@ -1077,8 +1111,10 @@ static int usb_audio_probe(struct usb_interface *intf, + * decrement before memory is possibly returned. + */ + atomic_dec(&chip->active); +- if (!chip->num_interfaces) ++ if (!chip->num_interfaces) { ++ usb_audio_disconnect_components(chip); + snd_card_free(chip->card); ++ } + } + return err; + } +@@ -1091,48 +1127,18 @@ static bool __usb_audio_disconnect(struct usb_interface *intf, + struct snd_usb_audio *chip, + struct snd_card *card) + { +- struct list_head *p; +- + guard(mutex)(®ister_mutex); + + if (platform_ops && platform_ops->disconnect_cb) + platform_ops->disconnect_cb(chip); + + if (atomic_inc_return(&chip->shutdown) == 1) { +- struct snd_usb_stream *as; +- struct snd_usb_endpoint *ep; +- struct usb_mixer_interface *mixer; +- + /* wait until all pending tasks done; + * they are protected by snd_usb_lock_shutdown() + */ + snd_refcount_sync(&chip->usage_count); + snd_card_disconnect(card); +- /* release the pcm resources */ +- list_for_each_entry(as, &chip->pcm_list, list) { +- snd_usb_stream_disconnect(as); +- } +- /* release the endpoint resources */ +- list_for_each_entry(ep, &chip->ep_list, list) { +- snd_usb_endpoint_release(ep); +- } +- /* release the midi resources */ +- list_for_each(p, &chip->midi_list) { +- snd_usbmidi_disconnect(p); +- } +- snd_usb_midi_v2_disconnect_all(chip); +- /* +- * Nice to check quirk && quirk->shares_media_device and +- * then call the snd_media_device_delete(). Don't have +- * access to the quirk here. snd_media_device_delete() +- * accesses mixer_list +- */ +- snd_media_device_delete(chip); +- +- /* release mixer resources */ +- list_for_each_entry(mixer, &chip->mixer_list, list) { +- snd_usb_mixer_disconnect(mixer); +- } ++ usb_audio_disconnect_components(chip); + } + + if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND) +diff --git a/sound/usb/clock.c b/sound/usb/clock.c +--- a/sound/usb/clock.c ++++ b/sound/usb/clock.c +@@ -600,7 +600,7 @@ int snd_usb_set_sample_rate_v2v3(struct snd_usb_audio *chip, + static int set_sample_rate_v2v3(struct snd_usb_audio *chip, + const struct audioformat *fmt, int rate) + { +- int cur_rate, prev_rate; ++ int cur_rate, prev_rate = 0; + int clock; + + /* First, try to find a valid clock. This may trigger +@@ -625,9 +625,12 @@ static int set_sample_rate_v2v3(struct snd_usb_audio *chip, + return clock; + } + +- prev_rate = get_sample_rate_v2v3(chip, fmt->iface, fmt->altsetting, clock); +- if (prev_rate == rate) +- goto validation; ++ if (!(chip->quirk_flags & QUIRK_FLAG_ALWAYS_SET_RATE)) { ++ prev_rate = get_sample_rate_v2v3(chip, fmt->iface, ++ fmt->altsetting, clock); ++ if (prev_rate == rate) ++ goto validation; ++ } + + cur_rate = snd_usb_set_sample_rate_v2v3(chip, fmt, clock, rate); + if (cur_rate < 0) { +diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c +--- a/sound/usb/endpoint.c ++++ b/sound/usb/endpoint.c +@@ -1236,7 +1236,10 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) + /* try to use enough URBs to contain an entire ALSA buffer */ + max_urbs = min((unsigned) MAX_URBS, + MAX_QUEUE * packs_per_ms / urb_packs); +- ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods); ++ if (chip->quirk_flags & QUIRK_FLAG_PLAYBACK_URB_FIXUP) ++ ep->nurbs = MAX_URBS; ++ else ++ ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods); + } + + /* allocate and initialize data urbs */ +@@ -1260,6 +1263,8 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) + goto out_of_memory; + u->urb->pipe = ep->pipe; + u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; ++ if (chip->quirk_flags & QUIRK_FLAG_PLAYBACK_URB_FIXUP) ++ u->urb->transfer_flags |= URB_ISO_ASAP; + u->urb->interval = 1 << ep->datainterval; + u->urb->context = u; + u->urb->complete = snd_complete_urb; +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -505,6 +505,19 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { + {} + }; + ++/* Audient iD14: FU 12 advertises Volume on only 4 of its 6 logical channels ++ * and sits on the monitor mixer branch, but it is traced through to the ++ * Speaker output terminal and gets named "Speaker Playback Volume". Userspace ++ * then adopts it as the stream's hardware volume, and any setting below 0 dB ++ * attenuates some channels but not others (20 dB imbalance at 80%). Give it a ++ * non-standard name so that it is no longer taken for the stream's master ++ * volume, while remaining reachable for anyone who wants the monitor gain. ++ */ ++static const struct usbmix_name_map audient_id14_map[] = { ++ { 12, "Monitor Mix Playback" }, /* FU, partial coverage */ ++ {} ++}; ++ + /* + * Control map entries + */ +@@ -588,6 +601,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x2573, 0x0008), + .map = maya44_map, + }, ++ { ++ /* Audient iD14 */ ++ .id = USB_ID(0x2708, 0x0008), ++ .map = audient_id14_map, ++ }, + { + /* KEF X300A */ + .id = USB_ID(0x27ac, 0x1000), +diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c +--- a/sound/usb/mixer_quirks.c ++++ b/sound/usb/mixer_quirks.c +@@ -3920,6 +3920,7 @@ static int snd_rme_digiface_controls_create(struct usb_mixer_interface *mixer) + #define SND_DJM_450_IDX 0x5 + #define SND_DJM_A9_IDX 0x6 + #define SND_DJM_V10_IDX 0x7 ++#define SND_DJM_S11_IDX 0x8 + + #define SND_DJM_CTL(_name, suffix, _default_value, _windex) { \ + .name = _name, \ +@@ -4260,6 +4261,21 @@ static const struct snd_djm_ctl snd_djm_ctls_v10[] = { + // playback channels are fixed and controlled by hardware knobs on the mixer + }; + ++// DJM-S11 ++static const u16 snd_djm_opts_s11_cap1[] = { ++ 0x0100, 0x0103, 0x0106, 0x0107, 0x0108, 0x0109, 0x010d }; ++static const u16 snd_djm_opts_s11_cap2[] = { ++ 0x0200, 0x0203, 0x0206, 0x0207, 0x0208, 0x0209, 0x020d }; ++static const u16 snd_djm_opts_s11_cap3[] = { ++ 0x0307, 0x0308, 0x0309, 0x030a, 0x030d, 0x0311, 0x0312 }; ++ ++static const struct snd_djm_ctl snd_djm_ctls_s11[] = { ++ SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), ++ SND_DJM_CTL("Input 1 Capture Switch", s11_cap1, 1, SND_DJM_WINDEX_CAP), ++ SND_DJM_CTL("Input 2 Capture Switch", s11_cap2, 1, SND_DJM_WINDEX_CAP), ++ SND_DJM_CTL("Input 3 Capture Switch", s11_cap3, 3, SND_DJM_WINDEX_CAP) ++}; ++ + static const struct snd_djm_device snd_djm_devices[] = { + [SND_DJM_250MK2_IDX] = SND_DJM_DEVICE(250mk2), + [SND_DJM_750_IDX] = SND_DJM_DEVICE(750), +@@ -4269,6 +4285,7 @@ static const struct snd_djm_device snd_djm_devices[] = { + [SND_DJM_450_IDX] = SND_DJM_DEVICE(450), + [SND_DJM_A9_IDX] = SND_DJM_DEVICE(a9), + [SND_DJM_V10_IDX] = SND_DJM_DEVICE(v10), ++ [SND_DJM_S11_IDX] = SND_DJM_DEVICE(s11), + }; + + static int snd_djm_controls_info(struct snd_kcontrol *kctl, +@@ -4573,6 +4590,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) + case USB_ID(0x2b73, 0x0034): /* Pioneer DJ DJM-V10 */ + err = snd_djm_controls_create(mixer, SND_DJM_V10_IDX); + break; ++ case USB_ID(0x2b73, 0x0037): /* Pioneer DJ DJM-S11 */ ++ err = snd_djm_controls_create(mixer, SND_DJM_S11_IDX); ++ break; + case USB_ID(0x03f0, 0x0269): /* HP TB Dock G2 */ + err = hp_dock_mixer_create(mixer); + break; +diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h +--- a/sound/usb/quirks-table.h ++++ b/sound/usb/quirks-table.h +@@ -3320,6 +3320,73 @@ YAMAHA_DEVICE(0x7010, "UB99"), + } + } + }, ++{ ++ /* ++ * Pioneer DJ / AlphaTheta DJM-S11 ++ */ ++ USB_DEVICE(0x2b73, 0x0037), ++ QUIRK_DRIVER_INFO { ++ QUIRK_DATA_COMPOSITE { ++ { ++ QUIRK_DATA_STANDARD_MIXER(0) ++ }, ++ { ++ QUIRK_DATA_AUDIOFORMAT(1) { ++ .formats = SNDRV_PCM_FMTBIT_S24_3LE, ++ .channels = 14, ++ .iface = 1, ++ .altsetting = 1, ++ .altset_idx = 1, ++ .endpoint = 0x01, ++ .ep_attr = USB_ENDPOINT_XFER_ISOC | ++ USB_ENDPOINT_SYNC_ASYNC, ++ .rates = SNDRV_PCM_RATE_48000, ++ .rate_min = 48000, ++ .rate_max = 48000, ++ .nr_rates = 1, ++ .rate_table = (unsigned int[]) { 48000 }, ++ .clock = 1, ++ .fmt_type = UAC_FORMAT_TYPE_I ++ } ++ }, ++ { ++ QUIRK_DATA_AUDIOFORMAT(2) { ++ .formats = SNDRV_PCM_FMTBIT_S24_3LE, ++ .channels = 10, ++ .iface = 2, ++ .altsetting = 1, ++ .altset_idx = 1, ++ .endpoint = 0x82, ++ .ep_attr = USB_ENDPOINT_XFER_ISOC | ++ USB_ENDPOINT_SYNC_ASYNC | ++ USB_ENDPOINT_USAGE_IMPLICIT_FB, ++ .rates = SNDRV_PCM_RATE_48000, ++ .rate_min = 48000, ++ .rate_max = 48000, ++ .nr_rates = 1, ++ .rate_table = (unsigned int[]) { 48000 }, ++ .clock = 1, ++ .fmt_type = UAC_FORMAT_TYPE_I ++ } ++ }, ++ { ++ /* Audio Control (unknown purpose) */ ++ .ifnum = 3, ++ .type = QUIRK_IGNORE_INTERFACE ++ }, ++ { ++ .ifnum = 4, ++ .type = QUIRK_MIDI_STANDARD_INTERFACE ++ }, ++ { ++ /* HID */ ++ .ifnum = 5, ++ .type = QUIRK_IGNORE_INTERFACE ++ }, ++ QUIRK_COMPOSITE_END ++ } ++ } ++}, + + /* + * MacroSilicon MS2100/MS2106 based AV capture cards +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -2145,7 +2145,7 @@ struct usb_string_match { + + struct usb_audio_quirk_flags_table { + u32 id; +- u32 flags; ++ u64 flags; + const struct usb_string_match *usb_string_match; + }; + +@@ -2314,7 +2314,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x0661, 0x0883, /* iBasso DC04 Ultra */ + QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x0666, 0x0880, /* SPACETOUCH USB Audio */ +- QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), ++ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY | ++ QUIRK_FLAG_CTL_MSG_DELAY_5M), + DEVICE_FLG(0x06f8, 0xb000, /* Hercules DJ Console (Windows Edition) */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x06f8, 0xd002, /* Hercules DJ Console (Macintosh Edition) */ +@@ -2337,6 +2338,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x0951, 0x16ad, /* Kingston HyperX */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), ++ DEVICE_FLG(0x0a73, 0x003a, /* Mackie DLZ Creator XS */ ++ QUIRK_FLAG_ALWAYS_SET_RATE), + DEVICE_FLG(0x0b05, 0x18a6, /* ASUSTek Computer, Inc. */ + QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x0b0e, 0x0349, /* Jabra 550a */ +@@ -2432,6 +2435,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), + DEVICE_FLG(0x1bcf, 0x2283, /* NexiGo N930AF FHD Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), ++ DEVICE_FLG(0x1e0b, 0xd01e, /* Generic USB Audio Device */ ++ QUIRK_FLAG_PLAYBACK_URB_FIXUP), + DEVICE_FLG(0x1ff7, 0x0f81, /* SC13A Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x2040, 0x7200, /* Hauppauge HVR-950Q */ +@@ -2528,6 +2533,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */ + QUIRK_FLAG_ALIGN_TRANSFER), ++ DEVICE_FLG(0x84ef, 0x002a, /* Valeton GP-200 */ ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x84ef, 0x0082, /* Hotone Audio Pulze Mini */ + QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL | QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL), + +@@ -2641,18 +2648,12 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { + QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), + QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY), + QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN), ++ QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), ++ QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + NULL + }; + +-const char *snd_usb_quirk_flag_find_name(unsigned long index) +-{ +- if (index >= ARRAY_SIZE(snd_usb_audio_quirk_flag_names)) +- return NULL; +- +- return snd_usb_audio_quirk_flag_names[index]; +-} +- +-u32 snd_usb_quirk_flags_from_name(const char *name) ++static u64 snd_usb_quirk_flags_from_name(const char *name) + { + int i; + +@@ -2661,7 +2662,7 @@ u32 snd_usb_quirk_flags_from_name(const char *name) + + for (i = 0; snd_usb_audio_quirk_flag_names[i]; i++) { + if (strcasecmp(name, snd_usb_audio_quirk_flag_names[i]) == 0) +- return BIT_U32(i); ++ return BIT_U64(i); + } + + return 0; +@@ -2719,7 +2720,7 @@ void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + { + u16 chip_vid = USB_ID_VENDOR(chip->usb_id); + u16 chip_pid = USB_ID_PRODUCT(chip->usb_id); +- u32 mask_flags, unmask_flags, bit; ++ u64 mask_flags, unmask_flags, bit; + char *p, *field, *flag; + bool is_unmask; + u16 vid, pid; +@@ -2773,7 +2774,7 @@ void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + is_unmask = false; + } + +- if (!kstrtou32(flag, 16, &bit)) { ++ if (!kstrtou64(flag, 16, &bit)) { + if (is_unmask) + unmask_flags |= bit; + else +diff --git a/sound/usb/quirks.h b/sound/usb/quirks.h +--- a/sound/usb/quirks.h ++++ b/sound/usb/quirks.h +@@ -56,7 +56,4 @@ void snd_usb_init_quirk_flags_table(struct snd_usb_audio *chip); + void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + const char *str); + +-const char *snd_usb_quirk_flag_find_name(unsigned long flag); +-u32 snd_usb_quirk_flags_from_name(const char *name); +- + #endif /* __USBAUDIO_QUIRKS_H */ +diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h +--- a/sound/usb/usbaudio.h ++++ b/sound/usb/usbaudio.h +@@ -44,7 +44,7 @@ struct snd_usb_audio { + atomic_t active; + atomic_t shutdown; + struct snd_refcount usage_count; +- unsigned int quirk_flags; ++ u64 quirk_flags; + unsigned int need_delayed_register:1; /* warn for delayed registration */ + int num_interfaces; + int last_iface; +@@ -254,6 +254,16 @@ extern bool snd_usb_skip_validation; + * check being non-fatal and only disabling GET_CUR instead of the whole mixer. + * The current volume will then be provided by the internal cache that stores + * the last set volume ++ * QUIRK_FLAG_PLAYBACK_URB_FIXUP ++ * Set URB_ISO_ASAP flag for isochronous URBs and force nurbs to MAX_URBS. ++ * This is needed for devices that exhibit boot-time audio stuttering due ++ * to insufficient buffer depth combined with xHCI scheduling variability. ++ * The larger buffer (MAX_URBS = 12, ~64ms) absorbs system scheduling ++ * jitter during boot, while URB_ISO_ASAP ensures consistent xHCI scheduling. ++ * QUIRK_FLAG_ALWAYS_SET_RATE: ++ * Issue SET_CUR for the sample rate even when the clock already reports the ++ * requested rate. A device advertising a single rate is otherwise never sent ++ * the request at all, and some require it before streaming will start. + */ + + enum { +@@ -288,10 +298,12 @@ enum { + QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, + QUIRK_TYPE_IFB_SILENCE_ON_EMPTY = 29, + QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, ++ QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, ++ QUIRK_TYPE_ALWAYS_SET_RATE = 32, + /* Please also edit snd_usb_audio_quirk_flag_names */ + }; + +-#define QUIRK_FLAG(x) BIT_U32(QUIRK_TYPE_ ## x) ++#define QUIRK_FLAG(x) BIT_U64(QUIRK_TYPE_ ## x) + + #define QUIRK_FLAG_GET_SAMPLE_RATE QUIRK_FLAG(GET_SAMPLE_RATE) + #define QUIRK_FLAG_SHARE_MEDIA_DEVICE QUIRK_FLAG(SHARE_MEDIA_DEVICE) +@@ -324,5 +336,7 @@ enum { + #define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) + #define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY QUIRK_FLAG(IFB_SILENCE_ON_EMPTY) + #define QUIRK_FLAG_MIXER_GET_CUR_BROKEN QUIRK_FLAG(MIXER_GET_CUR_BROKEN) ++#define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) ++#define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) + + #endif /* __USBAUDIO_H */ +diff --git a/sound/usb/usx2y/us144mkii_pcm.h b/sound/usb/usx2y/us144mkii_pcm.h +--- a/sound/usb/usx2y/us144mkii_pcm.h ++++ b/sound/usb/usx2y/us144mkii_pcm.h +@@ -7,7 +7,7 @@ + #include "us144mkii.h" + + /** +- * tascam_pcm_hw - Hardware capabilities for TASCAM US-144MKII PCM. ++ * var tascam_pcm_hw - Hardware capabilities for TASCAM US-144MKII PCM. + * + * Defines the supported PCM formats, rates, channels, and buffer/period sizes + * for the TASCAM US-144MKII audio interface. +@@ -15,14 +15,14 @@ + extern const struct snd_pcm_hardware tascam_pcm_hw; + + /** +- * tascam_playback_ops - ALSA PCM operations for playback. ++ * var tascam_playback_ops - ALSA PCM operations for playback. + * + * This structure defines the callback functions for playback stream operations. + */ + extern const struct snd_pcm_ops tascam_playback_ops; + + /** +- * tascam_capture_ops - ALSA PCM operations for capture. ++ * var tascam_capture_ops - ALSA PCM operations for capture. + * + * This structure defines the callback functions for capture stream operations. + */ +diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c +--- a/sound/usb/usx2y/usbusx2yaudio.c ++++ b/sound/usb/usx2y/usbusx2yaudio.c +@@ -167,7 +167,6 @@ static int usx2y_urb_submit(struct snd_usx2y_substream *subs, struct urb *urb, i + if (!urb) + return -ENODEV; + urb->start_frame = frame + NRURBS * nr_of_packs(); // let hcd do rollover sanity checks +- urb->hcpriv = NULL; + urb->dev = subs->usx2y->dev; /* we need to set this at each time */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { diff --git a/pkgbuilds/linux-omarchy/0511-sound-updates-fixes.patch b/pkgbuilds/linux-omarchy/0511-sound-updates-fixes.patch new file mode 100644 index 0000000..c32b5c3 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0511-sound-updates-fixes.patch @@ -0,0 +1,1655 @@ +diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +--- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h ++++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +@@ -237,34 +237,34 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 70 + +-#define LAPSS_CLK_ID_QAIF_IF0_IBIT 71 +-#define LAPSS_CLK_ID_QAIF_IF0_EBIT 72 +-#define LAPSS_CLK_ID_QAIF_IF1_IBIT 73 +-#define LAPSS_CLK_ID_QAIF_IF1_EBIT 74 +-#define LAPSS_CLK_ID_QAIF_IF2_IBIT 75 +-#define LAPSS_CLK_ID_QAIF_IF2_EBIT 76 +-#define LAPSS_CLK_ID_QAIF_IF3_IBIT 77 +-#define LAPSS_CLK_ID_QAIF_IF3_EBIT 78 +-#define LAPSS_CLK_ID_QAIF_IF4_IBIT 79 +-#define LAPSS_CLK_ID_QAIF_IF4_EBIT 80 +-#define LAPSS_CLK_ID_QAIF_IF5_IBIT 81 +-#define LAPSS_CLK_ID_QAIF_IF5_EBIT 82 +-#define LAPSS_CLK_ID_QAIF_IF6_IBIT 83 +-#define LAPSS_CLK_ID_QAIF_IF6_EBIT 84 +-#define LAPSS_CLK_ID_QAIF_IF7_IBIT 85 +-#define LAPSS_CLK_ID_QAIF_IF7_EBIT 86 +-#define LAPSS_CLK_ID_QAIF_IF8_IBIT 87 +-#define LAPSS_CLK_ID_QAIF_IF8_EBIT 88 +-#define LAPSS_CLK_ID_QAIF_IF9_IBIT 89 +-#define LAPSS_CLK_ID_QAIF_IF9_EBIT 90 +-#define LAPSS_CLK_ID_QAIF_IF10_IBIT 91 +-#define LAPSS_CLK_ID_QAIF_IF10_EBIT 92 +-#define LAPSS_CLK_ID_QAIF_IF11_IBIT 93 +-#define LAPSS_CLK_ID_QAIF_IF11_EBIT 94 +-#define LAPSS_CLK_ID_QAIF_IF12_IBIT 95 +-#define LAPSS_CLK_ID_QAIF_IF12_EBIT 96 +-#define LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 97 +-#define LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 98 ++#define LPASS_CLK_ID_QAIF_IF0_IBIT 71 ++#define LPASS_CLK_ID_QAIF_IF0_EBIT 72 ++#define LPASS_CLK_ID_QAIF_IF1_IBIT 73 ++#define LPASS_CLK_ID_QAIF_IF1_EBIT 74 ++#define LPASS_CLK_ID_QAIF_IF2_IBIT 75 ++#define LPASS_CLK_ID_QAIF_IF2_EBIT 76 ++#define LPASS_CLK_ID_QAIF_IF3_IBIT 77 ++#define LPASS_CLK_ID_QAIF_IF3_EBIT 78 ++#define LPASS_CLK_ID_QAIF_IF4_IBIT 79 ++#define LPASS_CLK_ID_QAIF_IF4_EBIT 80 ++#define LPASS_CLK_ID_QAIF_IF5_IBIT 81 ++#define LPASS_CLK_ID_QAIF_IF5_EBIT 82 ++#define LPASS_CLK_ID_QAIF_IF6_IBIT 83 ++#define LPASS_CLK_ID_QAIF_IF6_EBIT 84 ++#define LPASS_CLK_ID_QAIF_IF7_IBIT 85 ++#define LPASS_CLK_ID_QAIF_IF7_EBIT 86 ++#define LPASS_CLK_ID_QAIF_IF8_IBIT 87 ++#define LPASS_CLK_ID_QAIF_IF8_EBIT 88 ++#define LPASS_CLK_ID_QAIF_IF9_IBIT 89 ++#define LPASS_CLK_ID_QAIF_IF9_EBIT 90 ++#define LPASS_CLK_ID_QAIF_IF10_IBIT 91 ++#define LPASS_CLK_ID_QAIF_IF10_EBIT 92 ++#define LPASS_CLK_ID_QAIF_IF11_IBIT 93 ++#define LPASS_CLK_ID_QAIF_IF11_EBIT 94 ++#define LPASS_CLK_ID_QAIF_IF12_IBIT 95 ++#define LPASS_CLK_ID_QAIF_IF12_EBIT 96 ++#define LPASS_CLK_ID_VA_QAIF_IF0_IBIT 97 ++#define LPASS_CLK_ID_VA_QAIF_IF0_EBIT 98 + + #define LPASS_HW_AVTIMER_VOTE 101 + #define LPASS_HW_MACRO_VOTE 102 +diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h +--- a/include/sound/rawmidi.h ++++ b/include/sound/rawmidi.h +@@ -179,7 +179,8 @@ int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info + int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, + int mode, struct snd_rawmidi_file *rfile, + int depth); +-int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile); ++int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, ++ int depth); + int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params); + int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream, +@@ -201,6 +202,11 @@ static inline int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, + return snd_rawmidi_kernel_open_nested(rmidi, subdevice, mode, rfile, 0); + } + ++static inline int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) ++{ ++ return snd_rawmidi_kernel_release_nested(rfile, 0); ++} ++ + /* set up the tied devices */ + static inline void snd_rawmidi_tie_devices(struct snd_rawmidi *r1, + struct snd_rawmidi *r2) +diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c +--- a/sound/aoa/soundbus/i2sbus/core.c ++++ b/sound/aoa/soundbus/i2sbus/core.c +@@ -240,6 +240,8 @@ static int i2sbus_add_dev(struct macio_dev *macio, + } + for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) { + int irq = irq_of_parse_and_map(np, i); ++ if (!irq) ++ goto err; + if (request_irq(irq, ints[i], 0, dev->rnames[i], dev)) + goto err; + dev->interrupts[i] = irq; +diff --git a/sound/core/control_led.c b/sound/core/control_led.c +--- a/sound/core/control_led.c ++++ b/sound/core/control_led.c +@@ -255,6 +255,8 @@ static int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id, + kctl = snd_ctl_find_id(card, id); + if (!kctl) + return -ENOENT; ++ if (!kctl->info || !kctl->get) ++ return -EINVAL; + ioff = snd_ctl_get_ioff(kctl, id); + vd = &kctl->vd[ioff]; + access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; +diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c +--- a/sound/core/rawmidi.c ++++ b/sound/core/rawmidi.c +@@ -571,7 +571,6 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) + struct snd_rawmidi *rmidi; + + rmidi = rfile->rmidi; +- guard(mutex)(&rmidi->open_mutex); + if (rfile->input) { + close_substream(rmidi, rfile->input, 1); + rfile->input = NULL; +@@ -585,7 +584,8 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) + } + + /* called from sound/core/seq/seq_midi.c */ +-int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) ++int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, ++ int depth) + { + struct snd_rawmidi *rmidi; + +@@ -593,11 +593,13 @@ int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) + return -ENXIO; + + rmidi = rfile->rmidi; ++ mutex_lock_nested(&rmidi->open_mutex, depth); + rawmidi_release_priv(rfile); ++ mutex_unlock(&rmidi->open_mutex); + module_put(rmidi->card->module); + return 0; + } +-EXPORT_SYMBOL(snd_rawmidi_kernel_release); ++EXPORT_SYMBOL(snd_rawmidi_kernel_release_nested); + + static int snd_rawmidi_release(struct inode *inode, struct file *file) + { +@@ -607,7 +609,8 @@ static int snd_rawmidi_release(struct inode *inode, struct file *file) + + rfile = file->private_data; + rmidi = rfile->rmidi; +- rawmidi_release_priv(rfile); ++ scoped_guard(mutex, &rmidi->open_mutex) ++ rawmidi_release_priv(rfile); + kfree(rfile); + module = rmidi->card->module; + snd_card_file_remove(rmidi->card, file); +diff --git a/sound/core/ump.c b/sound/core/ump.c +--- a/sound/core/ump.c ++++ b/sound/core/ump.c +@@ -1184,7 +1184,8 @@ static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream) + ump->legacy_substreams[dir][group] = NULL; + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!--ump->legacy_out_opens) +- snd_rawmidi_kernel_release(&ump->legacy_out_rfile); ++ snd_rawmidi_kernel_release_nested(&ump->legacy_out_rfile, ++ SINGLE_DEPTH_NESTING); + } + return 0; + } +diff --git a/sound/drivers/mtpav.c b/sound/drivers/mtpav.c +--- a/sound/drivers/mtpav.c ++++ b/sound/drivers/mtpav.c +@@ -642,9 +642,7 @@ static void snd_mtpav_free(struct snd_card *card) + { + struct mtpav *crd = card->private_data; + +- guard(spinlock_irqsave)(&crd->spinlock); +- if (crd->istimer > 0) +- snd_mtpav_remove_output_timer(crd); ++ timer_shutdown_sync(&crd->timer); + } + + /* +diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c +--- a/sound/hda/codecs/conexant.c ++++ b/sound/hda/codecs/conexant.c +@@ -207,7 +207,7 @@ static void cx_remove(struct hda_codec *codec) + snd_hda_gen_remove(codec); + } + +-static void cx_process_headset_plugin(struct hda_codec *codec) ++static void cx_process_headset_detect_plug_type(struct hda_codec *codec) + { + unsigned int val; + unsigned int count = 0; +@@ -223,11 +223,9 @@ static void cx_process_headset_plugin(struct hda_codec *codec) + count++; + } while (count < 3); + val = snd_hda_codec_read(codec, 0x1c, 0, 0xcb0, 0x0); +- if (val & 0x800) { +- codec_dbg(codec, "headset plugin, type is CTIA\n"); +- snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); +- } else if (val & 0x400) { +- codec_dbg(codec, "headset plugin, type is OMTP\n"); ++ if (val & 0xc00) { ++ codec_dbg(codec, "headset plugin, type is %s\n", ++ val & 0x800 ? "CTIA" : "OMTP"); + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); + } else { + codec_dbg(codec, "headphone plugin\n"); +@@ -243,10 +241,12 @@ static void cx_update_headset_mic_vref(struct hda_codec *codec, struct hda_jack_ + * Check hp&mic tag to process headset plugin & plugout. + */ + mic_present = snd_hda_codec_read(codec, 0x19, 0, AC_VERB_GET_PIN_SENSE, 0x0); +- if (!(mic_present & AC_PINSENSE_PRESENCE)) /* mic plugout */ ++ if (!(mic_present & AC_PINSENSE_PRESENCE)) { /* mic plugout */ + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20); +- else +- cx_process_headset_plugin(codec); ++ } else { ++ cx_process_headset_detect_plug_type(codec); ++ snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); ++ } + } + + static int cx_suspend(struct hda_codec *codec) +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -1370,6 +1370,61 @@ static void alc269_fixup_hp_gpio_led(struct hda_codec *codec, + alc_fixup_hp_gpio_led(codec, action, 0x08, 0x10); + } + ++/* ++ * HP Laptop 15-fd0xxx (SSID 103c:8bb6) Speaker Mute LED fix ++ * ++ * The speaker mute LED is controlled via VREF100 on NID 0x1a. ++ * This pin must remain powered (D0) even during suspend, otherwise ++ * the LED state is lost and the pin defaults to Hi-Z on resume, ++ * causing the LED to stop responding to mute toggles. ++ * Windows keeps this pin powered unconditionally, so matching that ++ * behavior ensures consistent LED operation across suspend/resume. ++ * The mic-mute LED is controlled via GPIO 0 with active-low polarity. ++ */ ++static unsigned int hp_8bb6_power_filter(struct hda_codec *codec, ++ hda_nid_t nid, ++ unsigned int power_state) ++{ ++ if (nid == 0x1a) ++ return AC_PWRST_D0; ++ return snd_hda_gen_path_power_filter(codec, nid, power_state); ++} ++ ++static int hp_8bb6_speaker_mute_led_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); ++ unsigned int val = (brightness == LED_OFF) ? PIN_IN : PIN_VREF100; ++ ++ snd_hda_set_pin_ctl_cache(codec, 0x1a, val); ++ return 0; ++} ++ ++static void alc236_fixup_hp_15_fd0xxx(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ struct alc_spec *spec = codec->spec; ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->micmute_led_polarity = 1; ++ alc_fixup_hp_gpio_led(codec, action, 0x00, 0x01); ++ ++ spec->mute_led_polarity = 0; ++ snd_hda_gen_add_mute_led_cdev(codec, hp_8bb6_speaker_mute_led_set); ++ ++ codec->power_filter = hp_8bb6_power_filter; ++ spec->no_shutup_pins = 1; ++ break; ++ ++ case HDA_FIXUP_ACT_INIT: ++ if (spec->gen.vmaster_mute.hook) ++ snd_hda_sync_vmaster_hook(&spec->gen.vmaster_mute); ++ break; ++ } ++} ++ + static void alc285_fixup_hp_gpio_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { +@@ -4089,6 +4144,7 @@ enum { + ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE, + ALC225_FIXUP_S3_POP_NOISE, + ALC700_FIXUP_INTEL_REFERENCE, ++ ALC700_FIXUP_INTEL_HADES_CANYON, + ALC274_FIXUP_DELL_BIND_DACS, + ALC274_FIXUP_DELL_AIO_LINEOUT_VERB, + ALC298_FIXUP_TPT470_DOCK_FIX, +@@ -4152,6 +4208,7 @@ enum { + ALC236_FIXUP_HP_GPIO_LED, + ALC236_FIXUP_HP_MUTE_LED, + ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF, ++ ALC236_FIXUP_HP_15_FD0XXX, + ALC236_FIXUP_LENOVO_INV_DMIC, + ALC298_FIXUP_SAMSUNG_AMP, + ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, +@@ -4297,6 +4354,7 @@ enum { + ALC287_FIXUP_LENOVO_LEGION_AW88399, + ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, + ALC285_LENOVO_DAC_RENAME, ++ ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, + }; + + /* A special fixup for Lenovo C940 and Yoga Duet 7; +@@ -4333,6 +4391,19 @@ static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, + __snd_hda_apply_fixup(codec, id, action, 0); + } + ++static void alc700_fixup_intel_hades_canyon(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ /* ++ * Windows may leave coef 0x1b bit 0x0400 cleared, causing broken ++ * analog playback after rebooting into Linux. Restore the bit ++ * during codec initialization. ++ */ ++ if (action == HDA_FIXUP_ACT_INIT) ++ alc_update_coef_idx(codec, 0x1b, 0x0400, 0x0400); ++} ++ + static const struct hda_fixup alc269_fixups[] = { + [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { + .type = HDA_FIXUP_PINS, +@@ -5411,6 +5482,10 @@ static const struct hda_fixup alc269_fixups[] = { + {} + } + }, ++ [ALC700_FIXUP_INTEL_HADES_CANYON] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc700_fixup_intel_hades_canyon, ++ }, + [ALC274_FIXUP_DELL_BIND_DACS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc274_fixup_bind_dacs, +@@ -5913,6 +5988,10 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc236_fixup_hp_mute_led_micmute_gpio, + }, ++ [ALC236_FIXUP_HP_15_FD0XXX] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc236_fixup_hp_15_fd0xxx, ++ }, + [ALC236_FIXUP_LENOVO_INV_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, +@@ -7010,6 +7089,12 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_lenovo_dac_rename, + }, ++ [ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_fixup_speaker2_to_dac1, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_TXNW2781_I2C, ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -7448,6 +7533,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), + SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), +@@ -8031,6 +8117,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), ++ SND_PCI_QUIRK(0x17aa, 0x3846, "Lenovo Yoga Pro 9 16IAH10", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), + /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; + * use codec SSID to distinguish them + */ +@@ -8044,14 +8131,15 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), ++ SND_PCI_QUIRK(0x17aa, 0x3862, "Lenovo IdeaPad Slim 3 15ABR8", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x3865, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3866, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), +- HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ ++ HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), + SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), +@@ -8231,6 +8319,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), + SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), ++ SND_PCI_QUIRK(0x8086, 0x2073, "Intel NUC 8 Hades Canyon", ++ ALC700_FIXUP_INTEL_HADES_CANYON), + SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC), + SND_PCI_QUIRK(0x8086, 0x2080, "Intel NUC 8 Rugged", ALC256_FIXUP_INTEL_NUC8_RUGGED), + SND_PCI_QUIRK(0x8086, 0x2081, "Intel NUC 10", ALC256_FIXUP_INTEL_NUC10), +diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c +--- a/sound/pci/ice1712/ice1712.c ++++ b/sound/pci/ice1712/ice1712.c +@@ -2523,7 +2523,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) + { + static int dev; +- struct snd_card *card; ++ struct snd_card *card __free(snd_card_unref) = NULL; + struct snd_ice1712 *ice; + int pcm_dev = 0, err; + const struct snd_ice1712_card_info * const *tbl, *c; +@@ -2535,8 +2535,8 @@ static int snd_ice1712_probe(struct pci_dev *pci, + return -ENOENT; + } + +- err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, +- sizeof(*ice), &card); ++ err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, ++ sizeof(*ice), &card); + if (err < 0) + return err; + ice = card->private_data; +@@ -2640,6 +2640,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, + if (err < 0) + return err; + pci_set_drvdata(pci, card); ++ card = NULL; /* probe succeeded, don't release as error */ + dev++; + return 0; + } +diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c +--- a/sound/soc/amd/acp-config.c ++++ b/sound/soc/amd/acp-config.c +@@ -54,7 +54,7 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC"), +- DMI_MATCH(DMI_PRODUCT_NAME, "TUF Gaming A14 FA401EA"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "FA401EA"), + }, + }, + { +@@ -70,6 +70,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_BOARD_NAME, "8EA1"), + }, + }, ++ { ++ /* HP OmniBook X Flip 16-cc0xxx */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "HP"), ++ DMI_MATCH(DMI_BOARD_NAME, "8EA2"), ++ }, ++ }, + {} + }; + +@@ -209,6 +216,20 @@ static const struct config_entry config_table[] = { + {} + }, + }, ++ { ++ .flags = FLAG_AMD_LEGACY, ++ .device = ACP_PCI_DEV_ID, ++ .dmi_table = (const struct dmi_system_id []) { ++ { ++ .matches = { ++ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1060"), ++ }, ++ }, ++ {} ++ }, ++ }, + }; + + static int snd_amd_acp_acpi_find_config(struct pci_dev *pci) +diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +--- a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c ++++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +@@ -382,6 +382,14 @@ static const struct dmi_system_id acp3x_es83xx_dmi_table[] = { + }, + .driver_data = (void *)(ES83XX_ENABLE_DMIC), + }, ++ { ++ .matches = { ++ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1060"), ++ }, ++ .driver_data = (void *)(ES83XX_ENABLE_DMIC), ++ }, + {} + }; + +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -570,6 +570,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Alienware"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R1 AMD"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +diff --git a/sound/soc/codecs/max98926.c b/sound/soc/codecs/max98926.c +--- a/sound/soc/codecs/max98926.c ++++ b/sound/soc/codecs/max98926.c +@@ -360,7 +360,7 @@ static int max98926_dai_set_fmt(struct snd_soc_dai *codec_dai, + regmap_write(max98926->regmap, + MAX98926_FORMAT, MAX98926_DAI_DLY_MASK); + regmap_update_bits(max98926->regmap, MAX98926_FORMAT, +- MAX98926_DAI_BCI_MASK, invert); ++ MAX98926_DAI_BCI_MASK | MAX98926_DAI_WCI_MASK, invert); + return 0; + } + +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -1779,6 +1779,7 @@ static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) + dev_dbg(dev, "%s jack/mic/amp func_status=0x%x, 0x%x, 0x%x\n", + __func__, jack_func_status, mic_func_status, amp_func_status); + ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_JD_CTL3, 0x7778); + /* DMIC */ + if ((mic_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt712->first_hw_init)) { + rt712_sdca_index_write(rt712, RT712_VENDOR_HDA_CTL, RT712_DMIC2_FU_IT_FLOAT_CTL, 0x1526); +diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h +--- a/sound/soc/codecs/rt712-sdca.h ++++ b/sound/soc/codecs/rt712-sdca.h +@@ -74,6 +74,7 @@ struct rt712_dmic_kctrl_priv { + #define RT712_JD_PRODUCT_NUM 0x00 + #define RT712_ANALOG_BIAS_CTL3 0x04 + #define RT712_JD_CTL1 0x09 ++#define RT712_JD_CTL3 0x0b + #define RT712_IO_CTL 0x0c + #define RT712_LDO2_3_CTL1 0x0e + #define RT712_PARA_VERB_CTL 0x1a +diff --git a/sound/soc/codecs/rt766-sdca-sdw.c b/sound/soc/codecs/rt766-sdca-sdw.c +--- a/sound/soc/codecs/rt766-sdca-sdw.c ++++ b/sound/soc/codecs/rt766-sdca-sdw.c +@@ -233,6 +233,8 @@ static void rt766_sdca_sdw_remove(struct sdw_slave *slave) + static const struct sdw_device_id rt766_sdca_id[] = { + SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x3, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x3, 0x1, 0), ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x4, 0x1, 0), ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x4, 0x1, 0), + {}, + }; + MODULE_DEVICE_TABLE(sdw, rt766_sdca_id); +diff --git a/sound/soc/codecs/tac5xx2-sdw.c b/sound/soc/codecs/tac5xx2-sdw.c +--- a/sound/soc/codecs/tac5xx2-sdw.c ++++ b/sound/soc/codecs/tac5xx2-sdw.c +@@ -55,6 +55,12 @@ + #define TAC_XU_BYPASS_REG(func, xu) \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##xu, \ + TAC_SDCA_CTL_XU_BYPASS, 0) ++#define TAC_VOLUME_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##fu, \ ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_##ch) ++#define TAC_GAIN_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##fu, \ ++ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_##ch) + + /* mute registers */ + #define FU21_L_MUTE_REG TAC_MUTE_REG(SA, FU21, LEFT) +@@ -123,9 +129,9 @@ static const DECLARE_TLV_DB_SCALE(tac5xx2_dvc_tlv, -7200, 50, 0); + #define TAC_DOUBLE_Q78_TLV(name, func_id, ent_id) \ + SDCA_DOUBLE_Q78_TLV(name, \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func_id, TAC_SDCA_ENT_##ent_id, \ +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), \ ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func_id, TAC_SDCA_ENT_##ent_id, \ +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), \ ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), \ + TAC_DVC_MIN, TAC_DVC_MAX, TAC_DVC_STEP, tac5xx2_dvc_tlv) + + struct tac5xx2_prv { +@@ -381,37 +387,53 @@ static const struct reg_default tac_reg_default[] = { + {TAC_REG_SDW(0, 1, 0x71), 0x5}, + {TAC_REG_SDW(0, 1, 0x72), 0x6}, + {TAC_REG_SDW(0, 1, 0x73), 0x7}, ++ /* SA */ ++ {TAC_MUTE_REG(SA, FU21, LEFT), 0x1}, ++ {TAC_MUTE_REG(SA, FU21, RIGHT), 0x1}, ++ {TAC_VOLUME_REG(SA, FU21, LEFT), 0x9c00}, ++ {TAC_VOLUME_REG(SA, FU21, RIGHT), 0x9c00}, ++ {TAC_MUTE_REG(SA, FU23, LEFT), 0x1}, ++ {TAC_MUTE_REG(SA, FU23, RIGHT), 0x1}, ++ {TAC_GAIN_REG(SA, FU23, LEFT), 0x0}, ++ {TAC_GAIN_REG(SA, FU23, RIGHT), 0x0}, ++ /* SM */ ++ {TAC_USAGE_REG(SM, IT11), 0x0}, ++ {TAC_USAGE_REG(SM, OT113), 0x0}, ++ {TAC_MUTE_REG(SM, FU113, LEFT), 0x1}, ++ {TAC_MUTE_REG(SM, FU113, RIGHT), 0x1}, ++ {TAC_GAIN_REG(SM, FU113, LEFT), 0x0}, ++ {TAC_GAIN_REG(SM, FU113, RIGHT), 0x0}, ++ {TAC_MUTE_REG(SM, FU11, LEFT), 0x1}, ++ {TAC_MUTE_REG(SM, FU11, RIGHT), 0x1}, ++ {TAC_GAIN_REG(SM, FU11, LEFT), 0x0}, ++ {TAC_GAIN_REG(SM, FU11, RIGHT), 0x0}, ++ {TAC_XU_BYPASS_REG(SM, XU12), 0x1}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_CS113, ++ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, ++ /* UAJ */ ++ {TAC_USAGE_REG(UAJ, IT33), 0x0}, ++ {TAC_USAGE_REG(UAJ, IT41), 0x0}, ++ {TAC_USAGE_REG(UAJ, OT36), 0x0}, ++ {TAC_USAGE_REG(UAJ, OT45), 0x0}, ++ {TAC_MUTE_REG(UAJ, FU41, LEFT), 0x1}, ++ {TAC_MUTE_REG(UAJ, FU41, RIGHT), 0x1}, ++ {TAC_VOLUME_REG(UAJ, FU41, LEFT), 0x0}, ++ {TAC_VOLUME_REG(UAJ, FU41, RIGHT), 0x0}, ++ {TAC_MUTE_REG(UAJ, FU36, RIGHT), 0x1}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, ++ TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), 0x0}, ++ {TAC_XU_BYPASS_REG(UAJ, XU42), 0x0}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_CS36, ++ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_CS41, ++ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, + }; + + static const struct reg_sequence tac_spk_seq[] = { + REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), + REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +-}; +- +-static const struct reg_sequence tac_sm_seq[] = { +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +-}; +- +-static const struct reg_sequence tac_uaj_seq[] = { +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, +- TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS), 0), ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), + }; + + static bool tac_volatile_reg(struct device *dev, unsigned int reg) +@@ -468,25 +490,25 @@ static int tac_sdca_mbq_size(struct device *dev, unsigned int reg) + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_MASTER_GAIN, 0): ++ TAC_SDCA_CHANNEL_GAIN, 0): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, +- TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS): + return 2; + + default: +@@ -531,7 +553,7 @@ static const struct snd_kcontrol_new tac_uaj_controls[] = { + TAC_DOUBLE_Q78_TLV("UAJ Playback Volume", UAJ, FU41), + SDCA_SINGLE_Q78_TLV("UAJ Capture Volume", + SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, +- TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS), ++ TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), + TAC_DVC_MIN, TAC_DVC_MAX, TAC_DVC_STEP, tac5xx2_dvc_tlv), + }; + +@@ -1761,16 +1783,6 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) + goto io_init_err; + } + dev_dbg(dev, "smartmic init done\n"); +- +- if (first) { +- ret = regmap_multi_reg_write(tac_dev->regmap, tac_sm_seq, +- ARRAY_SIZE(tac_sm_seq)); +- if (ret) { +- dev_err(tac_dev->dev, +- "init writes failed, err=%d", ret); +- goto io_init_err; +- } +- } + } + + if (tac_dev->uaj_func_data) { +@@ -1783,14 +1795,6 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) + dev_dbg(dev, "uaj init done\n"); + + if (first) { +- ret = regmap_multi_reg_write(tac_dev->regmap, tac_uaj_seq, +- ARRAY_SIZE(tac_uaj_seq)); +- if (ret) { +- dev_err(tac_dev->dev, +- "init writes failed, err=%d", ret); +- goto io_init_err; +- } +- + if (tac_dev->hs_jack) { + ret = tac5xx2_jack_init(tac_dev); + if (ret) { +diff --git a/sound/soc/codecs/tac5xx2.h b/sound/soc/codecs/tac5xx2.h +--- a/sound/soc/codecs/tac5xx2.h ++++ b/sound/soc/codecs/tac5xx2.h +@@ -174,14 +174,13 @@ + #define TAC_SDCA_ENT_SAPU29 0x35 + + /* Control selector definitions */ +-#define TAC_SDCA_MASTER_GAIN 0x0B + #define TAC_SDCA_MASTER_MUTE 0x01 + #define TAC_SDCA_CHANNEL_MUTE 0x01 +-#define TAC_SDCA_CHANNEL_GAIN 0x02 ++#define TAC_SDCA_CHANNEL_VOLUME 0x02 + #define TAC_SDCA_POSTURENUMBER 0x10 + #define TAC_SDCA_REQUESTED_PS 0x01 + #define TAC_SDCA_ACTUAL_PS 0x10 +-#define TAC_SDCA_CHANNEL_VOLUME 0x02 ++#define TAC_SDCA_CHANNEL_GAIN 0x0B + + /* 2. smart mic function */ + #define TAC_FUNCTION_ID_SM 0x2 +diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c +--- a/sound/soc/codecs/tas2783-sdw.c ++++ b/sound/soc/codecs/tas2783-sdw.c +@@ -1310,6 +1310,7 @@ static int tas_port_prep(struct sdw_slave *slave, struct sdw_prepare_ch *prep_ch + enum sdw_port_prep_ops pre_ops) + { + struct device *dev = &slave->dev; ++ struct tas2783_prv *tas_dev = dev_get_drvdata(dev); + struct sdw_dpn_prop *dpn_prop; + u32 addr; + int ret; +@@ -1321,6 +1322,25 @@ static int tas_port_prep(struct sdw_slave *slave, struct sdw_prepare_ch *prep_ch + addr = SDW_DPN_PREPARECTRL(prep_ch->num); + switch (pre_ops) { + case SDW_OPS_PORT_PRE_PREP: ++ /* ++ * The Function has to be powered before the port can complete ++ * channel preparation. hw_params() does that when a stream is ++ * set up, but a stream that is only re-prepared - as it is ++ * after the peripheral lost power in S0i3 - does not go ++ * through hw_params() again, and the peripheral is back at its ++ * PS3 reset default. Power it up here, where it is needed. ++ */ ++ scoped_guard(mutex, &tas_dev->pde_lock) ++ ret = regmap_write(tas_dev->regmap, ++ SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, ++ TAS2783_SDCA_CTL_REQ_POW_STATE, 0), ++ TAS2783_SDCA_POW_STATE_ON); ++ if (ret) { ++ dev_err(dev, "power up failed for port %d, err=%d\n", ++ prep_ch->num, ret); ++ return ret; ++ } ++ + ret = sdw_write_no_pm(slave, addr, prep_ch->ch_mask); + if (ret) + dev_err(dev, "prep failed for port %d, err=%d\n", +diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig +--- a/sound/soc/fsl/Kconfig ++++ b/sound/soc/fsl/Kconfig +@@ -244,7 +244,7 @@ config SND_SOC_MPC5200_AC97 + config SND_MPC52xx_SOC_PCM030 + tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712" + depends on PPC_MPC5200_SIMPLE +- select SND_SOC_MPC5200_AC97 ++ depends on SND_SOC_MPC5200_AC97 + select SND_SOC_WM9712 + help + Say Y if you want to add support for sound on the Phytec pcm030 +@@ -253,7 +253,7 @@ config SND_MPC52xx_SOC_PCM030 + config SND_MPC52xx_SOC_EFIKA + tristate "SoC AC97 Audio support for bbplan Efika and STAC9766" + depends on PPC_EFIKA +- select SND_SOC_MPC5200_AC97 ++ depends on SND_SOC_MPC5200_AC97 + select SND_SOC_STAC9766 + help + Say Y if you want to add support for sound on the Efika. +diff --git a/sound/soc/fsl/efika-audio-fabric.c b/sound/soc/fsl/efika-audio-fabric.c +--- a/sound/soc/fsl/efika-audio-fabric.c ++++ b/sound/soc/fsl/efika-audio-fabric.c +@@ -22,8 +22,6 @@ + #include + #include + +-#include "mpc5200_dma.h" +- + #define DRV_NAME "efika-audio-fabric" + + SND_SOC_DAILINK_DEFS(analog, +diff --git a/sound/soc/fsl/pcm030-audio-fabric.c b/sound/soc/fsl/pcm030-audio-fabric.c +--- a/sound/soc/fsl/pcm030-audio-fabric.c ++++ b/sound/soc/fsl/pcm030-audio-fabric.c +@@ -13,8 +13,6 @@ + + #include + +-#include "mpc5200_dma.h" +- + #define DRV_NAME "pcm030-audio-fabric" + + struct pcm030_audio_data { +diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c +--- a/sound/soc/loongson/loongson_card.c ++++ b/sound/soc/loongson/loongson_card.c +@@ -55,8 +55,8 @@ static const struct loongson_card_config ls2k0300_dl2k0300b_card_config = { + .add_dapm_routes = true, + }; + +-static int tegra_machine_event(struct snd_soc_dapm_widget *w, +- struct snd_kcontrol *k, int event) ++static int loongson_asoc_machine_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *k, int event) + { + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct loongson_card_data *priv = snd_soc_card_get_drvdata(card); +@@ -72,9 +72,9 @@ static int tegra_machine_event(struct snd_soc_dapm_widget *w, + return 0; + } + +-static const struct snd_soc_dapm_widget loongson_aosc_dapm_widgets[] = { +- SND_SOC_DAPM_HP("Headphone", tegra_machine_event), +- SND_SOC_DAPM_SPK("Speaker", tegra_machine_event), ++static const struct snd_soc_dapm_widget loongson_asoc_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone", loongson_asoc_machine_event), ++ SND_SOC_DAPM_SPK("Speaker", loongson_asoc_machine_event), + }; + + /* Headphones Jack */ +@@ -314,8 +314,8 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) + card->num_links = ARRAY_SIZE(loongson_dai_links); + + if (ls_priv->cfg->add_dapm_widgets) { +- card->dapm_widgets = loongson_aosc_dapm_widgets; +- card->num_dapm_widgets = ARRAY_SIZE(loongson_aosc_dapm_widgets); ++ card->dapm_widgets = loongson_asoc_dapm_widgets; ++ card->num_dapm_widgets = ARRAY_SIZE(loongson_asoc_dapm_widgets); + } + + snd_soc_card_set_drvdata(card, ls_priv); +diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c +--- a/sound/soc/qcom/common.c ++++ b/sound/soc/qcom/common.c +@@ -103,14 +103,14 @@ static int qcom_snd_parse_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, + int ret; + + if (!link_np) +- return -EINVAL; ++ return -ENOENT; + + struct device_node *cpu_np __free(device_node) = + of_get_child_by_name(link_np, "cpu"); + struct device_node *codec_np __free(device_node) = + of_get_child_by_name(link_np, "codec"); + if (!cpu_np || !codec_np) +- return -EINVAL; ++ return -ENOENT; + + ret = qcom_snd_parse_tdm_slot(cpu_np, cpu_cfg); + if (ret) +@@ -172,7 +172,7 @@ int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd) + + ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); + if (ret) +- return ret == -EINVAL ? 0 : ret; ++ return ret == -ENOENT ? 0 : ret; + + return qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); + } +diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c +--- a/sound/soc/qcom/qdsp6/q6apm-dai.c ++++ b/sound/soc/qcom/qdsp6/q6apm-dai.c +@@ -216,7 +216,7 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, + { + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; +- struct audioreach_module_config cfg; ++ struct audioreach_module_config cfg = {}; + struct device *dev = component->dev; + struct q6apm_dai_data *pdata; + int ret; +@@ -814,7 +814,7 @@ static int q6apm_dai_compr_set_params(struct snd_soc_component *component, + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + struct q6apm_dai_data *pdata; +- struct audioreach_module_config cfg; ++ struct audioreach_module_config cfg = {}; + struct snd_codec *codec = ¶ms->codec; + int dir = stream->direction; + int ret; +diff --git a/sound/soc/qcom/qdsp6/q6prm-clocks.c b/sound/soc/qcom/qdsp6/q6prm-clocks.c +--- a/sound/soc/qcom/qdsp6/q6prm-clocks.c ++++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c +@@ -64,34 +64,34 @@ static const struct q6dsp_clk_init q6prm_clks[] = { + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_2X_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_EBIT), + Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6PRM_HW_CORE_ID_LPASS, + "LPASS_HW_MACRO"), + Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6PRM_HW_CORE_ID_DCODEC, +diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h +--- a/sound/soc/qcom/qdsp6/q6prm.h ++++ b/sound/soc/qcom/qdsp6/q6prm.h +@@ -97,34 +97,34 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 0x318 + +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_IBIT 0x500 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_EBIT 0x501 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_IBIT 0x502 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_EBIT 0x503 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_IBIT 0x504 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_EBIT 0x505 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_IBIT 0x506 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_EBIT 0x507 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_IBIT 0x508 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_EBIT 0x509 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_IBIT 0x50A +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_EBIT 0x50B +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_IBIT 0x50C +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_EBIT 0x50D +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_IBIT 0x50E +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_EBIT 0x50F +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_IBIT 0x510 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_EBIT 0x511 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_IBIT 0x512 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_EBIT 0x513 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_IBIT 0x514 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_EBIT 0x515 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_IBIT 0x516 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_EBIT 0x517 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_IBIT 0x518 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_EBIT 0x519 +-#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 +-#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_IBIT 0x500 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_EBIT 0x501 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_IBIT 0x502 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_EBIT 0x503 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_IBIT 0x504 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_EBIT 0x505 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_IBIT 0x506 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_EBIT 0x507 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_IBIT 0x508 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_EBIT 0x509 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_IBIT 0x50A ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_EBIT 0x50B ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_IBIT 0x50C ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_EBIT 0x50D ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_IBIT 0x50E ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_EBIT 0x50F ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_IBIT 0x510 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_EBIT 0x511 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_IBIT 0x512 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_EBIT 0x513 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_IBIT 0x514 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_EBIT 0x515 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_IBIT 0x516 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_EBIT 0x517 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_IBIT 0x518 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_EBIT 0x519 ++#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 ++#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 + + #define Q6PRM_LPASS_CLK_SRC_INTERNAL 1 + #define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0 +diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c +--- a/sound/soc/qcom/sc8280xp.c ++++ b/sound/soc/qcom/sc8280xp.c +@@ -115,33 +115,42 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai; + struct qcom_snd_tdm_slot_cfg cpu_cfg; + struct qcom_snd_tdm_slot_cfg codec_cfg; +- unsigned int bclk_freq; ++ int bclk_freq; + int ret; + int i; + + ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); + if (ret) +- return ret == -EINVAL ? 0 : ret; ++ return ret == -ENOENT ? 0 : ret; + + if (!cpu_cfg.slots) + return 0; + + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); +- if (ret) ++ if (ret && ret != -ENOTSUPP) + return ret; + ++ if (data->priv->codec_dai_fmt) { ++ for_each_rtd_codec_dais(rtd, i, codec_dai) { ++ ret = snd_soc_dai_set_fmt(codec_dai, ++ data->priv->codec_dai_fmt); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ } ++ } ++ + ret = qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); + if (ret) + return ret; + + bclk_freq = snd_soc_tdm_params_to_bclk(params, cpu_cfg.slot_width, cpu_cfg.slots, 1); +- if (!bclk_freq) ++ if (bclk_freq <= 0) + return -EINVAL; + + if (data->priv->mi2s_bclk_enable) { + ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, + SND_SOC_CLOCK_IN); +- if (ret) { ++ if (ret && ret != -ENOTSUPP) { + dev_err(rtd->dev, "%s: failed to set cpu sysclk: %d\n", + __func__, ret); + return ret; +@@ -152,7 +161,7 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, + for_each_rtd_codec_dais(rtd, i, codec_dai) { + ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq, + SND_SOC_CLOCK_IN); +- if (ret) { ++ if (ret && ret != -ENOTSUPP) { + dev_err(rtd->dev, "%s: failed to set codec sysclk on %s: %d\n", + __func__, codec_dai->name, ret); + return ret; +diff --git a/sound/soc/sdw_utils/soc_sdw_rt_amp.c b/sound/soc/sdw_utils/soc_sdw_rt_amp.c +--- a/sound/soc/sdw_utils/soc_sdw_rt_amp.c ++++ b/sound/soc/sdw_utils/soc_sdw_rt_amp.c +@@ -252,11 +252,13 @@ int asoc_sdw_rt_amp_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai + if (ctx->amp_dev1) { + device_remove_software_node(ctx->amp_dev1); + put_device(ctx->amp_dev1); ++ ctx->amp_dev1 = NULL; + } + + if (ctx->amp_dev2) { + device_remove_software_node(ctx->amp_dev2); + put_device(ctx->amp_dev2); ++ ctx->amp_dev2 = NULL; + } + + return 0; +diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c +--- a/sound/soc/sdw_utils/soc_sdw_utils.c ++++ b/sound/soc/sdw_utils/soc_sdw_utils.c +@@ -81,6 +81,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* speaker */ + .direction = {true, false}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tac5572", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -94,6 +95,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tac5572", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -102,6 +104,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* UAJ */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif3", ++ .component_name = "tac5572", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .controls = generic_jack_controls, +@@ -122,6 +125,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* speaker with IV sense feedback */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tac5672", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -135,6 +139,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tac5672", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -143,6 +148,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* UAJ */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif3", ++ .component_name = "tac5672", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .controls = generic_jack_controls, +@@ -163,6 +169,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* speaker with echo reference feedback */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tac5682", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -176,6 +183,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tac5682", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -184,6 +192,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* UAJ */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif3", ++ .component_name = "tac5682", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .controls = generic_jack_controls, +@@ -203,6 +212,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + { + .direction = {true, false}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tas2883", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -216,6 +226,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tas2883", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -1510,9 +1521,24 @@ int asoc_sdw_trigger(struct snd_pcm_substream *substream, int cmd) + } + + switch (cmd) { ++ case SNDRV_PCM_TRIGGER_RESUME: ++ /* ++ * The peripherals lose their port configuration when the ++ * controller is power-gated during system suspend, and an ++ * application that restarts the stream with ++ * SNDRV_PCM_IOCTL_RESUME - which platforms advertising ++ * SNDRV_PCM_INFO_RESUME allow - never goes through ++ * .prepare() again. Prepare the stream here so that the ++ * ports are reprogrammed before they are enabled; ++ * sdw_prepare_stream() reapplies the parameters without ++ * recomputing them when the stream is disabled. ++ */ ++ ret = sdw_prepare_stream(sdw_stream); ++ if (ret) ++ break; ++ fallthrough; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +- case SNDRV_PCM_TRIGGER_RESUME: + ret = sdw_enable_stream(sdw_stream); + break; + +diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c +--- a/sound/soc/soc-dapm.c ++++ b/sound/soc/soc-dapm.c +@@ -3614,7 +3614,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, + val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; + mask = e->mask << e->shift_l; + if (e->shift_l != e->shift_r) { +- if (item[1] > e->items) ++ if (item[1] >= e->items) + return -EINVAL; + val |= snd_soc_enum_item_to_val(e, item[1]) << e->shift_r; + mask |= e->mask << e->shift_r; +diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c +--- a/sound/soc/soc-generic-dmaengine-pcm.c ++++ b/sound/soc/soc-generic-dmaengine-pcm.c +@@ -397,24 +397,8 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + if (PTR_ERR(chan) == -EPROBE_DEFER) + return -EPROBE_DEFER; + +- bool has_fw_node = dev->of_node || is_acpi_device_node(dev->fwnode); +- bool name_exists_in_fw = false; +- +- if (has_fw_node) +- name_exists_in_fw = device_property_match_string(dev, +- "dma-names", +- name) >= 0; +- +- if (has_fw_node && name_exists_in_fw) +- dev_warn(dev, "DTS/ACPI DMA channel '%s' request failed (%ld)\n", +- name, PTR_ERR(chan)); +- +- if (has_fw_node && !name_exists_in_fw) +- dev_warn(dev, "DTS/ACPI name '%s' not found, legacy failed (%ld)\n", +- name, PTR_ERR(chan)); +- +- if (!has_fw_node) +- dev_warn(dev, "Legacy DMA channel '%s' request failed (%ld)\n", ++ if (device_property_match_string(dev, "dma-names", name) >= 0) ++ dev_warn(dev, "dma-names has '%s' but request failed (%ld)\n", + name, PTR_ERR(chan)); + + pcm->chan[i] = NULL; +diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c +--- a/sound/usb/mixer.c ++++ b/sound/usb/mixer.c +@@ -1237,21 +1237,27 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) + } + + /* +- * Additional checks for sticky mixers ++ * Additional checks for sticky GET_CUR + * +- * Some devices' volume control mixers are sticky, which accept SET_CUR but +- * do absolutely nothing. ++ * Some devices' volume control mixers have sticky GET_CUR, which implies either ++ * stubbed SET_CUR or broken GET_CUR. For the former case, the mixer accepts ++ * SET_CUR but do absolutely nothing, so falling back to soft mixer is the only ++ * way to control the volume. For the latter case, the mixer has effective ++ * SET_CUR despite GET_CUR being constant, and the mixer is usable as long as we ++ * always provide mixer value from the ceche. + * + * Check the return values of GET_CUR with different SET_CUR values. Consider +- * the mixer as sticky if GET_CUR always returns a constant value. ++ * GET_CUR as sticky if GET_CUR always returns a constant value. + * +- * Some devices have effective SET_CUR despite GET_CUR being constant. Do not +- * consider the mixer as sticky if a quirk flag indicates that. ++ * Unfortunately, we can't distinguish between stubbed SET_CUR and broken ++ * GET_CUR with simple read-back tests. Disabling the mixer regardless and ++ * forcing userspace to use soft mixer instead can lead to audible distortion at ++ * low volume on some wireless headphones, probably due to their poorly- ++ * performed lossy codec. + * +- * Gate the registration of sticky mixers to prevent confusing userspace, so +- * that they won't cause ineffective volume control. However, for mixers with +- * effective SET_CUR but broken GET_CUR, the registration can continue normally +- * but further GET_CUR requests will be gated. ++ * Instead, mark GET_CUR as broken regardless and only provide mixer value from ++ * the cache. Users may opt into soft mixer in userspace audio stack if they ++ * need it. + */ + static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, + int channel, int saved) +@@ -1271,24 +1277,13 @@ static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, + return 0; + } + +- if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_BROKEN) { +- usb_audio_info(cval->head.mixer->chip, +- "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", +- cval->head.id, mixer_ctrl_intf(cval->head.mixer), +- cval->min, cval->max, cval->res, saved); +- +- cval->get_cur_broken = 1; +- return -ENXIO; +- } +- +- usb_audio_err(cval->head.mixer->chip, +- "%d:%d: sticky mixer values (%d/%d/%d => %d), disabling\n", +- cval->head.id, mixer_ctrl_intf(cval->head.mixer), +- cval->min, cval->max, cval->res, saved); + usb_audio_info(cval->head.mixer->chip, +- "check MIXER_GET_CUR_BROKEN if you believe the mixer is non-sticky"); ++ "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", ++ cval->head.id, mixer_ctrl_intf(cval->head.mixer), ++ cval->min, cval->max, cval->res, saved); + +- return -ENODEV; ++ cval->get_cur_broken = 1; ++ return -ENXIO; + } + + /* +@@ -1385,8 +1380,6 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, + goto no_checks; + + ret = check_sticky_volume_control(cval, minchn, saved); +- if (ret == -ENODEV) +- goto sticky; + if (ret) + goto no_checks; + +@@ -1454,34 +1447,15 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, + } + } + +- return 0; +- +-sticky: + /* +- * It makes no sense to restore the saved value for a sticky mixer, +- * since setting any value is a no-op. +- * +- * However, in some rare cases, SET_CUR is effective despite GET_CUR +- * always returns a constant value. These mixers are not sticky, but +- * there's no way to distinguish them. Without any additional +- * information, the best thing we can do is to set the mixer value to +- * the maximum before bailing out, so that a soft mixer can still reach +- * the maximum hardware volume if the mixer turns out to be non-sticky. +- * Meanwhile, all channels must be synchronized to prevent imbalance +- * volume. ++ * When GET_CUR is sticky, the saved value is bogus, so mixer values set ++ * by the sanity checks must be discarded through init_cur_mix_raw(). ++ * After that, we can clear the flag as per QUIRK_FLAG_MIXER_GET_CUR_OK. + */ +- if (!cval->cmask) { +- snd_usb_set_cur_mix_value(cval, 0, 0, cval->max); +- } else { +- idx = 0; +- for (i = 0; i < MAX_CHANNELS; i++) { +- if (cval->cmask & BIT(i)) { +- snd_usb_set_cur_mix_value(cval, i + 1, idx, cval->max); +- idx++; +- } +- } +- } +- return ret; ++ if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_OK) ++ cval->get_cur_broken = 0; ++ ++ return 0; + } + + #define get_min_max(cval, def) get_min_max_with_quirks(cval, def, NULL) +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -505,13 +505,13 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { + {} + }; + +-/* Audient iD14: FU 12 advertises Volume on only 4 of its 6 logical channels +- * and sits on the monitor mixer branch, but it is traced through to the +- * Speaker output terminal and gets named "Speaker Playback Volume". Userspace +- * then adopts it as the stream's hardware volume, and any setting below 0 dB +- * attenuates some channels but not others (20 dB imbalance at 80%). Give it a +- * non-standard name so that it is no longer taken for the stream's master +- * volume, while remaining reachable for anyone who wants the monitor gain. ++/* Audient iD14 MkI and MkII: FU 12 sits on the monitor mixer branch but is ++ * traced through to the Speaker output terminal, so it is named "Speaker ++ * Playback Volume". On MkII it controls only 4 of 6 playback channels. MkI ++ * testing found asymmetric attenuation within the main stereo pair. Userspace ++ * adopts this control as the stream's hardware volume, causing imbalance below ++ * 0 dB. Give it a non-standard name so that userspace no longer treats it as ++ * the stream master, while keeping the monitor gain reachable. + */ + static const struct usbmix_name_map audient_id14_map[] = { + { 12, "Monitor Mix Playback" }, /* FU, partial coverage */ +@@ -602,7 +602,12 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .map = maya44_map, + }, + { +- /* Audient iD14 */ ++ /* Audient iD14 MkI */ ++ .id = USB_ID(0x2708, 0x0002), ++ .map = audient_id14_map, ++ }, ++ { ++ /* Audient iD14 MkII */ + .id = USB_ID(0x2708, 0x0008), + .map = audient_id14_map, + }, +diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h +--- a/sound/usb/quirks-table.h ++++ b/sound/usb/quirks-table.h +@@ -1814,6 +1814,28 @@ YAMAHA_DEVICE(0x7010, "UB99"), + } + } + }, ++{ ++ /* ++ * M-Audio Venom ++ * ++ * The AudioControl interface times out on every GET_CUR request, ++ * which adds around 47 seconds to the card registration and ++ * freezes the device, blocking streaming. ++ * Using an explicit composite quirk to skip the mixer entirely. ++ */ ++ USB_DEVICE_VENDOR_SPEC(0x0763, 0x2084), ++ QUIRK_DRIVER_INFO { ++ .vendor_name = "M-Audio", ++ .product_name = "Venom", ++ QUIRK_DATA_COMPOSITE { ++ { QUIRK_DATA_IGNORE(0) }, ++ { QUIRK_DATA_STANDARD_AUDIO(1) }, ++ { QUIRK_DATA_STANDARD_AUDIO(2) }, ++ { QUIRK_DATA_STANDARD_MIDI(3) }, ++ QUIRK_COMPOSITE_END ++ } ++ } ++}, + + /* Casio devices */ + { +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -2215,10 +2215,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), +- DEVICE_FLG(0x040b, 0x0897, /* Weltrend Semiconductor, sold as Redragon H510-PRO Wireless headset */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */ + QUIRK_FLAG_IGNORE_CTL_ERROR), ++ DEVICE_FLG(0x041e, 0x324d, /* Creative Sound Blaster Play! 3 */ ++ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x041e, 0x4080, /* Creative Live Cam VF0610 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x045e, 0x083c, /* MS USB Link headset */ +@@ -2256,8 +2256,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x046d, 0x0a8f, /* Logitech H390 headset */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x046d, 0x0af7, /* Logitech PRO X 2 LIGHTSPEED */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), ++ DEVICE_FLG(0x046d, 0x0aba, /* Logitech PRO X Wireless */ ++ QUIRK_FLAG_MIXER_GET_CUR_OK | ++ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x0499, 0x1506, /* Yamaha THR5 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0499, 0x1509, /* Steinberg UR22 */ +@@ -2329,9 +2330,11 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x0763, 0x2031, /* M-Audio Fast Track C600 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0763, 0x2080, /* M-Audio Fast Track Ultra */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0763, 0x2081, /* M-Audio Fast Track Ultra */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ DEVICE_FLG(0x0763, 0x2084, /* M-Audio Venom */ ++ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_DISABLE_AUTOSUSPEND), + DEVICE_FLG(0x07fd, 0x000b, /* MOTU M Series 2nd hardware revision */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x08bb, 0x2702, /* LineX FM Transmitter */ +@@ -2370,8 +2373,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x1038, 0x1294, /* SteelSeries Arctis Pro Wireless */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x1038, 0x2232, /* SteelSeries Arctis Nova 5 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */ +@@ -2379,8 +2380,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x1224, 0x2a25, /* Jieli Technology USB PHY 2.0 */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), +- DEVICE_FLG(0x1377, 0x6004, /* Sennheiser MOMENTUM 3 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x1395, 0x740a, /* Sennheiser DECT */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x1397, 0x0507, /* Behringer UMC202HD */ +@@ -2393,6 +2392,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_IFB_SILENCE_ON_EMPTY), + DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ + QUIRK_FLAG_IGNORE_CTL_ERROR), ++ DEVICE_FLG(0x152a, 0x85dd, /* SMSL USB DAC */ ++ QUIRK_FLAG_DSD_RAW | QUIRK_FLAG_DISABLE_AUTOSUSPEND | ++ QUIRK_FLAG_SKIP_IFACE_SETUP), + DEVICE_FLG(0x152a, 0x880a, /* NeuralDSP Quad Cortex */ + 0), /* Doesn't have the vendor quirk which would otherwise apply */ + DEVICE_FLG(0x1532, 0x055e, /* Razer Nommo V2 X */ +@@ -2505,8 +2507,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x2d99, 0x0026, /* HECATE G2 GAMING HEADSET */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x2d99, 0xa024, /* Edifier MF200 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x2fc6, 0xf06b, /* MOONDROP Moonriver2 Ti */ + QUIRK_FLAG_CTL_MSG_DELAY), + DEVICE_FLG(0x2fc6, 0xf0b5, /* iBasso DC-Elite */ +@@ -2647,7 +2647,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { + QUIRK_STRING_ENTRY(MIXER_PLAYBACK_LINEAR_VOL), + QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), + QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY), +- QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN), ++ QUIRK_STRING_ENTRY(MIXER_GET_CUR_OK), + QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), + QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + NULL +diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h +--- a/sound/usb/usbaudio.h ++++ b/sound/usb/usbaudio.h +@@ -243,24 +243,18 @@ extern bool snd_usb_skip_validation; + * from snd_usb_handle_sync_urb. Instead fall through and enqueue a + * packet_info containing only size-0 packets, so the OUT ring keeps + * moving (emits silence). Needed by Behringer Flow 8 (1397:050c). +- * QUIRK_FLAG_MIXER_GET_CUR_BROKEN +- * Some mixers are sticky, which means that setting their current volume is a +- * no-op, and reading the current volume returns a constant value. The sticky +- * check disables these mixers to prevent confusing userspace. However, some +- * devices do have a tunable volume despite the reported current volume being +- * constant. As the sticky check can't distinguish between the two categories, +- * setting this flag tells that the device should fall into the second +- * category when GET_CUR returns a constant value, resulting in the sticky +- * check being non-fatal and only disabling GET_CUR instead of the whole mixer. +- * The current volume will then be provided by the internal cache that stores +- * the last set volume ++ * QUIRK_FLAG_MIXER_GET_CUR_OK ++ * On some devices, whether their GET_CUR being sticky depends on whether ++ * hotpluggable components are present. When the hotpluggable components are ++ * missing on probe, their GET_CUR behavior is classified as broken. Set the ++ * flag to prevent the heuristics from gating GET_CUR. + * QUIRK_FLAG_PLAYBACK_URB_FIXUP + * Set URB_ISO_ASAP flag for isochronous URBs and force nurbs to MAX_URBS. + * This is needed for devices that exhibit boot-time audio stuttering due + * to insufficient buffer depth combined with xHCI scheduling variability. + * The larger buffer (MAX_URBS = 12, ~64ms) absorbs system scheduling + * jitter during boot, while URB_ISO_ASAP ensures consistent xHCI scheduling. +- * QUIRK_FLAG_ALWAYS_SET_RATE: ++ * QUIRK_FLAG_ALWAYS_SET_RATE + * Issue SET_CUR for the sample rate even when the clock already reports the + * requested rate. A device advertising a single rate is otherwise never sent + * the request at all, and some require it before streaming will start. +@@ -297,10 +291,10 @@ enum { + QUIRK_TYPE_MIXER_PLAYBACK_LINEAR_VOL = 27, + QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, + QUIRK_TYPE_IFB_SILENCE_ON_EMPTY = 29, +- QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, ++ QUIRK_TYPE_MIXER_GET_CUR_OK = 30, + QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, + QUIRK_TYPE_ALWAYS_SET_RATE = 32, +-/* Please also edit snd_usb_audio_quirk_flag_names */ ++/* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */ + }; + + #define QUIRK_FLAG(x) BIT_U64(QUIRK_TYPE_ ## x) +@@ -335,7 +329,7 @@ enum { + #define QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL QUIRK_FLAG(MIXER_PLAYBACK_LINEAR_VOL) + #define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) + #define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY QUIRK_FLAG(IFB_SILENCE_ON_EMPTY) +-#define QUIRK_FLAG_MIXER_GET_CUR_BROKEN QUIRK_FLAG(MIXER_GET_CUR_BROKEN) ++#define QUIRK_FLAG_MIXER_GET_CUR_OK QUIRK_FLAG(MIXER_GET_CUR_OK) + #define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) + #define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) + diff --git a/pkgbuilds/linux-omarchy/0512-sound-fixes.patch b/pkgbuilds/linux-omarchy/0512-sound-fixes.patch new file mode 100644 index 0000000..9566376 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0512-sound-fixes.patch @@ -0,0 +1,507 @@ +diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c +--- a/sound/core/pcm_native.c ++++ b/sound/core/pcm_native.c +@@ -4023,20 +4023,33 @@ int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file, + return -EINVAL; + } + runtime = substream->runtime; +- if (runtime->state == SNDRV_PCM_STATE_OPEN) +- return -EBADFD; +- if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) +- return -ENXIO; ++ /* don't race with buffer reallocation in hw_params/hw_free */ ++ if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) ++ return -EBUSY; ++ if (runtime->state == SNDRV_PCM_STATE_OPEN) { ++ err = -EBADFD; ++ goto out; ++ } ++ if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) { ++ err = -ENXIO; ++ goto out; ++ } + if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || +- runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) +- return -EINVAL; ++ runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) { ++ err = -EINVAL; ++ goto out; ++ } + size = area->vm_end - area->vm_start; + offset = area->vm_pgoff << PAGE_SHIFT; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); +- if ((size_t)size > dma_bytes) +- return -EINVAL; +- if (offset > dma_bytes - size) +- return -EINVAL; ++ if ((size_t)size > dma_bytes) { ++ err = -EINVAL; ++ goto out; ++ } ++ if (offset > dma_bytes - size) { ++ err = -EINVAL; ++ goto out; ++ } + + area->vm_ops = &snd_pcm_vm_ops_data; + area->vm_private_data = substream; +@@ -4046,6 +4059,8 @@ int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file, + err = snd_pcm_lib_default_mmap(substream, area); + if (!err) + atomic_inc(&substream->mmap_count); ++out: ++ atomic_dec(&runtime->buffer_accessing); + return err; + } + EXPORT_SYMBOL(snd_pcm_mmap_data); +diff --git a/sound/core/ump.c b/sound/core/ump.c +--- a/sound/core/ump.c ++++ b/sound/core/ump.c +@@ -1335,6 +1335,8 @@ static void update_legacy_names(struct snd_ump_endpoint *ump) + { + struct snd_rawmidi *rmidi = ump->legacy_rmidi; + ++ if (!rmidi) ++ return; + update_legacy_substreams(ump, rmidi, SNDRV_RAWMIDI_STREAM_INPUT); + update_legacy_substreams(ump, rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT); + } +@@ -1343,6 +1345,8 @@ static void ump_legacy_set_rawmidi_name(struct snd_ump_endpoint *ump) + { + struct snd_rawmidi *rmidi = ump->legacy_rmidi; + ++ if (!rmidi) ++ return; + snprintf(rmidi->name, sizeof(rmidi->name), "%.68s (MIDI 1.0)", + ump->core.name); + } +diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c +--- a/sound/drivers/dummy.c ++++ b/sound/drivers/dummy.c +@@ -807,7 +807,7 @@ static int snd_dummy_capsrc_put(struct snd_kcontrol *kcontrol, struct snd_ctl_el + left = ucontrol->value.integer.value[0] & 1; + right = ucontrol->value.integer.value[1] & 1; + guard(spinlock_irq)(&dummy->mixer_lock); +- change = dummy->capture_source[addr][0] != left && ++ change = dummy->capture_source[addr][0] != left || + dummy->capture_source[addr][1] != right; + dummy->capture_source[addr][0] = left; + dummy->capture_source[addr][1] = right; +diff --git a/sound/hda/codecs/cirrus/cs420x.c b/sound/hda/codecs/cirrus/cs420x.c +--- a/sound/hda/codecs/cirrus/cs420x.c ++++ b/sound/hda/codecs/cirrus/cs420x.c +@@ -571,6 +571,7 @@ static const struct hda_model_fixup cs4208_models[] = { + + static const struct hda_quirk cs4208_fixup_tbl[] = { + SND_PCI_QUIRK_VENDOR(0x106b, "Apple", CS4208_MAC_AUTO), ++ SND_PCI_QUIRK(0x8086, 0x7270, "MacBookAir 7,2", CS4208_MAC_AUTO), + {} /* terminator */ + }; + +@@ -583,6 +584,7 @@ static const struct hda_quirk cs4208_mac_fixup_tbl[] = { + SND_PCI_QUIRK(0x106b, 0x7800, "MacPro 6,1", CS4208_MACMINI), + SND_PCI_QUIRK(0x106b, 0x7b00, "MacBookPro 12,1", CS4208_MBP11), + SND_PCI_QUIRK(0x106b, 0x7f00, "iMac 16,1", CS4208_MBP11), ++ SND_PCI_QUIRK(0x8086, 0x7270, "MacBookAir 7,2", CS4208_MBA6), + {} /* terminator */ + }; + +diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c +--- a/sound/hda/codecs/conexant.c ++++ b/sound/hda/codecs/conexant.c +@@ -249,6 +249,25 @@ static void cx_update_headset_mic_vref(struct hda_codec *codec, struct hda_jack_ + } + } + ++#define SN6140_S3_AFG_D0_DELAY_MS 1000 ++ ++static void cx_set_power_state(struct hda_codec *codec, hda_nid_t fg, ++ unsigned int power_state) ++{ ++ snd_hda_codec_write_sync(codec, fg, 0, AC_VERB_SET_POWER_STATE, power_state); ++ ++ /* ++ * SN6140 may not respond to AFG D0 immediately after S3. ++ * Wait before the D0 verb so the power-state command itself succeeds. ++ */ ++ if (codec->core.vendor_id == 0x14f11f87 && ++ power_state == AC_PWRST_D0 && ++ codec->core.dev.power.power_state.event == PM_EVENT_RESUME) ++ msleep(SN6140_S3_AFG_D0_DELAY_MS); ++ ++ snd_hda_codec_set_power_to_all(codec, fg, power_state); ++} ++ + static int cx_suspend(struct hda_codec *codec) + { + cx_auto_shutdown(codec); +@@ -1308,6 +1327,7 @@ static const struct hda_codec_ops cx_codec_ops = { + .init = cx_init, + .unsol_event = snd_hda_jack_unsol_event, + .suspend = cx_suspend, ++ .set_power_state = cx_set_power_state, + .check_power_status = snd_hda_gen_check_power_status, + .stream_pm = snd_hda_gen_stream_pm, + }; +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -2379,6 +2379,33 @@ static void alc_fixup_headset_mode_alc255_no_hp_mic(struct hda_codec *codec, + } + } + ++/* ++ * On the Acer Aspire A515-57G (and possibly other models sharing this ++ * board), if headphones are already inserted into the combo jack before ++ * the codec powers up (cold boot), the impedance-based headset-type ++ * sensing races and misclassifies the jack, driving the wrong output ++ * configuration (audible as missing center-panned/vocal content). A ++ * genuine physical unplug/replug after boot fixes it by forcing a fresh ++ * sense transient. Mirror that here on cold boot only: give the sense ++ * hardware time to settle, then force a fresh classification. ++ */ ++static void alc_fixup_headset_mode_acer_coldboot(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ struct alc_spec *spec = codec->spec; ++ ++ alc_fixup_headset_mode(codec, fix, action); ++ ++ if (action == HDA_FIXUP_ACT_INIT && ++ !is_s3_resume(codec) && !is_s4_resume(codec) && ++ spec->current_headset_mode != ALC_HEADSET_MODE_UNPLUGGED) { ++ msleep(500); ++ spec->current_headset_mode = ALC_HEADSET_MODE_UNKNOWN; ++ spec->current_headset_type = ALC_HEADSET_TYPE_UNKNOWN; ++ alc_fixup_headset_mode(codec, fix, action); ++ } ++} ++ + static void alc288_update_headset_jack_cb(struct hda_codec *codec, + struct hda_jack_callback *jack) + { +@@ -4248,6 +4275,7 @@ enum { + ALC282_FIXUP_ACER_DISABLE_LINEOUT, + ALC255_FIXUP_ACER_LIMIT_INT_MIC_BOOST, + ALC256_FIXUP_ACER_HEADSET_MIC, ++ ALC256_FIXUP_ACER_COLDBOOT, + ALC285_FIXUP_IDEAPAD_S740_COEF, + ALC285_FIXUP_HP_LIMIT_INT_MIC_BOOST, + ALC295_FIXUP_ASUS_DACS, +@@ -6311,6 +6339,12 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, ++ [ALC256_FIXUP_ACER_COLDBOOT] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_headset_mode_acer_coldboot, ++ .chained = true, ++ .chain_id = ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, ++ }, + [ALC285_FIXUP_IDEAPAD_S740_COEF] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_ideapad_s740_coef, +@@ -7148,13 +7182,14 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1597, "Acer Nitro 5 AN517-55", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x159e, "Acer Nitro 5 AN515-46", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), +- SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_COLDBOOT), + SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x1731, "Acer Predator PHN16-72", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x173a, "Acer Swift SFG14-73", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1758, "Acer Nitro ANV15-41", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), +@@ -8100,6 +8135,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3801, "Lenovo Yoga9 14IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x3802, "DuetITL 2021", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3802, "Lenovo Yoga Pro 9 14IRP8", ALC287_FIXUP_TAS2781_I2C), ++ SND_PCI_QUIRK(0x17aa, 0x380b, "Lenovo Yoga Slim 9 14ILL10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + /* Yoga Pro 9 16IMH9 and Legion 7 16ITHG6 share PCI SSID 17aa:3811 + * with Legion S7 15IMH05; use codec SSID to distinguish them + */ +@@ -8287,6 +8323,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1d05, 0x3034, "TongFang X6KK45xU", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1d05, 0x30ba, "TongFang XxAF5xxx", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1d17, 0x3288, "Haier Boyue G42", ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS), ++ SND_PCI_QUIRK(0x1d19, 0x0006, "VAIO VJS131", ALC233_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1d72, 0x1602, "RedmiBook", ALC255_FIXUP_XIAOMI_HEADSET_MIC), + SND_PCI_QUIRK(0x1d72, 0x1701, "XiaomiNotebook Pro", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1d72, 0x1901, "RedmiBook 14", ALC256_FIXUP_ASUS_HEADSET_MIC), +diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c +--- a/sound/hda/core/device.c ++++ b/sound/hda/core/device.c +@@ -404,6 +404,7 @@ static void setup_fg_nodes(struct hdac_device *codec) + */ + int snd_hdac_refresh_widgets(struct hdac_device *codec) + { ++ hda_nid_t fg = codec->afg ? codec->afg : codec->mfg; + hda_nid_t start_nid; + int nums, err = 0; + +@@ -412,10 +413,10 @@ int snd_hdac_refresh_widgets(struct hdac_device *codec) + * widgets array. + */ + guard(mutex)(&codec->widget_lock); +- nums = snd_hdac_get_sub_nodes(codec, codec->afg, &start_nid); ++ nums = snd_hdac_get_sub_nodes(codec, fg, &start_nid); + if (!start_nid || nums <= 0 || nums >= 0xff) { + dev_err(&codec->dev, "cannot read sub nodes for FG 0x%02x\n", +- codec->afg); ++ fg); + return -EINVAL; + } + +diff --git a/sound/usb/caiaq/audio.c b/sound/usb/caiaq/audio.c +--- a/sound/usb/caiaq/audio.c ++++ b/sound/usb/caiaq/audio.c +@@ -828,16 +828,13 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev) + + cdev->data_urbs_in = alloc_urbs(cdev, SNDRV_PCM_STREAM_CAPTURE, &ret); + if (ret < 0) { +- kfree(cdev->data_cb_info); +- free_urbs(cdev->data_urbs_in); ++ snd_usb_caiaq_audio_free(cdev); + return ret; + } + + cdev->data_urbs_out = alloc_urbs(cdev, SNDRV_PCM_STREAM_PLAYBACK, &ret); + if (ret < 0) { +- kfree(cdev->data_cb_info); +- free_urbs(cdev->data_urbs_in); +- free_urbs(cdev->data_urbs_out); ++ snd_usb_caiaq_audio_free(cdev); + return ret; + } + +@@ -858,6 +855,9 @@ void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev) + + dev_dbg(dev, "%s(%p)\n", __func__, cdev); + free_urbs(cdev->data_urbs_in); ++ cdev->data_urbs_in = NULL; + free_urbs(cdev->data_urbs_out); ++ cdev->data_urbs_out = NULL; + kfree(cdev->data_cb_info); ++ cdev->data_cb_info = NULL; + } +diff --git a/sound/usb/fcp.c b/sound/usb/fcp.c +--- a/sound/usb/fcp.c ++++ b/sound/usb/fcp.c +@@ -191,6 +191,10 @@ static int fcp_usb(struct usb_mixer_interface *mixer, u32 opcode, + const int max_retries = 5; + int err; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + if (!private->urb) + return -ENODEV; + +@@ -1026,6 +1030,10 @@ static int fcp_init(struct usb_mixer_interface *mixer, + struct usb_device *dev = mixer->chip->dev; + int err; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + FCP_USB_REQ_STEP0, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -518,6 +518,19 @@ static const struct usbmix_name_map audient_id14_map[] = { + {} + }; + ++/* ++ * Audient iD24: feature unit 12 ("Speaker Playback Volume") sits in the ++ * monitor-mixer branch and does not apply volume to all of its channels; ++ * when userspace adopts it as the master playback volume, the left main ++ * output stays at 0 dB while the right one is attenuated, producing a ++ * stereo imbalance. Rename it so that it is not picked up as the ++ * stream's master volume control. ++ */ ++static const struct usbmix_name_map audient_id24_map[] = { ++ { 12, "Monitor Mix Playback" }, /* FU, partial channel coverage */ ++ {} ++}; ++ + /* + * Control map entries + */ +@@ -611,6 +624,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x2708, 0x0008), + .map = audient_id14_map, + }, ++ { ++ /* Audient iD24 */ ++ .id = USB_ID(0x2708, 0x000d), ++ .map = audient_id24_map, ++ }, + { + /* KEF X300A */ + .id = USB_ID(0x27ac, 0x1000), +diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c +--- a/sound/usb/mixer_quirks.c ++++ b/sound/usb/mixer_quirks.c +@@ -3480,6 +3480,10 @@ static int snd_rme_digiface_write_reg(struct snd_kcontrol *kcontrol, int item, u + struct usb_device *dev = chip->dev; + int err; + ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + item, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, +@@ -3499,6 +3503,10 @@ static int snd_rme_digiface_read_status(struct snd_kcontrol *kcontrol, u32 statu + __le32 buf[4] = {}; + int err; + ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + RME_DIGIFACE_READ_STATUS, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, +diff --git a/sound/usb/mixer_s1810c.c b/sound/usb/mixer_s1810c.c +--- a/sound/usb/mixer_s1810c.c ++++ b/sound/usb/mixer_s1810c.c +@@ -474,6 +474,10 @@ snd_s1810c_switch_get(struct snd_kcontrol *kctl, + u32 state = 0; + int ret; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + guard(mutex)(&private->data_mutex); + ret = snd_s1810c_get_switch_state(mixer, kctl, &state); + if (ret < 0) +@@ -504,6 +508,10 @@ snd_s1810c_switch_set(struct snd_kcontrol *kctl, + u32 newval = 0; + int ret = 0; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + guard(mutex)(&private->data_mutex); + ret = snd_s1810c_get_switch_state(mixer, kctl, &curval); + if (ret < 0) +diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c +--- a/sound/usb/mixer_scarlett.c ++++ b/sound/usb/mixer_scarlett.c +@@ -707,6 +707,10 @@ static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, + int idx = snd_usb_ctrl_intf(elem->head.mixer->hostif) | (elem->head.id << 8); + int err; + ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_MEM, +diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c +--- a/sound/usb/mixer_scarlett2.c ++++ b/sound/usb/mixer_scarlett2.c +@@ -2603,9 +2603,9 @@ static int scarlett2_usb_rx(struct usb_device *dev, int interface, + } + + /* Send a proprietary format request to the Scarlett interface */ +-static int scarlett2_usb( +- struct usb_mixer_interface *mixer, u32 cmd, +- void *req_data, u16 req_size, void *resp_data, u16 resp_size) ++static int scarlett2_usb_nopm(struct usb_mixer_interface *mixer, u32 cmd, ++ void *req_data, u16 req_size, ++ void *resp_data, u16 resp_size) + { + struct scarlett2_data *private = mixer->private_data; + struct usb_device *dev = mixer->chip->dev; +@@ -2713,6 +2713,18 @@ static int scarlett2_usb( + return err; + } + ++static int scarlett2_usb(struct usb_mixer_interface *mixer, u32 cmd, ++ void *req_data, u16 req_size, ++ void *resp_data, u16 resp_size) ++{ ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ ++ return scarlett2_usb_nopm(mixer, cmd, req_data, req_size, ++ resp_data, resp_size); ++} ++ + /* Send a USB message to get data; result placed in *buf */ + static int scarlett2_usb_get( + struct usb_mixer_interface *mixer, +@@ -3020,9 +3032,21 @@ static int scarlett2_usb_set_config_buf( + /* Send SCARLETT2_USB_DATA_CMD SCARLETT2_USB_CONFIG_SAVE */ + static void scarlett2_config_save(struct usb_mixer_interface *mixer) + { +- int err; ++ __le32 req = cpu_to_le32(SCARLETT2_USB_CONFIG_SAVE); ++ int err = scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD, ++ &req, sizeof(req), NULL, 0); ++ ++ if (err < 0) ++ usb_audio_err(mixer->chip, "config save failed: %d\n", err); ++} ++ ++/* The USB suspend callback must not acquire another PM reference. */ ++static void scarlett2_config_save_nopm(struct usb_mixer_interface *mixer) ++{ ++ __le32 req = cpu_to_le32(SCARLETT2_USB_CONFIG_SAVE); ++ int err = scarlett2_usb_nopm(mixer, SCARLETT2_USB_DATA_CMD, ++ &req, sizeof(req), NULL, 0); + +- err = scarlett2_usb_activate_config(mixer, SCARLETT2_USB_CONFIG_SAVE); + if (err < 0) + usb_audio_err(mixer->chip, "config save failed: %d\n", err); + } +@@ -8639,7 +8663,7 @@ static void scarlett2_private_suspend(struct usb_mixer_interface *mixer) + struct scarlett2_data *private = mixer->private_data; + + if (cancel_delayed_work_sync(&private->work)) +- scarlett2_config_save(private->mixer); ++ scarlett2_config_save_nopm(private->mixer); + + scarlett2_cleanup_urb(mixer); + } +diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c +--- a/sound/usb/mixer_us16x08.c ++++ b/sound/usb/mixer_us16x08.c +@@ -151,6 +151,9 @@ static const char *const route_names[] = { + static int snd_us16x08_recv_urb(struct snd_usb_audio *chip, + unsigned char *buf, int size) + { ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; + + guard(mutex)(&chip->mutex); + snd_usb_ctl_msg(chip->dev, +@@ -165,6 +168,10 @@ static int snd_us16x08_recv_urb(struct snd_usb_audio *chip, + */ + static int snd_us16x08_send_urb(struct snd_usb_audio *chip, char *buf, int size) + { ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + return snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), + SND_US16X08_URB_REQUEST, SND_US16X08_URB_REQUESTTYPE, + 0, 0, buf, size); diff --git a/pkgbuilds/linux-omarchy/0513-xps13-sof-quirk.patch b/pkgbuilds/linux-omarchy/0513-xps13-sof-quirk.patch new file mode 100644 index 0000000..6a5a07c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0513-xps13-sof-quirk.patch @@ -0,0 +1,19 @@ +diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c +--- a/sound/soc/intel/boards/sof_sdw.c ++++ b/sound/soc/intel/boards/sof_sdw.c +@@ -845,6 +845,15 @@ static const struct dmi_system_id sof_sdw_quirk_table[] = { + }, + .driver_data = (void *)(SOC_SDW_PCH_DMIC), + }, ++ { ++ /* Dell XPS 13 DX13260 (Wildcat Lake), CS42L43 + 2x CS35L63 sidecar amps */ ++ .callback = sof_sdw_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0E53") ++ }, ++ .driver_data = (void *)(SOC_SDW_SIDECAR_AMPS), ++ }, + {} + }; + diff --git a/pkgbuilds/linux-omarchy/0514-rt766-stream-config-type.patch b/pkgbuilds/linux-omarchy/0514-rt766-stream-config-type.patch new file mode 100644 index 0000000..25fee81 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0514-rt766-stream-config-type.patch @@ -0,0 +1,30 @@ +diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c +--- a/sound/soc/codecs/rt766-sdca.c ++++ b/sound/soc/codecs/rt766-sdca.c +@@ -936,9 +936,8 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; + unsigned int sampling_rate; + int retval, port; +@@ -957,7 +956,6 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + + /* SoundWire specific configuration */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; + if (dai->id == RT766_AIF1) + port = 3; + else if (dai->id == RT766_AIF2) +@@ -965,7 +963,6 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } else { +- direction = SDW_DATA_DIR_TX; + if (dai->id == RT766_AIF1) + port = 12; + else if (dai->id == RT766_AIF3) diff --git a/pkgbuilds/linux-omarchy/0516-hda-realtek-rog-strix-g733zw-speakers.patch b/pkgbuilds/linux-omarchy/0516-hda-realtek-rog-strix-g733zw-speakers.patch new file mode 100644 index 0000000..47dae22 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0516-hda-realtek-rog-strix-g733zw-speakers.patch @@ -0,0 +1,39 @@ +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -4226,6 +4226,7 @@ enum { + ALC294_FIXUP_ASUS_GU502_VERBS, + ALC294_FIXUP_ASUS_G513_PINS, + ALC285_FIXUP_ASUS_G533Z_PINS, ++ ALC285_FIXUP_ASUS_G733Z_VERBS, + ALC285_FIXUP_HP_GPIO_LED, + ALC285_FIXUP_HP_MUTE_LED, + ALC285_FIXUP_HP_SPECTRE_X360_MUTE_LED, +@@ -7129,6 +7130,19 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC287_FIXUP_TXNW2781_I2C, + }, ++ [ALC285_FIXUP_ASUS_G733Z_VERBS] = { ++ .type = HDA_FIXUP_VERBS, ++ .v.verbs = (const struct hda_verb[]) { ++ /* Enables internal speaker */ ++ {0x17, AC_VERB_SET_CONNECT_SEL, 0x00}, ++ {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, ++ {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, ++ {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, ++ {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00}, ++ {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00}, ++ { } ++ } ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -7775,6 +7789,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x12a3, "Asus N7691ZM", ALC269_FIXUP_ASUS_N7601ZM), + SND_PCI_QUIRK(0x1043, 0x12af, "ASUS UX582ZS", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x12b4, "ASUS B3405CCA / P3405CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), ++ SND_PCI_QUIRK(0x1043, 0x12bf, "ASUS ROG Strix G733ZW", ALC285_FIXUP_ASUS_G733Z_VERBS), + SND_PCI_QUIRK(0x1043, 0x12e0, "ASUS X541SA", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x12f0, "ASUS X541UV", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1313, "Asus K42JZ", ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE), diff --git a/pkgbuilds/linux-omarchy/0517-asoc-amd-yc-acer-aspire-a314-23p.patch b/pkgbuilds/linux-omarchy/0517-asoc-amd-yc-acer-aspire-a314-23p.patch new file mode 100644 index 0000000..0d8e619 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0517-asoc-amd-yc-acer-aspire-a314-23p.patch @@ -0,0 +1,17 @@ +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -619,6 +619,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFA16-41"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "MDU"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A314-23P"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { diff --git a/pkgbuilds/linux-omarchy/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch b/pkgbuilds/linux-omarchy/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch new file mode 100644 index 0000000..3762b2e --- /dev/null +++ b/pkgbuilds/linux-omarchy/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch @@ -0,0 +1,41 @@ +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -173,6 +173,19 @@ static const struct acpi_device_id ivsc_acpi_ids[] = { + { "INTC10E1" }, /* PTL */ + }; + ++/* ++ * The subset of ivsc_acpi_ids[] which are IVSC, rather than CVS, devices. The ++ * CVS IDs are deliberately not listed here: new ones keep being added, whereas ++ * this list is complete. ++ */ ++static const struct acpi_device_id ivsc_only_acpi_ids[] = { ++ { "INTC1059" }, ++ { "INTC1095" }, ++ { "INTC100A" }, ++ { "INTC10CF" }, ++ { } ++}; ++ + static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev) + { + unsigned int i; +@@ -224,6 +237,17 @@ static struct device *ipu_bridge_get_ivsc_csi_dev(struct acpi_device *adev) + return csi_dev; + } + ++ /* ++ * The lookups below match on the ACPI companion alone. That is fine for ++ * CVS, which binds a driver to that very device, but not for IVSC: there ++ * the ACPI device also has a driverless platform device, which would be ++ * returned instead of the mei-csi client. Return NULL for IVSC so that ++ * the caller fails and the probe is retried once the IVSC device shows ++ * up. ++ */ ++ if (!acpi_match_device_ids(adev, ivsc_only_acpi_ids)) ++ return NULL; ++ + /* Try to locate CVS device on the I2C bus */ + csi_dev = bus_find_device_by_acpi_dev(&i2c_bus_type, adev); + if (csi_dev) diff --git a/pkgbuilds/linux-omarchy/0541-cvs-nova-lake-acpi-id.patch b/pkgbuilds/linux-omarchy/0541-cvs-nova-lake-acpi-id.patch new file mode 100644 index 0000000..39ccf4d --- /dev/null +++ b/pkgbuilds/linux-omarchy/0541-cvs-nova-lake-acpi-id.patch @@ -0,0 +1,33 @@ +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -862,6 +862,7 @@ static const char * const acpi_honor_dep_ids[] = { + "INTC10DE", /* CVS (LNL) driver must be loaded to allow camera streaming */ + "INTC10E0", /* CVS (ARL) driver must be loaded to allow camera streaming */ + "INTC10E1", /* CVS (PTL) driver must be loaded to allow camera streaming */ ++ "INTC10FA", /* CVS (NVL) driver must be loaded to allow camera streaming */ + "RSCV0001", /* RISC-V PLIC */ + "RSCV0002", /* RISC-V APLIC */ + "RSCV0005", /* RISC-V SBI MPXY MBOX */ +diff --git a/drivers/media/i2c/cvs/core.c b/drivers/media/i2c/cvs/core.c +--- a/drivers/media/i2c/cvs/core.c ++++ b/drivers/media/i2c/cvs/core.c +@@ -962,6 +962,7 @@ static const struct acpi_device_id intel_cvs_acpi_match[] = { + { "INTC10DE" }, /* LNL */ + { "INTC10E0" }, /* ARL */ + { "INTC10E1" }, /* PTL */ ++ { "INTC10FA" }, /* NVL */ + { } + }; + MODULE_DEVICE_TABLE(acpi, intel_cvs_acpi_match); +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -171,6 +171,7 @@ static const struct acpi_device_id ivsc_acpi_ids[] = { + { "INTC10DE" }, /* LNL */ + { "INTC10E0" }, /* ARL */ + { "INTC10E1" }, /* PTL */ ++ { "INTC10FA" }, /* NVL */ + }; + + /* diff --git a/pkgbuilds/linux-omarchy/0542-media-cvs-wake-irq-without-claiming-gpio.patch b/pkgbuilds/linux-omarchy/0542-media-cvs-wake-irq-without-claiming-gpio.patch new file mode 100644 index 0000000..6a78d2e --- /dev/null +++ b/pkgbuilds/linux-omarchy/0542-media-cvs-wake-irq-without-claiming-gpio.patch @@ -0,0 +1,33 @@ +diff --git a/drivers/media/i2c/cvs/core.c b/drivers/media/i2c/cvs/core.c +--- a/drivers/media/i2c/cvs/core.c ++++ b/drivers/media/i2c/cvs/core.c +@@ -723,8 +723,6 @@ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c) + } + + if (ctx->res == ICVS_FULLCAP) { +- struct gpio_desc *wake; +- + ctx->rst = devm_gpiod_get(dev, "rst", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->rst)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->rst), +@@ -732,14 +730,12 @@ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c) + goto err_put_ipu; + } + +- wake = devm_gpiod_get(dev, "wake", GPIOD_IN); +- if (IS_ERR(wake)) { +- ret = dev_err_probe(dev, PTR_ERR(wake), +- "failed to get wake GPIO\n"); +- goto err_put_ipu; +- } +- +- ctx->irq = gpiod_to_irq(wake); ++ /* ++ * Do not request the line: another device's _CRS may list ++ * the same pin, and its driver would then fail with -EBUSY. ++ */ ++ ctx->irq = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(dev), ++ "wake", 0); + if (ctx->irq < 0) { + ret = dev_err_probe(dev, ctx->irq, + "failed to get wake IRQ\n"); diff --git a/pkgbuilds/linux-omarchy/0560-input.patch b/pkgbuilds/linux-omarchy/0560-input.patch new file mode 100644 index 0000000..0d58569 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0560-input.patch @@ -0,0 +1,76 @@ +diff --git a/drivers/input/mouse/focaltech.c b/drivers/input/mouse/focaltech.c +--- a/drivers/input/mouse/focaltech.c ++++ b/drivers/input/mouse/focaltech.c +@@ -78,8 +78,8 @@ struct focaltech_finger_state { + * Absolute position (from the bottom left corner) of the + * finger. + */ +- unsigned int x; +- unsigned int y; ++ int x; ++ int y; + }; + + /* +@@ -108,7 +108,7 @@ struct focaltech_hw_state { + }; + + struct focaltech_data { +- unsigned int x_max, y_max; ++ int x_max, y_max; + struct focaltech_hw_state state; + }; + +@@ -126,17 +126,16 @@ static void focaltech_report_state(struct psmouse *psmouse) + input_mt_slot(dev, i); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, active); + if (active) { +- unsigned int clamped_x, clamped_y; + /* + * The touchpad might report invalid data, so we clamp + * the resulting values so that we do not confuse +- * userspace. ++ * userspace or accumulate coordinate wind-up. + */ +- clamped_x = clamp(finger->x, 0U, priv->x_max); +- clamped_y = clamp(finger->y, 0U, priv->y_max); +- input_report_abs(dev, ABS_MT_POSITION_X, clamped_x); ++ finger->x = clamp(finger->x, 0, priv->x_max); ++ finger->y = clamp(finger->y, 0, priv->y_max); ++ input_report_abs(dev, ABS_MT_POSITION_X, finger->x); + input_report_abs(dev, ABS_MT_POSITION_Y, +- priv->y_max - clamped_y); ++ priv->y_max - finger->y); + input_report_abs(dev, ABS_TOOL_WIDTH, state->width); + } + } +diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c +--- a/drivers/input/mouse/psmouse-base.c ++++ b/drivers/input/mouse/psmouse-base.c +@@ -267,7 +267,15 @@ void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) + */ + static int psmouse_handle_byte(struct psmouse *psmouse) + { +- psmouse_ret_t rc = psmouse->protocol_handler(psmouse); ++ psmouse_ret_t rc; ++ ++ /* protocol_handler is NULL when device is being disconnected */ ++ if (unlikely(!psmouse->protocol_handler)) { ++ psmouse->pktcnt = 0; ++ return 0; ++ } ++ ++ rc = psmouse->protocol_handler(psmouse); + + switch (rc) { + case PSMOUSE_BAD_DATA: +@@ -1466,6 +1474,9 @@ static void psmouse_disconnect(struct serio *serio) + psmouse_deactivate(parent); + } + ++ scoped_guard(serio_pause_rx, serio) ++ psmouse->protocol_handler = NULL; ++ + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + diff --git a/pkgbuilds/linux-omarchy/0565-i2c-asue140d-touchpad-100khz.patch b/pkgbuilds/linux-omarchy/0565-i2c-asue140d-touchpad-100khz.patch new file mode 100644 index 0000000..01748b9 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0565-i2c-asue140d-touchpad-100khz.patch @@ -0,0 +1,11 @@ +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -373,6 +373,7 @@ static const struct acpi_device_id i2c_acpi_force_100khz_device_ids[] = { + * the device works without issues on Windows at what is expected to be + * a 400KHz frequency. The root cause of the issue is not known. + */ ++ { "ASUE140D", 0 }, + { "DLL0945", 0 }, + { "ELAN0678", 0 }, + { "ELAN06FA", 0 }, diff --git a/pkgbuilds/linux-omarchy/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch b/pkgbuilds/linux-omarchy/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch new file mode 100644 index 0000000..e569f4c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch @@ -0,0 +1,24 @@ +diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c +--- a/drivers/hid/hid-asus.c ++++ b/drivers/hid/hid-asus.c +@@ -1294,12 +1294,14 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) + return ret; + } + +- for (int r = 0; r < ARRAY_SIZE(asus_report_id_init); r++) { +- if (asus_has_report_id(hdev, asus_report_id_init[r])) { +- ret = asus_kbd_init(hdev, asus_report_id_init[r]); +- if (ret < 0) +- hid_warn(hdev, "Failed to initialize 0x%x: %d.\n", +- asus_report_id_init[r], ret); ++ if (!drvdata->tp) { ++ for (int r = 0; r < ARRAY_SIZE(asus_report_id_init); r++) { ++ if (asus_has_report_id(hdev, asus_report_id_init[r])) { ++ ret = asus_kbd_init(hdev, asus_report_id_init[r]); ++ if (ret < 0) ++ hid_warn(hdev, "Failed to initialize 0x%x: %d.\n", ++ asus_report_id_init[r], ret); ++ } + } + } + diff --git a/pkgbuilds/linux-omarchy/0600-usb4stream-fixes.patch b/pkgbuilds/linux-omarchy/0600-usb4stream-fixes.patch new file mode 100644 index 0000000..5fe836e --- /dev/null +++ b/pkgbuilds/linux-omarchy/0600-usb4stream-fixes.patch @@ -0,0 +1,47 @@ +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +index 4cc86d8d6491..43d29d69dcdb 100644 +--- a/drivers/thunderbolt/stream.c ++++ b/drivers/thunderbolt/stream.c +@@ -512,8 +512,10 @@ tbstream_dev_alloc_tx(struct tbstream_dev *sdev, enum tbstream_frame_pdf pdf, + dma_sync_single_for_cpu(dma_dev, sf->frame.buffer_phy, size, + DMA_TO_DEVICE); + if (pdf == TBSTREAM_DATA) { +- if (copy_page_from_iter(sf->page, 0, size, from) != size) ++ if (copy_page_from_iter(sf->page, 0, size, from) != size) { ++ sdev->tx_ring.cons--; + return ERR_PTR(-EFAULT); ++ } + } else { + memset(page_address(sf->page), 0, size); + } +@@ -671,7 +673,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + } + + nbytes = 0; +- while (nbytes < iov_iter_count(to)) { ++ while (iov_iter_count(to)) { + struct tbstream_frame *sf; + size_t size, sf_size; + +@@ -693,7 +695,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + } + + sf_size = tb_ring_frame_size(&sf->frame); +- size = min(iov_iter_count(to) - nbytes, sf_size); ++ size = min(iov_iter_count(to), sf_size); + + if (copy_page_to_iter(sf->page, sf->offset, size, to) != size) { + ret = -EFAULT; +@@ -763,10 +765,10 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + } + + nbytes = 0; +- while (nbytes < iov_iter_count(from)) { ++ while (iov_iter_count(from)) { + size_t size; + +- size = min(iov_iter_count(from) - nbytes, TB_MAX_FRAME_SIZE); ++ size = min(iov_iter_count(from), TB_MAX_FRAME_SIZE); + ret = tbstream_dev_send_data(sdev, from, size); + if (ret) { + /* diff --git a/pkgbuilds/linux-omarchy/0601-usb4stream-busy-poll.patch b/pkgbuilds/linux-omarchy/0601-usb4stream-busy-poll.patch new file mode 100644 index 0000000..0cb7d2a --- /dev/null +++ b/pkgbuilds/linux-omarchy/0601-usb4stream-busy-poll.patch @@ -0,0 +1,477 @@ +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +index 35e3c119d5ee..a4816db5cacd 100644 +--- a/drivers/thunderbolt/nhi.c ++++ b/drivers/thunderbolt/nhi.c +@@ -235,6 +235,12 @@ static void ring_write_descriptors(struct tb_ring *ring) + { + struct ring_frame *frame, *n; + struct ring_desc *descriptor; ++ u32 flags; ++ ++ flags = RING_DESC_POSTED; ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ flags |= RING_DESC_INTERRUPT; ++ + list_for_each_entry_safe(frame, n, &ring->queue, list) { + if (ring_full(ring)) + break; +@@ -242,7 +248,7 @@ static void ring_write_descriptors(struct tb_ring *ring) + descriptor = &ring->descriptors[ring->head]; + descriptor->phys = frame->buffer_phy; + descriptor->time = 0; +- descriptor->flags = RING_DESC_POSTED | RING_DESC_INTERRUPT; ++ descriptor->flags = flags; + if (ring->is_tx) { + descriptor->length = frame->size; + descriptor->eof = frame->eof; +@@ -339,8 +345,9 @@ EXPORT_SYMBOL_GPL(__tb_ring_enqueue); + * @ring: Ring to poll + * + * 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. ++ * has been called or the ring is created with %RING_FLAG_NO_INTERRUPT. ++ * It will read one completed frame from the ring and return it to the ++ * caller. + * + * Return: Pointer to &struct ring_frame, %NULL if there is no more + * completed frames. +@@ -538,6 +545,12 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + dev_dbg(nhi->dev, "allocating %s ring %d of size %d\n", + transmit ? "TX" : "RX", hop, size); + ++ if ((flags & RING_FLAG_NO_INTERRUPT) && start_poll) { ++ dev_WARN(nhi->dev, ++ "start_poll() and NO_INTERRUPT cannot be used at the same time\n"); ++ return NULL; ++ } ++ + ring = kzalloc_obj(*ring); + if (!ring) + return NULL; +@@ -568,7 +581,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + if (!ring->descriptors) + goto err_free_ring; + +- if (nhi->ops->request_ring_irq) { ++ if (!(flags & RING_FLAG_NO_INTERRUPT) && nhi->ops->request_ring_irq) { + if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND)) + goto err_free_descs; + } +@@ -701,7 +714,8 @@ void tb_ring_start(struct tb_ring *ring) + ring_iowrite32options(ring, flags, 0); + } + +- ring_interrupt_active(ring, true); ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ ring_interrupt_active(ring, true); + ring->running = true; + err: + spin_unlock(&ring->lock); +@@ -761,7 +775,8 @@ void tb_ring_stop(struct tb_ring *ring) + RING_TYPE(ring), ring->hop); + goto err; + } +- ring_interrupt_active(ring, false); ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ ring_interrupt_active(ring, false); + + ring_iowrite32options(ring, 0, 0); + ring_iowrite64desc(ring, 0, 0); +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +index 43d29d69dcdb..a7d9fda66875 100644 +--- a/drivers/thunderbolt/stream.c ++++ b/drivers/thunderbolt/stream.c +@@ -9,10 +9,12 @@ + + #define pr_fmt(fmt) "tbstream: " fmt + ++#include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -128,6 +130,7 @@ struct tbstream_ring { + * @out_hopid: Out HopID + * @ring_size: Size of the rings + * @throttling: Interrupt throttling rate in ns ++ * @busy_poll: Instead of interrupts, busy poll the rings + * @users: Number of times @cdev has been opened + * @closed: CLOSE packet was received + * @removed: Userspace removed the ConfigFS group underneath. +@@ -147,6 +150,7 @@ struct tbstream_dev { + int out_hopid; + unsigned int ring_size; + unsigned int throttling; ++ bool busy_poll; + int users; + bool closed; + bool removed; +@@ -536,10 +540,39 @@ tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from, + return tb_ring_tx(sdev->tx_ring.ring, &sf->frame); + } + ++static void ++tbstream_dev_poll_ring(struct tbstream_dev *sdev, struct tbstream_ring *ring) ++{ ++ struct ring_frame *frame; ++ ++ if (!sdev->busy_poll) ++ return; ++ ++ while ((frame = tb_ring_poll(ring->ring))) ++ frame->callback(ring->ring, frame, false); ++} ++ + static int tbstream_dev_send_close(struct tbstream_dev *sdev) + { + struct tbstream_frame *sf; + ++ if (sdev->busy_poll) { ++ /* ++ * When busy polling it's the write(2) path that ++ * advances the completions so it is possible that the ++ * ring is full at this point. Advance the ring here so ++ * that there is room for the CLOSE packet to be sent. ++ */ ++ ktime_t timeout = ktime_add_ms(ktime_get(), 500); ++ ++ do { ++ if (tbstream_ring_available(&sdev->tx_ring)) ++ break; ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); ++ fsleep(15); ++ } while (ktime_before(ktime_get(), timeout)); ++ } ++ + sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_CLOSE, NULL, SZ_256); + if (IS_ERR(sf)) + return PTR_ERR(sf); +@@ -549,12 +582,15 @@ static int tbstream_dev_send_close(struct tbstream_dev *sdev) + static int tbstream_dev_start(struct tbstream_dev *sdev) + { + struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); ++ unsigned int flags = RING_FLAG_FRAME | RING_FLAG_E2E; + u16 sof_mask, eof_mask; + struct tb_ring *ring; + int ret, e2e_tx_hop; + +- ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, +- RING_FLAG_FRAME | RING_FLAG_E2E); ++ if (sdev->busy_poll) ++ flags |= RING_FLAG_NO_INTERRUPT; ++ ++ ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, flags); + if (!ring) + return -ENOMEM; + sdev->tx_ring.ring = ring; +@@ -567,9 +603,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) + sof_mask = BIT(TBSTREAM_FRAME_START); + eof_mask = BIT(TBSTREAM_DATA) | BIT(TBSTREAM_CLOSE); + +- ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, +- RING_FLAG_FRAME | RING_FLAG_E2E, e2e_tx_hop, +- sof_mask, eof_mask, NULL, NULL); ++ ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, flags, ++ e2e_tx_hop, sof_mask, eof_mask, NULL, NULL); + if (!ring) { + ret = -ENOMEM; + goto err_free_tx_buffers; +@@ -607,15 +642,43 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) + return ret; + } + ++static bool tbstream_dev_tx_drained(const struct tbstream_dev *sdev) ++{ ++ const struct tbstream_ring *ring = &sdev->tx_ring; ++ ++ /* ++ * Everything is completed when number of free TX slots is back ++ * to the maximum. ++ */ ++ return ring->prod - ring->cons == tb_ring_size(ring->ring) - 1; ++} ++ + static void tbstream_dev_stop(struct tbstream_dev *sdev) + { + struct tb_xdomain *xd; + +- /* Wait for the ring to complete any outstanding frames */ +- tb_ring_flush(sdev->tx_ring.ring, 500); +- tb_ring_stop(sdev->tx_ring.ring); +- tb_ring_flush(sdev->rx_ring.ring, 500); +- tb_ring_stop(sdev->rx_ring.ring); ++ if (sdev->busy_poll) { ++ /* ++ * When busy polling we must advance the ring ourselves ++ * to push all outstanding frames on the wire. ++ */ ++ ktime_t timeout = ktime_add_ms(ktime_get(), 500); ++ ++ do { ++ if (tbstream_dev_tx_drained(sdev)) ++ break; ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); ++ fsleep(15); ++ } while (ktime_before(ktime_get(), timeout)); ++ ++ tb_ring_stop(sdev->tx_ring.ring); ++ tb_ring_stop(sdev->rx_ring.ring); ++ } else { ++ tb_ring_flush(sdev->tx_ring.ring, 500); ++ tb_ring_stop(sdev->tx_ring.ring); ++ tb_ring_flush(sdev->rx_ring.ring, 500); ++ tb_ring_stop(sdev->rx_ring.ring); ++ } + + xd = tbstream_dev_xdomain(sdev); + if (xd) { +@@ -633,10 +696,24 @@ static void tbstream_dev_stop(struct tbstream_dev *sdev) + sdev->tx_ring.ring = NULL; + } + ++/* Use only with read_iter/write_iter() to handle nowait */ ++static int tbstream_dev_lock(struct tbstream_dev *sdev, bool nowait) ++{ ++ if (nowait) { ++ if (!mutex_trylock(&sdev->lock)) ++ return -EAGAIN; ++ } else { ++ if (mutex_lock_interruptible(&sdev->lock)) ++ return -ERESTARTSYS; ++ } ++ return 0; ++} ++ + static ssize_t + tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + { + struct file *file = kiocb->ki_filp; ++ bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; +@@ -645,31 +722,50 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + if (ret) + return ret; + +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; ++ ret = tbstream_dev_lock(sdev, nowait); ++ if (ret) ++ return ret; + +- while (!tbstream_ring_available(&sdev->rx_ring)) { +- mutex_unlock(&sdev->lock); +- +- if (file->f_flags & O_NONBLOCK) +- return -EAGAIN; +- ret = wait_event_interruptible(sdev->wait, +- tbstream_ring_available(&sdev->rx_ring) || +- tbstream_dev_valid(sdev) != 0 || +- tbstream_dev_closed(sdev) || +- tbstream_dev_removed(sdev)); +- if (ret) +- return ret; ++ for (;;) { ++ /* When busy polling, advance any completions manually */ ++ tbstream_dev_poll_ring(sdev, &sdev->rx_ring); + + ret = tbstream_dev_valid(sdev); ++ if (ret) { ++ mutex_unlock(&sdev->lock); ++ return ret; ++ } ++ ++ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { ++ mutex_unlock(&sdev->lock); ++ return 0; ++ } ++ ++ if (tbstream_ring_available(&sdev->rx_ring)) ++ break; ++ ++ mutex_unlock(&sdev->lock); ++ ++ if (nowait) ++ return -EAGAIN; ++ ++ if (sdev->busy_poll) { ++ if (signal_pending(current)) ++ return -ERESTARTSYS; ++ cond_resched(); ++ } else { ++ ret = wait_event_interruptible(sdev->wait, ++ tbstream_ring_available(&sdev->rx_ring) || ++ tbstream_dev_valid(sdev) != 0 || ++ tbstream_dev_closed(sdev) || ++ tbstream_dev_removed(sdev)); ++ if (ret) ++ return ret; ++ } ++ ++ ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; +- +- if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) +- return 0; +- +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; + } + + nbytes = 0; +@@ -729,6 +825,7 @@ static ssize_t + tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + { + struct file *file = kiocb->ki_filp; ++ bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; +@@ -737,31 +834,49 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + if (ret) + return ret; + +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; ++ ret = tbstream_dev_lock(sdev, nowait); ++ if (ret) ++ return ret; + +- while (!tbstream_ring_available(&sdev->tx_ring)) { +- mutex_unlock(&sdev->lock); +- +- if (file->f_flags & O_NONBLOCK) +- return -EAGAIN; +- ret = wait_event_interruptible(sdev->wait, +- tbstream_ring_available(&sdev->tx_ring) || +- tbstream_dev_valid(sdev) != 0 || +- tbstream_dev_closed(sdev) || +- tbstream_dev_removed(sdev)); +- if (ret) +- return ret; ++ for (;;) { ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); + + ret = tbstream_dev_valid(sdev); ++ if (ret) { ++ mutex_unlock(&sdev->lock); ++ return ret; ++ } ++ ++ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { ++ mutex_unlock(&sdev->lock); ++ return -ENXIO; ++ } ++ ++ if (tbstream_ring_available(&sdev->tx_ring)) ++ break; ++ ++ mutex_unlock(&sdev->lock); ++ ++ if (nowait) ++ return -EAGAIN; ++ ++ if (sdev->busy_poll) { ++ if (signal_pending(current)) ++ return -ERESTARTSYS; ++ cond_resched(); ++ } else { ++ ret = wait_event_interruptible(sdev->wait, ++ tbstream_ring_available(&sdev->tx_ring) || ++ tbstream_dev_valid(sdev) != 0 || ++ tbstream_dev_closed(sdev) || ++ tbstream_dev_removed(sdev)); ++ if (ret) ++ return ret; ++ } ++ ++ ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; +- +- if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) +- return -ENXIO; +- +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; + } + + nbytes = 0; +@@ -795,6 +910,13 @@ tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait) + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + __poll_t mask = 0; + ++ /* ++ * Without interrupts there is nothing that can wake us up so ++ * return failure instead. ++ */ ++ if (sdev->busy_poll) ++ return EPOLLERR; ++ + poll_wait(file, &sdev->wait, wait); + guard(mutex)(&sdev->lock); + if (tbstream_dev_valid(sdev) != 0) { +@@ -904,6 +1026,35 @@ tbstream_dev_from_group(struct config_group *group) + return container_of(group, struct tbstream_dev, group); + } + ++static ssize_t tbstream_dev_busy_poll_show(struct config_item *item, char *buf) ++{ ++ struct config_group *group = to_config_group(item); ++ struct tbstream_dev *sdev = tbstream_dev_from_group(group); ++ ++ return sysfs_emit(buf, "%u\n", sdev->busy_poll); ++} ++ ++static ssize_t ++tbstream_dev_busy_poll_store(struct config_item *item, const char *buf, ++ size_t count) ++{ ++ struct config_group *group = to_config_group(item); ++ struct tbstream_dev *sdev = tbstream_dev_from_group(group); ++ bool busy_poll; ++ int ret; ++ ++ ret = kstrtobool(buf, &busy_poll); ++ if (ret) ++ return ret; ++ ++ guard(mutex)(&sdev->lock); ++ if (sdev->users) ++ return -EBUSY; ++ sdev->busy_poll = busy_poll; ++ return count; ++} ++CONFIGFS_ATTR(tbstream_dev_, busy_poll); ++ + static ssize_t tbstream_dev_index_show(struct config_item *item, char *buf) + { + struct config_group *group = to_config_group(item); +@@ -1210,6 +1361,7 @@ tbstream_dev_throttling_store(struct config_item *item, const char *buf, + CONFIGFS_ATTR(tbstream_dev_, throttling); + + static struct configfs_attribute *tbstream_dev_attrs[] = { ++ &tbstream_dev_attr_busy_poll, + &tbstream_dev_attr_index, + &tbstream_dev_attr_in_hopid, + &tbstream_dev_attr_out_hopid, +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +index 557288c0274b..8367d106d81b 100644 +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -592,6 +592,8 @@ struct tb_ring { + #define RING_FLAG_FRAME BIT(1) + /* Enable end-to-end flow control */ + #define RING_FLAG_E2E BIT(2) ++/* Do not enable interrupt for the ring */ ++#define RING_FLAG_NO_INTERRUPT BIT(3) + + struct ring_frame; + typedef void (*ring_cb)(struct tb_ring *, struct ring_frame *, bool canceled); diff --git a/pkgbuilds/linux-omarchy/0610-typec-cable-altmode-check.patch b/pkgbuilds/linux-omarchy/0610-typec-cable-altmode-check.patch new file mode 100644 index 0000000..3db7549 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0610-typec-cable-altmode-check.patch @@ -0,0 +1,132 @@ +diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c +index 263a89c5f324..5ee33a69b9cf 100644 +--- a/drivers/usb/typec/altmodes/displayport.c ++++ b/drivers/usb/typec/altmodes/displayport.c +@@ -790,7 +790,6 @@ int dp_altmode_probe(struct typec_altmode *alt) + dp->alt = alt; + + alt->desc = "DisplayPort"; +- typec_altmode_set_ops(alt, &dp_altmode_ops); + + if (plug) { + plug->desc = "Displayport"; +@@ -811,6 +810,10 @@ int dp_altmode_probe(struct typec_altmode *alt) + if (plug) + typec_altmode_set_drvdata(plug, dp); + ++ if ((alt->vdo & DP_CAP_RECEPTACLE) && typec_cable_altmode_unsupported(alt)) ++ return 0; ++ ++ typec_altmode_set_ops(alt, &dp_altmode_ops); + if (!alt->mode_selection) { + dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER; + schedule_work(&dp->work); +diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c +index 32250b94262a..2eccdddf1b1f 100644 +--- a/drivers/usb/typec/altmodes/thunderbolt.c ++++ b/drivers/usb/typec/altmodes/thunderbolt.c +@@ -284,6 +284,10 @@ static int tbt_altmode_probe(struct typec_altmode *alt) + + alt->desc = "Thunderbolt3"; + typec_altmode_set_drvdata(alt, tbt); ++ ++ if (typec_cable_altmode_unsupported(alt)) ++ return 0; ++ + typec_altmode_set_ops(alt, &tbt_altmode_ops); + + if (!alt->mode_selection && tbt_ready(alt)) { +diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c +index 0595e8cb83aa..54c7810b563b 100644 +--- a/drivers/usb/typec/class.c ++++ b/drivers/usb/typec/class.c +@@ -1429,6 +1429,77 @@ int typec_cable_is_active(struct typec_cable *cable) + } + EXPORT_SYMBOL_GPL(typec_cable_is_active); + ++enum typec_cable_altmode_support { ++ CABLE_SUPPORT_UNKNOWN, ++ CABLE_SUPPORTED, ++ CABLE_NOT_SUPPORTED, ++}; ++ ++static enum typec_cable_altmode_support ++typec_cable_check_altmode_support(struct typec_cable *cable, ++ struct typec_altmode *alt) ++{ ++ struct typec_altmode *plug; ++ u32 speed; ++ ++ /* ++ * Check if the cable has an e-marker, supports modal operation, and the ++ * SOP' altmode nodes are created. ++ */ ++ plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); ++ if (plug) { ++ typec_altmode_put_plug(plug); ++ return CABLE_SUPPORTED; ++ } ++ ++ /* The identity is not specified */ ++ if (!cable->identity) ++ return CABLE_SUPPORT_UNKNOWN; ++ ++ /* Non-e-marked cable */ ++ if (!cable->identity->id_header) ++ return CABLE_NOT_SUPPORTED; ++ ++ switch (PD_IDH_PTYPE(cable->identity->id_header)) { ++ case IDH_PTYPE_PCABLE: ++ speed = VDO_TYPEC_CABLE_SPEED(cable->identity->vdo[0]); ++ if (speed == CABLE_USB2_ONLY) ++ return CABLE_NOT_SUPPORTED; ++ return CABLE_SUPPORTED; ++ case IDH_PTYPE_ACABLE: ++ /* ++ * Active cables must establish an SOP' communication ++ * node. Since that check failed at the beginning of ++ * this function, this active cable does not support ++ * this specific altmode. ++ */ ++ return CABLE_NOT_SUPPORTED; ++ } ++ ++ return CABLE_SUPPORT_UNKNOWN; ++} ++ ++/** ++ * typec_cable_altmode_unsupported - Check if a cable restricts altmode ++ * @alt: The Alternate Mode to evaluate ++ * ++ * Returns true if the connected cable is incapable of handling the altmode. ++ */ ++bool typec_cable_altmode_unsupported(struct typec_altmode *alt) ++{ ++ enum typec_cable_altmode_support support = CABLE_SUPPORT_UNKNOWN; ++ struct typec_cable *cable; ++ ++ cable = typec_cable_get(typec_altmode2port(alt)); ++ if (cable) { ++ support = typec_cable_check_altmode_support(cable, alt); ++ typec_cable_put(cable); ++ } ++ ++ return support == CABLE_NOT_SUPPORTED; ++} ++EXPORT_SYMBOL_GPL(typec_cable_altmode_unsupported); ++ + /** + * typec_cable_set_identity - Report result from Discover Identity command + * @cable: The cable updated identity values +diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h +index d61ec38216fa..10a783b738ef 100644 +--- a/include/linux/usb/typec.h ++++ b/include/linux/usb/typec.h +@@ -337,6 +337,7 @@ void typec_unregister_cable(struct typec_cable *cable); + struct typec_cable *typec_cable_get(struct typec_port *port); + void typec_cable_put(struct typec_cable *cable); + int typec_cable_is_active(struct typec_cable *cable); ++bool typec_cable_altmode_unsupported(struct typec_altmode *alt); + + struct typec_plug *typec_register_plug(struct typec_cable *cable, + struct typec_plug_desc *desc); diff --git a/pkgbuilds/linux-omarchy/0620-usb-string-sanitize.patch b/pkgbuilds/linux-omarchy/0620-usb-string-sanitize.patch new file mode 100644 index 0000000..d2ac981 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0620-usb-string-sanitize.patch @@ -0,0 +1,29 @@ +diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c +--- a/drivers/usb/core/message.c ++++ b/drivers/usb/core/message.c +@@ -1052,6 +1052,25 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size) + UTF16_LITTLE_ENDIAN, buf, size); + buf[err] = 0; + ++ /* ++ * Some devices report string descriptors with a declared length ++ * greater than the actual serial, leaving uninitialized firmware ++ * memory (often including C0 control characters) appended to the ++ * returned string. Truncate at the first control character so ++ * callers get a clean, well-formed string. ++ */ ++ { ++ int i; ++ for (i = 0; i < err; i++) { ++ unsigned char c = buf[i]; ++ if (c < 0x20 || c == 0x7f) { ++ buf[i] = 0; ++ err = i; ++ break; ++ } ++ } ++ } ++ + if (tbuf[1] != USB_DT_STRING) + dev_dbg(&dev->dev, + "wrong descriptor type %02x for string %d (\"%s\")\n", diff --git a/pkgbuilds/linux-omarchy/0650-wireguard-tstamp-type.patch b/pkgbuilds/linux-omarchy/0650-wireguard-tstamp-type.patch new file mode 100644 index 0000000..87743c9 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0650-wireguard-tstamp-type.patch @@ -0,0 +1,18 @@ +diff --git a/drivers/net/wireguard/queueing.h b/drivers/net/wireguard/queueing.h +--- a/drivers/net/wireguard/queueing.h ++++ b/drivers/net/wireguard/queueing.h +@@ -78,12 +78,14 @@ static inline void wg_reset_packet(struct sk_buff *skb, bool encapsulating) + u8 l4_hash = skb->l4_hash; + u8 sw_hash = skb->sw_hash; + u32 hash = skb->hash; ++ u8 tstamp_type = skb->tstamp_type; + skb_scrub_packet(skb, true); + memset(&skb->headers, 0, sizeof(skb->headers)); + if (encapsulating) { + skb->l4_hash = l4_hash; + skb->sw_hash = sw_hash; + skb->hash = hash; ++ skb->tstamp_type = tstamp_type; + } + skb->queue_mapping = 0; + skb->nohdr = 0; diff --git a/pkgbuilds/linux-omarchy/0660-btusb-mediatek-mt7922-13d3-3625.patch b/pkgbuilds/linux-omarchy/0660-btusb-mediatek-mt7922-13d3-3625.patch new file mode 100644 index 0000000..8789452 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0660-btusb-mediatek-mt7922-13d3-3625.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -801,6 +801,8 @@ static const struct usb_device_id quirks_table[] = { + BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3613), .driver_info = BTUSB_MEDIATEK | + BTUSB_WIDEBAND_SPEECH }, ++ { USB_DEVICE(0x13d3, 0x3625), .driver_info = BTUSB_MEDIATEK | ++ BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3627), .driver_info = BTUSB_MEDIATEK | + BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3628), .driver_info = BTUSB_MEDIATEK | diff --git a/pkgbuilds/linux-omarchy/0661-rtw89-command-offload-source.patch b/pkgbuilds/linux-omarchy/0661-rtw89-command-offload-source.patch new file mode 100644 index 0000000..539fe9a --- /dev/null +++ b/pkgbuilds/linux-omarchy/0661-rtw89-command-offload-source.patch @@ -0,0 +1,32 @@ +diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c +--- a/drivers/net/wireless/realtek/rtw89/fw.c ++++ b/drivers/net/wireless/realtek/rtw89/fw.c +@@ -11780,7 +11780,7 @@ static void rtw89_fw_cmd_ofld_write_rf(struct rtw89_dev *rtwdev, + static void rtw89_fw_cmd_ofld_udelay(struct rtw89_dev *rtwdev, u32 us) + { + struct rtw89_fw_cmd_ofld_arg cmd = { +- .src = RTW89_FW_CMD_OFLD_SRC_OTHER, ++ .src = RTW89_FW_CMD_OFLD_SRC_BB, + .type = RTW89_FW_CMD_OFLD_DELAY, + .value = us, + }; +@@ -11794,7 +11794,7 @@ static void rtw89_fw_cmd_ofld_udelay(struct rtw89_dev *rtwdev, u32 us) + static void rtw89_fw_cmd_ofld_mdelay(struct rtw89_dev *rtwdev, u32 ms) + { + struct rtw89_fw_cmd_ofld_arg cmd = { +- .src = RTW89_FW_CMD_OFLD_SRC_OTHER, ++ .src = RTW89_FW_CMD_OFLD_SRC_BB, + .type = RTW89_FW_CMD_OFLD_DELAY, + .value = ms * 1000, + }; +diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h +--- a/drivers/net/wireless/realtek/rtw89/fw.h ++++ b/drivers/net/wireless/realtek/rtw89/fw.h +@@ -3142,7 +3142,6 @@ enum rtw89_fw_cmd_ofld_arg_src { + RTW89_FW_CMD_OFLD_SRC_RF, + RTW89_FW_CMD_OFLD_SRC_MAC, + RTW89_FW_CMD_OFLD_SRC_RF_DDIE, +- RTW89_FW_CMD_OFLD_SRC_OTHER, + }; + + enum rtw89_fw_cmd_ofld_arg_type { diff --git a/pkgbuilds/linux-omarchy/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch b/pkgbuilds/linux-omarchy/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch new file mode 100644 index 0000000..d3b514c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch @@ -0,0 +1,27 @@ +diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c ++++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +@@ -516,6 +516,10 @@ iwl_mld_mac80211_tx(struct ieee80211_hw *hw, + u32 link_id = u32_get_bits(info->control.flags, + IEEE80211_TX_CTRL_MLO_LINK); + ++ if (unlikely(test_bit(STATUS_FW_ERROR, &mld->trans->status))) { ++ ieee80211_free_txskb(hw, skb); ++ return; ++ } + /* In AP mode, mgmt frames are sent on the bcast station, + * so the FW can't translate the MLD addr to the link addr. Do it here + */ +diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.c b/drivers/net/wireless/intel/iwlwifi/mld/tx.c +--- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c ++++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.c +@@ -987,6 +987,9 @@ void iwl_mld_tx_from_txq(struct iwl_mld *mld, struct ieee80211_txq *txq) + struct sk_buff *skb = NULL; + u8 zero_addr[ETH_ALEN] = {}; + ++ if (unlikely(test_bit(STATUS_FW_ERROR, &mld->trans->status))) ++ return; ++ + /* + * Don't transmit during firmware restart. The firmware is dead, + * so iwl_trans_tx() would return -EIO for each frame. Avoid the diff --git a/pkgbuilds/linux-omarchy/0700-pci-target-speed-quirk.patch b/pkgbuilds/linux-omarchy/0700-pci-target-speed-quirk.patch new file mode 100644 index 0000000..1102b3f --- /dev/null +++ b/pkgbuilds/linux-omarchy/0700-pci-target-speed-quirk.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -108,7 +108,11 @@ int pcie_failed_link_retrain(struct pci_dev *dev) + + pcie_capability_read_word(dev, PCI_EXP_LNKSTA, &lnksta); + pcie_capability_read_word(dev, PCI_EXP_LNKCTL2, &oldlnkctl2); +- if (!(lnksta & PCI_EXP_LNKSTA_DLLLA) && pcie_lbms_seen(dev, lnksta)) { ++ if (lnksta & PCI_EXP_LNKSTA_DLLLA) { ++ ; ++ } else if (PCIE_LNKCTL2_TLS2SPEED(oldlnkctl2) == PCIE_SPEED_2_5GT) { ++ return ret; ++ } else if (pcie_lbms_seen(dev, lnksta)) { + pci_info(dev, "broken device, retraining non-functional downstream link at 2.5GT/s\n"); + ret = pcie_set_target_speed(dev, PCIE_SPEED_2_5GT, false); + if (ret) diff --git a/pkgbuilds/linux-omarchy/0750-applesmc-cache-race.patch b/pkgbuilds/linux-omarchy/0750-applesmc-cache-race.patch new file mode 100644 index 0000000..bd8dd7c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0750-applesmc-cache-race.patch @@ -0,0 +1,31 @@ +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -33,6 +33,7 @@ + #include + #include + #include ++#include + + /* data port used by Apple SMC */ + #define APPLESMC_DATA_PORT 0x300 +@@ -372,7 +373,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) + __be32 be; + int ret = 0; + +- if (cache->valid) ++ /* Pairs with smp_store_release() to ensure cache contents are visible */ ++ if (smp_load_acquire(&cache->valid)) + return cache; + + mutex_lock(&smcreg.mutex); +@@ -391,7 +393,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) + cache->len = info[0]; + memcpy(cache->type, &info[1], 4); + cache->flags = info[5]; +- cache->valid = true; ++ /* Pairs with smp_load_acquire() to commit cache contents before setting valid */ ++ smp_store_release(&cache->valid, true); + + out: + mutex_unlock(&smcreg.mutex); diff --git a/pkgbuilds/linux-omarchy/0751-applesmc-key-backlight-workqueue-leak.patch b/pkgbuilds/linux-omarchy/0751-applesmc-key-backlight-workqueue-leak.patch new file mode 100644 index 0000000..6eda26b --- /dev/null +++ b/pkgbuilds/linux-omarchy/0751-applesmc-key-backlight-workqueue-leak.patch @@ -0,0 +1,22 @@ +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -1252,12 +1252,17 @@ static void applesmc_release_light_sensor(void) + + static int applesmc_create_key_backlight(void) + { ++ int ret; ++ + if (!smcreg.has_key_backlight) + return 0; + applesmc_led_wq = create_singlethread_workqueue("applesmc-led"); + if (!applesmc_led_wq) + return -ENOMEM; +- return led_classdev_register(&pdev->dev, &applesmc_backlight); ++ ret = led_classdev_register(&pdev->dev, &applesmc_backlight); ++ if (ret) ++ destroy_workqueue(applesmc_led_wq); ++ return ret; + } + + static void applesmc_release_key_backlight(void) diff --git a/pkgbuilds/linux-omarchy/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch b/pkgbuilds/linux-omarchy/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch new file mode 100644 index 0000000..da85b37 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch @@ -0,0 +1,23 @@ +diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c +--- a/drivers/acpi/power.c ++++ b/drivers/acpi/power.c +@@ -1121,6 +1121,19 @@ static const struct dmi_system_id dmi_leave_unused_power_resources_on[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "ZERO"), + } + ++ }, ++ { ++ /* ++ * Acer Swift 3 SF314-56G with NVIDIA MX250 suffers from the ++ * same ACPI power resource regression as the Thunderobot ZERO. ++ * The power resource controlling the dGPU is incorrectly turned off ++ * during initialization, causing the GPU to fall off the bus (D3cold). ++ */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Acer"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Swift SF314-56G"), ++ } ++ + }, + {} + }; diff --git a/pkgbuilds/linux-omarchy/0800-platform-updates.patch b/pkgbuilds/linux-omarchy/0800-platform-updates.patch new file mode 100644 index 0000000..bb6a9de --- /dev/null +++ b/pkgbuilds/linux-omarchy/0800-platform-updates.patch @@ -0,0 +1,8751 @@ +diff --git a/arch/x86/include/uapi/asm/amd_hsmp.h b/arch/x86/include/uapi/asm/amd_hsmp.h +--- a/arch/x86/include/uapi/asm/amd_hsmp.h ++++ b/arch/x86/include/uapi/asm/amd_hsmp.h +@@ -53,9 +53,21 @@ enum hsmp_message_ids { + HSMP_SET_XGMI_PSTATE_RANGE, /* 26h Set xGMI P-state range */ + HSMP_CPU_RAIL_ISO_FREQ_POLICY, /* 27h Get/Set Cpu Iso frequency policy */ + HSMP_DFC_ENABLE_CTRL, /* 28h Enable/Disable DF C-state */ ++ HSMP_PC6_ENABLE, /* 29h Get/Set PC6 enable/disable status */ ++ HSMP_CC6_ENABLE, /* 2Ah Get/Set CC6 enable/disable status */ + HSMP_GET_RAPL_UNITS = 0x30, /* 30h Get scaling factor for energy */ + HSMP_GET_RAPL_CORE_COUNTER, /* 31h Get core energy counter value */ + HSMP_GET_RAPL_PACKAGE_COUNTER, /* 32h Get package energy counter value */ ++ HSMP_DIMM_SB_RD, /* 33h Get DIMM sideband data */ ++ HSMP_READ_CCD_POWER, /* 34h Get average CCD power */ ++ HSMP_READ_TDELTA, /* 35h Get thermal behaviour */ ++ HSMP_GET_SVI3_VR_CTRL_TEMP, /* 36h Get SVI3 VR controller rail temp */ ++ HSMP_GET_ENABLED_HSMP_CMDS, /* 37h Get supported HSMP commands */ ++ HSMP_SET_GET_FLOOR_LIMIT, /* 38h Get/Set core floor frequency limit */ ++ HSMP_DIMM_SB_WR, /* 39h Set DIMM sideband data */ ++ HSMP_SDPS_LIMIT, /* 3Ah Get/Set SDPS limit */ ++ HSMP_PQOS_TRAFFIC_PRIORITY, /* 3Bh Get/Set traffic priority */ ++ HSMP_PQOS_FLOATING_BW, /* 3Ch Get/Set max floating bandwidth */ + HSMP_MSG_ID_MAX, + }; + +@@ -170,16 +182,27 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + {0, 1, HSMP_GET}, + + /* +- * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0 +- * input: args[0] = min link width[15:8] + max link width[7:0] ++ * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0/1 ++ * input: args[0] = set/get XGMI Link width[31] (0 = set, 1 = get) + ++ * min link width[15:8] + max link width[7:0] ++ * Link width encoding: 0 = x4, 1 = x8, 2 = x16. ++ * On SET, max must be >= min. On GET, [15:0] are reserved. ++ * output: args[0] = reserved[31:16] + min link width[15:8] + ++ * max link width[7:0] + */ +- {1, 0, HSMP_SET}, ++ {1, 1, HSMP_SET_GET}, + + /* +- * HSMP_SET_DF_PSTATE, num_args = 1, response_sz = 0 +- * input: args[0] = df pstate[7:0] ++ * HSMP_SET_DF_PSTATE (APBDisable), num_args = 1, response_sz = 0/1 ++ * input: args[0] = set APB_DISABLE / get APB state[31] ++ * (0 = set & lock DF P-state, 1 = get) + ++ * reserved[30:8] + ++ * DF P-state[7:0] (0..2; reserved on GET) ++ * output: args[0] = reserved[31:9] + ++ * APB state[8] (1 = disabled, 0 = enabled) + ++ * locked DF P-state[7:0] if [8] = 1, else reserved + */ +- {1, 0, HSMP_SET}, ++ {1, 1, HSMP_SET_GET}, + + /* HSMP_SET_AUTO_DF_PSTATE, num_args = 0, response_sz = 0 */ + {0, 0, HSMP_SET}, +@@ -305,16 +328,32 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + {1, 1, HSMP_SET}, + + /* +- * HSMP_SET_POWER_MODE, num_args = 1, response_sz = 0 +- * input: args[0] = power efficiency mode[2:0] ++ * HSMP_SET_POWER_MODE (PwrEfficiencyModeSelection), ++ * num_args = 1, response_sz = 1 ++ * input: args[0] = set/get policy[31] (0 = set, 1 = get) + ++ * high util point[30:24] + ++ * low util point[23:17] + ++ * PPT limit[16:5] + ++ * reserved[4:3] + mode selection[2:0] ++ * [30:5] are valid only when [2:0] is a balanced core mode ++ * (4 or 5). [2:0] is reserved when getting (bit[31] = 1). ++ * output: args[0] same layout, [31] reserved, [2:0] = arbitrated ++ * current efficiency mode. + */ + {1, 1, HSMP_SET_GET}, + + /* +- * HSMP_SET_PSTATE_MAX_MIN, num_args = 1, response_sz = 0 +- * input: args[0] = min df pstate[15:8] + max df pstate[7:0] ++ * HSMP_SET_PSTATE_MAX_MIN (DfPstateRange), num_args = 1, response_sz = 0/1 ++ * input: args[0] = set/get DF P-state range[31] (0 = set, 1 = get) + ++ * reserved[30:16] + ++ * min DF P-state[15:8] + max DF P-state[7:0] ++ * DF P-state encoding: 0 = DFP0 (high performance), ++ * 1 = DFP1, 2 = DFP2 (low performance). ++ * [15:0] are reserved when getting (args[0] bit[31] = 1). ++ * output: args[0] = reserved[31:16] + min DF P-state[15:8] + ++ * max DF P-state[7:0] + */ +- {1, 0, HSMP_SET}, ++ {1, 1, HSMP_SET_GET}, + + /* + * HSMP_GET_METRIC_TABLE_VER, num_args = 0, response_sz = 1 +@@ -328,11 +367,12 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + {0, 0, HSMP_GET}, + + /* +- * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 2 ++ * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 3 + * output: args[0] = lower 32 bits of the address + * output: args[1] = upper 32 bits of the address ++ * output: args[2] = DRAM region size in bytes + */ +- {0, 2, HSMP_GET}, ++ {0, 3, HSMP_GET}, + + /* + * HSMP_SET_XGMI_PSTATE_RANGE, num_args = 1, response_sz = 0 +@@ -355,9 +395,31 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + */ + {1, 1, HSMP_SET_GET}, + +- /* RESERVED(0x29-0x2f) */ +- {0, 0, HSMP_RSVD}, +- {0, 0, HSMP_RSVD}, ++ /* ++ * HSMP_PC6_ENABLE (Pc6Enable), num_args = 1, response_sz = 0/1 ++ * input: args[0] = set/get PC6 control[31] (0 = set, 1 = get) + ++ * reserved[30:1] + ++ * enable PC6[0] (0 = disable, 1 = enable; ++ * reserved on GET) ++ * output: args[0] = reserved[31:1] + current PC6 control[0] ++ * (last value configured via HSMP or APML) ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_CC6_ENABLE (CC6Enable), num_args = 1, response_sz = 0/1 ++ * Configures CC6 enable for all cores; changing the setting does ++ * not by itself transition cores in or out of CC6. ++ * input: args[0] = set/get CC6 control[31] (0 = set, 1 = get) + ++ * reserved[30:1] + ++ * enable CC6[0] (0 = disable, 1 = enable; ++ * reserved on GET) ++ * output: args[0] = reserved[31:1] + current CC6 control[0] ++ * (last value configured via HSMP or APML) ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* RESERVED(0x2B-0x2F) */ + {0, 0, HSMP_RSVD}, + {0, 0, HSMP_RSVD}, + {0, 0, HSMP_RSVD}, +@@ -385,6 +447,89 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + */ + {0, 2, HSMP_GET}, + ++ /* ++ * HSMP_DIMM_SB_RD, num_args = 1, response_sz = 1 ++ * input: args[0] = reg space[23] + reg offset[22:12] + ++ * device LID[11:8] + DIMM address[7:0] ++ * output: args[0] = read data byte[3:0] ++ */ ++ {1, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_READ_CCD_POWER, num_args = 1, response_sz = 1 ++ * input: args[0] = apic id of core[15:0] ++ * output: args[0] = CCD power(mWatts)[31:0] ++ */ ++ {1, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_READ_TDELTA, num_args = 0, response_sz = 1 ++ * output: args[0] = thermal behaviour[31:0] ++ */ ++ {0, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_GET_SVI3_VR_CTRL_TEMP, num_args = 1, response_sz = 1 ++ * input: args[0] = SVI3 rail index[3:1] + read temperature[0] ++ * output: args[0] = SVI3 rail index[30:28] + ++ * rail temperature in degree C[27:0] ++ */ ++ {1, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_GET_ENABLED_HSMP_CMDS, num_args = 1, response_sz = 3 ++ * input: args[0] = HSMP command mask[0] ++ * output: status of HSMP command = args[0], args[1], args[2] ++ */ ++ {1, 3, HSMP_GET}, ++ ++ /* ++ * HSMP_SET_GET_FLOOR_LIMIT, num_args = 1, response_sz = 1 ++ * input: args[0] = op[31:30] + reserved[29:28] + ++ * apic id[27:16] + floor frequency MHz[15:0] ++ * op encoding: 00 = set per-core floor, ++ * 01 = set all-cores floor (apic id reserved), ++ * 10 = get per-core floor, ++ * 11 = get per-core effective floor. ++ * Floor frequency field is reserved on GET (bit[31] = 1). ++ * output: args[0] = floor frequency MHz[15:0] ++ * (effective for op 11, configured for op 10; ++ * reserved on SET) ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_DIMM_SB_WR, num_args = 1, response_sz = 0 ++ * input: args[0] = write data[31:24] + reg space[23] + ++ * reg offset[22:12] + device LID[11:8] + ++ * DIMM address[7:0] ++ */ ++ {1, 0, HSMP_SET}, ++ ++ /* ++ * HSMP_SDPS_LIMIT, num_args = 1, response_sz = 1 ++ * input: args[0] = set/get SDPS limit[31] (0 = set, 1 = get) + ++ * SDPS limit[30:0] ++ * output: args[0] = SDPS limit[30:0] ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_PQOS_TRAFFIC_PRIORITY, num_args = 1, response_sz = 1 ++ * input: args[0] = op[31:30] + priority sel[27:26] + ++ * priority val[21:20] + input[19:0] ++ * output: args[0] = supported priorities or priority val[1:0] ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_PQOS_FLOATING_BW, num_args = 1, response_sz = 2 ++ * input: args[0] = op[31] + sub-op[30:29] + params[28:0] ++ * output: args[0] = discovery bits or floating/global memory BW (Gbps) ++ * output: args[1] = reserved or config (drop adj, sampling delay, ++ * hysteresis) ++ */ ++ {1, 2, HSMP_SET_GET}, + }; + + /* Metrics table (supported only with proto version 6) */ +@@ -467,6 +612,39 @@ struct hsmp_metric_table { + __u32 gfxclk_frequency[8]; + }; + ++/** ++ * struct hsmp_telemetry_data - Request descriptor for HSMP telemetry IOCTL ++ * @buf: Input. Userspace pointer (encoded as __u64 to keep the layout ++ * stable between 32-bit and 64-bit callers) to the destination ++ * buffer that receives the metric table. ++ * @size: Input. Size in bytes of the buffer pointed to by @buf, and the ++ * number of bytes copied out on success. Must be non-zero and no ++ * larger than the metric table size firmware reports for this ++ * socket; a larger value is rejected with -EINVAL rather than ++ * short-written. A smaller value returns the leading @size bytes ++ * of the snapshot. The kernel does not write this field back. ++ * @sock_ind: Input. Socket index from which the metric table is read. ++ * @reserved: Reserved for future use. Callers should set this to zero; ++ * future kernels may begin interpreting the field, so passing ++ * a non-zero value today is not forwards compatible. ++ * ++ * Placing @buf first lets all fields fall on their natural alignment under ++ * the surrounding #pragma pack(4), so the struct is a tight 16 bytes with ++ * the same wire layout on 32-bit and 64-bit userspace. ++ * ++ * The metric table layout depends on the HSMP protocol version reported by ++ * firmware, which userspace can read from the protocol_version sysfs ++ * attribute. Protocol version 6 uses struct hsmp_metric_table, so callers on ++ * that version pass sizeof(struct hsmp_metric_table). Later version metrics ++ * table layout is documented in the Public PPR. ++ */ ++struct hsmp_telemetry_data { ++ __u64 buf; ++ __u32 size; ++ __u16 sock_ind; ++ __u16 reserved; ++}; ++ + /* Reset to default packing */ + #pragma pack() + +@@ -474,4 +652,16 @@ struct hsmp_metric_table { + #define HSMP_BASE_IOCTL_NR 0xF8 + #define HSMP_IOCTL_CMD _IOWR(HSMP_BASE_IOCTL_NR, 0, struct hsmp_message) + ++/* ++ * Fetch the firmware metric (telemetry) table for a given socket via the ++ * HSMP character device. This avoids the PAGE_SIZE limitation of the ++ * sysfs binary attribute path for tables larger than one page (such as the ++ * ~13 KB table used by HSMP protocol version 7). ++ * ++ * The direction is _IOW because the kernel only reads the request struct; ++ * the table itself is written to the buffer that @buf points at. ++ */ ++#define HSMP_IOCTL_GET_TELEMETRY_DATA \ ++ _IOW(HSMP_BASE_IOCTL_NR, 1, struct hsmp_telemetry_data) ++ + #endif /*_ASM_X86_AMD_HSMP_H_*/ +diff --git a/arch/x86/platform/olpc/olpc-xo15-sci.c b/arch/x86/platform/olpc/olpc-xo15-sci.c +--- a/arch/x86/platform/olpc/olpc-xo15-sci.c ++++ b/arch/x86/platform/olpc/olpc-xo15-sci.c +@@ -18,8 +18,6 @@ + + #define DRV_NAME "olpc-xo15-sci" + #define PFX DRV_NAME ": " +-#define XO15_SCI_CLASS DRV_NAME +-#define XO15_SCI_DEVICE_NAME "OLPC XO-1.5 SCI" + + static unsigned long xo15_sci_gpe; + static bool lid_wake_on_close; +@@ -148,9 +146,6 @@ static int xo15_sci_probe(struct platform_device *pdev) + if (!device) + return -ENODEV; + +- strscpy(acpi_device_name(device), XO15_SCI_DEVICE_NAME); +- strscpy(acpi_device_class(device), XO15_SCI_CLASS); +- + /* Get GPE bit assignment (EC events). */ + status = acpi_evaluate_integer(device->handle, "_GPE", NULL, &tmp); + if (ACPI_FAILURE(status)) +diff --git a/drivers/char/sonypi.c b/drivers/char/sonypi.c +--- a/drivers/char/sonypi.c ++++ b/drivers/char/sonypi.c +@@ -1125,8 +1125,6 @@ static int sonypi_acpi_probe(struct platform_device *pdev) + return -ENODEV; + + sonypi_acpi_device = device; +- strscpy(acpi_device_name(device), "Sony laptop hotkeys"); +- strscpy(acpi_device_class(device), "sony/hotkey"); + return 0; + } + +diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c +--- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c ++++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c +@@ -7,6 +7,7 @@ + * + * Author: Basavaraj Natikar + */ ++#include + #include + #include + #include +@@ -102,20 +103,20 @@ static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement) + *platform_type = mode.op_mode.devicemode; + + if (mode.op_mode.ontablestate == 1) { +- *laptop_placement = ON_TABLE; ++ *laptop_placement = AMD_PMF_ON_TABLE; + } else if (mode.op_mode.ontablestate == 2) { +- *laptop_placement = ON_LAP_MOTION; ++ *laptop_placement = AMD_PMF_ON_LAP_MOTION; + } else if (mode.op_mode.inbagstate == 1) { +- *laptop_placement = IN_BAG; ++ *laptop_placement = AMD_PMF_IN_BAG; + } else if (mode.op_mode.outbagstate == 1) { +- *laptop_placement = OUT_OF_BAG; ++ *laptop_placement = AMD_PMF_OUT_OF_BAG; + } else if (mode.op_mode.ontablestate == 0 || mode.op_mode.inbagstate == 0 || + mode.op_mode.outbagstate == 0) { +- *laptop_placement = LP_UNKNOWN; ++ *laptop_placement = AMD_PMF_LP_UNKNOWN; + pr_warn_once("Unknown laptop placement\n"); + } else if (mode.op_mode.ontablestate == 3 || mode.op_mode.inbagstate == 3 || + mode.op_mode.outbagstate == 3) { +- *laptop_placement = LP_UNDEFINED; ++ *laptop_placement = AMD_PMF_LP_UNDEFINED; + pr_warn_once("Undefined laptop placement\n"); + } + +diff --git a/drivers/platform/arm64/qcom-hamoa-ec.c b/drivers/platform/arm64/qcom-hamoa-ec.c +--- a/drivers/platform/arm64/qcom-hamoa-ec.c ++++ b/drivers/platform/arm64/qcom-hamoa-ec.c +@@ -92,8 +92,10 @@ static int qcom_ec_read(struct qcom_ec *ec, u8 cmd, u8 resp_len, u8 *resp) + return ret; + else if (ret == 0 || ret == 0xff) + return -EOPNOTSUPP; ++ else if (ret != resp_len) ++ return -EIO; + +- if (resp[0] >= resp_len) ++ if (resp[0] != resp_len - 1) + return -EINVAL; + + return 0; +diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c +--- a/drivers/platform/mellanox/mlxbf-bootctl.c ++++ b/drivers/platform/mellanox/mlxbf-bootctl.c +@@ -10,6 +10,7 @@ + + #include + #include ++#include + #include + #include + #include +diff --git a/drivers/platform/mellanox/mlxbf-pmc.c b/drivers/platform/mellanox/mlxbf-pmc.c +--- a/drivers/platform/mellanox/mlxbf-pmc.c ++++ b/drivers/platform/mellanox/mlxbf-pmc.c +@@ -2262,13 +2262,19 @@ static int mlxbf_pmc_map_counters(struct device *dev) + + static int mlxbf_pmc_probe(struct platform_device *pdev) + { +- struct acpi_device *acpi_dev = ACPI_COMPANION(&pdev->dev); +- const char *hid = acpi_device_hid(acpi_dev); + struct device *dev = &pdev->dev; ++ struct acpi_device *acpi_dev; + struct arm_smccc_res res; ++ const char *hid; + guid_t guid; + int ret; + ++ acpi_dev = ACPI_COMPANION(&pdev->dev); ++ if (!acpi_dev) ++ return -ENODEV; ++ ++ hid = acpi_device_hid(acpi_dev); ++ + /* Ensure we have the UUID we expect for this service. */ + arm_smccc_smc(MLXBF_PMC_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res); + guid_parse(mlxbf_pmc_svc_uuid_str, &guid); +diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c +--- a/drivers/platform/surface/surface_acpi_notify.c ++++ b/drivers/platform/surface/surface_acpi_notify.c +@@ -777,12 +777,16 @@ static int san_consumer_links_setup(struct platform_device *pdev) + + static int san_probe(struct platform_device *pdev) + { +- struct acpi_device *san = ACPI_COMPANION(&pdev->dev); + struct ssam_controller *ctrl; ++ struct acpi_device *san; + struct san_data *data; + acpi_status astatus; + int status; + ++ san = ACPI_COMPANION(&pdev->dev); ++ if (!san) ++ return -ENODEV; ++ + ctrl = ssam_client_bind(&pdev->dev); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); +diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c +--- a/drivers/platform/surface/surface_aggregator_hub.c ++++ b/drivers/platform/surface/surface_aggregator_hub.c +@@ -348,8 +348,13 @@ static const struct ssam_hub_desc kip_hub = { + /* -- Driver registration. -------------------------------------------------- */ + + static const struct ssam_device_id ssam_hub_match[] = { +- { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, +- { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, ++ { ++ SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), ++ .driver_data = (unsigned long)&kip_hub, ++ }, { ++ SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), ++ .driver_data = (unsigned long)&base_hub, ++ }, + { } + }; + MODULE_DEVICE_TABLE(ssam, ssam_hub_match); +diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c +--- a/drivers/platform/surface/surface_aggregator_tabletsw.c ++++ b/drivers/platform/surface/surface_aggregator_tabletsw.c +@@ -622,8 +622,13 @@ static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { + /* -- Driver registration. -------------------------------------------------- */ + + static const struct ssam_device_id ssam_tablet_sw_match[] = { +- { SSAM_SDEV(KIP, SAM, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, +- { SSAM_SDEV(POS, SAM, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, ++ { ++ SSAM_SDEV(KIP, SAM, 0x00, 0x01), ++ .driver_data = (unsigned long)&ssam_kip_sw_desc, ++ }, { ++ SSAM_SDEV(POS, SAM, 0x00, 0x01), ++ .driver_data = (unsigned long)&ssam_pos_sw_desc, ++ }, + { }, + }; + MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -20,7 +20,6 @@ + #define SURFACE_PRO3_BUTTON_HID "MSHW0028" + #define SURFACE_PRO4_BUTTON_HID "MSHW0040" + #define SURFACE_BUTTON_OBJ_NAME "VGBI" +-#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons" + + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -212,11 +211,10 @@ static int surface_button_probe(struct platform_device *pdev) + goto err_free_button; + } + +- strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME); + snprintf(button->phys, sizeof(button->phys), "%s/buttons", + acpi_device_hid(device)); + +- input->name = acpi_device_name(device); ++ input->name = "Surface Pro 3/4 Buttons"; + input->phys = button->phys; + input->id.bustype = BUS_HOST; + input->dev.parent = &pdev->dev; +@@ -239,8 +237,8 @@ static int surface_button_probe(struct platform_device *pdev) + goto err_free_button; + } + +- dev_info(&pdev->dev, "%s [%s]\n", acpi_device_name(device), +- acpi_device_bid(device)); ++ dev_info(&pdev->dev, "%s [%s]\n", input->name, acpi_device_bid(device)); ++ + return 0; + + err_free_input: +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -802,7 +802,6 @@ config LG_LAPTOP + tristate "LG Laptop Extras" + depends on ACPI + depends on ACPI_BATTERY +- depends on ACPI_WMI + depends on INPUT + select INPUT_SPARSEKMAP + select NEW_LEDS +diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c +--- a/drivers/platform/x86/acer-wmi.c ++++ b/drivers/platform/x86/acer-wmi.c +@@ -1581,7 +1581,9 @@ static int WMI_gaming_execute_u32_u64(u32 method_id, u32 in, u64 *out) + return -EIO; + + obj = result.pointer; +- if (obj && out) { ++ if (!obj && out) { ++ ret = -ENOMSG; ++ } else if (obj && out) { + switch (obj->type) { + case ACPI_TYPE_INTEGER: + *out = obj->integer.value; +diff --git a/drivers/platform/x86/amd/Kconfig b/drivers/platform/x86/amd/Kconfig +--- a/drivers/platform/x86/amd/Kconfig ++++ b/drivers/platform/x86/amd/Kconfig +@@ -34,6 +34,17 @@ config AMD_WBRF + This mechanism will only be activated on platforms that advertise a + need for it. + ++config AMD_HALO_LED ++ tristate "AMD Halo Box RGB LED Driver" ++ depends on ACPI_WMI && LEDS_CLASS_MULTICOLOR ++ help ++ This driver provides RGB LED control for AMD Halo Box devices ++ through the LED multicolor subsystem. The Halo Box light bar can ++ be controlled via sysfs to display any RGB color combination. ++ ++ To compile this driver as a module, choose M here: the module ++ will be called amd_halo_led. ++ + config AMD_ISP_PLATFORM + tristate "AMD ISP4 platform driver" + depends on I2C && X86_64 && ACPI +diff --git a/drivers/platform/x86/amd/Makefile b/drivers/platform/x86/amd/Makefile +--- a/drivers/platform/x86/amd/Makefile ++++ b/drivers/platform/x86/amd/Makefile +@@ -10,5 +10,6 @@ obj-$(CONFIG_AMD_PMC) += pmc/ + obj-$(CONFIG_AMD_HSMP) += hsmp/ + obj-$(CONFIG_AMD_PMF) += pmf/ + obj-$(CONFIG_AMD_WBRF) += wbrf.o ++obj-$(CONFIG_AMD_HALO_LED) += amd_halo_led.o + obj-$(CONFIG_AMD_ISP_PLATFORM) += amd_isp4.o + obj-$(CONFIG_AMD_HFI) += hfi/ +diff --git a/drivers/platform/x86/amd/amd_halo_led.c b/drivers/platform/x86/amd/amd_halo_led.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/amd/amd_halo_led.c +@@ -0,0 +1,315 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * AMD Halo Box RGB LED Driver ++ * ++ * Copyright (C) 2026 Advanced Micro Devices, Inc. ++ * ++ * This driver provides RGB LED control for AMD Halo Box devices through ++ * the LED multicolor subsystem. The Halo Box light bar can be controlled ++ * via sysfs to display any RGB color combination. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#define AMD_HALO_GUID "081E747B-E028-4232-AF24-EAAEAB2B1E86" ++ ++/* WMI method IDs from MOF */ ++enum { ++ AMD_HALO_WMI_TURN_OFF = 0x04, ++ AMD_HALO_WMI_RGB = 0x07, ++}; ++ ++/* Arg0 of the AMD_HALO_WMI_RGB Method */ ++enum { ++ AMD_HALO_RGB_CMD_GET = 0x0, ++ AMD_HALO_RGB_CMD_SET = 0x1, ++}; ++ ++/* Status codes from spec */ ++#define AMD_HALO_STATUS_SUCCESS 0x0000 ++#define AMD_HALO_STATUS_INVALID_PARAM 0xFFFD ++ ++/* Brightness uses 0-100 range */ ++#define AMD_HALO_MAX_HW_BRIGHTNESS 100 ++ ++/** ++ * struct amd_halo_led_data - Driver private data ++ * @wdev: WMI device pointer ++ * @led_mc: LED multicolor class device ++ * @subled_info: RGB channel information ++ * @lock: Mutex to protect WMI calls ++ */ ++struct amd_halo_led_data { ++ struct wmi_device *wdev; ++ struct led_classdev_mc led_mc; ++ struct mc_subled subled_info[3]; ++ struct mutex lock; ++}; ++ ++struct amd_halo_wmi_args { ++ __le32 arg0; ++ __le32 arg1; ++}; ++ ++struct amd_halo_wmi_args_rgb { ++ __le32 cmd; ++ __le32 red; ++ __le32 green; ++ __le32 blue; ++}; ++ ++struct amd_halo_wmi_output_rgb { ++ __le16 status; ++ u8 red; ++ u8 green; ++ u8 blue; ++} __packed; ++ ++static inline int wmi_status_to_err(u16 status) ++{ ++ switch (status) { ++ case AMD_HALO_STATUS_SUCCESS: ++ return 0; ++ case AMD_HALO_STATUS_INVALID_PARAM: ++ return -EINVAL; ++ default: ++ return -EIO; ++ } ++} ++ ++static int __amd_halo_wmi_call(struct wmi_device *wdev, ++ u32 method_id, void *data, size_t length) ++{ ++ struct wmi_buffer input = { ++ .length = length, ++ .data = data, ++ }; ++ struct wmi_buffer output = { }; ++ int ret; ++ ++ /* Return buffer per spec: Bytes[0:1] = Status (little-endian) */ ++ ret = wmidev_invoke_method(wdev, 0, method_id, ++ &input, &output, sizeof(__le16)); ++ if (ret) ++ return ret; ++ ++ __le16 *result_status __free(kfree) = output.data; ++ ++ return wmi_status_to_err(le16_to_cpu(*result_status)); ++} ++ ++/** ++ * amd_halo_wmi_turn_off - Turn off all LED channels ++ * @wdev: WMI device pointer ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_wmi_turn_off(struct wmi_device *wdev) ++{ ++ struct amd_halo_wmi_args args = { }; ++ ++ return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_TURN_OFF, &args, sizeof(args)); ++} ++ ++/** ++ * amd_halo_wmi_set_rgb - Set all RGB channels atomically ++ * @wdev: WMI device pointer ++ * @r: brightness for red channel (0 - 100) ++ * @g: brightness for green channel (0 - 100) ++ * @b: brightness for blue channel (0 - 100) ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_wmi_set_rgb(struct wmi_device *wdev, u32 r, u32 g, u32 b) ++{ ++ struct amd_halo_wmi_args_rgb args = { ++ .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_SET), ++ .red = cpu_to_le32(r), ++ .green = cpu_to_le32(g), ++ .blue = cpu_to_le32(b), ++ }; ++ ++ if (r > AMD_HALO_MAX_HW_BRIGHTNESS || ++ g > AMD_HALO_MAX_HW_BRIGHTNESS || ++ b > AMD_HALO_MAX_HW_BRIGHTNESS) { ++ return -EINVAL; ++ } ++ ++ return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_RGB, &args, sizeof(args)); ++} ++ ++/** ++ * amd_halo_wmi_get_rgb - Get RGB values ++ * @wdev: WMI device pointer ++ * @r: output buffer for red value ++ * @g: output buffer for green value ++ * @b: output buffer for blue value ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_wmi_get_rgb(struct wmi_device *wdev, u8 *r, u8 *g, u8 *b) ++{ ++ struct amd_halo_wmi_args_rgb args = { ++ .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_GET), ++ }; ++ struct wmi_buffer input = { ++ .length = sizeof(args), ++ .data = &args, ++ }; ++ struct wmi_buffer output = { }; ++ int ret; ++ ++ ret = wmidev_invoke_method(wdev, 0, AMD_HALO_WMI_RGB, ++ &input, &output, sizeof(struct amd_halo_wmi_output_rgb)); ++ if (ret) ++ return ret; ++ ++ struct amd_halo_wmi_output_rgb *data __free(kfree) = output.data; ++ ++ ret = wmi_status_to_err(le16_to_cpu(data->status)); ++ if (ret) ++ return ret; ++ ++ if (data->red > AMD_HALO_MAX_HW_BRIGHTNESS || ++ data->green > AMD_HALO_MAX_HW_BRIGHTNESS || ++ data->blue > AMD_HALO_MAX_HW_BRIGHTNESS) { ++ return -EPROTO; ++ } ++ ++ *r = data->red; ++ *g = data->green; ++ *b = data->blue; ++ ++ return 0; ++} ++ ++/** ++ * amd_halo_brightness_set - Set LED brightness ++ * @cdev: LED class device ++ * @brightness: Brightness value ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_brightness_set(struct led_classdev *cdev, ++ enum led_brightness brightness) ++{ ++ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); ++ struct amd_halo_led_data *data = container_of(mc_cdev, ++ struct amd_halo_led_data, ++ led_mc); ++ u32 red_hw, green_hw, blue_hw; ++ int ret; ++ ++ guard(mutex)(&data->lock); ++ ++ led_mc_calc_color_components(mc_cdev, brightness); ++ ++ if (brightness == 0) ++ return amd_halo_wmi_turn_off(data->wdev); ++ ++ red_hw = mc_cdev->subled_info[0].brightness; ++ green_hw = mc_cdev->subled_info[1].brightness; ++ blue_hw = mc_cdev->subled_info[2].brightness; ++ ++ ret = amd_halo_wmi_set_rgb(data->wdev, red_hw, green_hw, blue_hw); ++ if (ret) ++ goto out; ++ ++ return 0; ++ ++out: ++ /* ++ * Consider the light bar non-functional if AMD_HALO_WMI_RGB failed. ++ * Attempt to turn the LED off completely as clean-up. ++ */ ++ if (amd_halo_wmi_turn_off(data->wdev)) ++ dev_warn_ratelimited(&data->wdev->dev, "Failed to turn LED off on cleanup\n"); ++ ++ return ret; ++} ++ ++static int amd_halo_probe(struct wmi_device *wdev, const void *context) ++{ ++ struct led_init_data led_init_data = { ++ .devicename = "amd_halo", ++ .default_label = "multicolor:" LED_FUNCTION_STATUS, ++ .devname_mandatory = true, ++ }; ++ struct amd_halo_led_data *data; ++ u8 r, g, b; ++ int ret; ++ ++ data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ ret = devm_mutex_init(&wdev->dev, &data->lock); ++ if (ret) ++ return ret; ++ ++ data->wdev = wdev; ++ dev_set_drvdata(&wdev->dev, data); ++ ++ data->subled_info[0].color_index = LED_COLOR_ID_RED; ++ data->subled_info[1].color_index = LED_COLOR_ID_GREEN; ++ data->subled_info[2].color_index = LED_COLOR_ID_BLUE; ++ ++ data->led_mc.led_cdev.brightness = AMD_HALO_MAX_HW_BRIGHTNESS; ++ data->led_mc.led_cdev.max_brightness = AMD_HALO_MAX_HW_BRIGHTNESS; ++ data->led_mc.led_cdev.brightness_set_blocking = amd_halo_brightness_set; ++ data->led_mc.led_cdev.flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN; ++ data->led_mc.num_colors = ARRAY_SIZE(data->subled_info); ++ data->led_mc.subled_info = data->subled_info; ++ ++ ret = amd_halo_wmi_get_rgb(wdev, &r, &g, &b); ++ if (ret) ++ return ret; ++ ++ data->subled_info[0].intensity = r; ++ data->subled_info[1].intensity = g; ++ data->subled_info[2].intensity = b; ++ ++ ret = devm_led_classdev_multicolor_register_ext(&wdev->dev, &data->led_mc, ++ &led_init_data); ++ if (ret) ++ return dev_err_probe(&wdev->dev, ret, ++ "Failed to register multicolor LED\n"); ++ return 0; ++} ++ ++static const struct wmi_device_id amd_halo_id_table[] = { ++ { .guid_string = AMD_HALO_GUID }, ++ { } ++}; ++MODULE_DEVICE_TABLE(wmi, amd_halo_id_table); ++ ++static struct wmi_driver amd_halo_driver = { ++ .driver = { ++ .name = "amd_halo_led", ++ }, ++ .id_table = amd_halo_id_table, ++ .probe = amd_halo_probe, ++ .no_singleton = true, ++}; ++ ++module_wmi_driver(amd_halo_driver); ++ ++MODULE_AUTHOR("Mario Limonciello (AMD) "); ++MODULE_AUTHOR("Yo-Jung Leo Lin (AMD) "); ++MODULE_DESCRIPTION("AMD Halo Box RGB LED Control Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c +--- a/drivers/platform/x86/amd/hsmp/acpi.c ++++ b/drivers/platform/x86/amd/hsmp/acpi.c +@@ -15,12 +15,18 @@ + #include + #include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include + #include ++#include ++#include ++#include + #include + #include + #include +@@ -38,6 +44,17 @@ + + static struct hsmp_plat_device *hsmp_pdev; + ++/* ++ * Tracks the ACPI socket platform devices that share the socket array and the ++ * /dev/hsmp misc device. The first probe initializes it, each further probe ++ * takes a reference and every remove (or probe failure) drops one; the last ++ * put frees the shared state via hsmp_acpi_sock_release(). All get/put run ++ * under hsmp_sock_rwsem held for write, so the counting is already serialized ++ * and the atomic in kref is not strictly needed; kref is used for the clearer ++ * get/put interface and its release callback. ++ */ ++static struct kref hsmp_acpi_sock_kref; ++ + struct hsmp_sys_attr { + struct device_attribute dattr; + u32 msg_id; +@@ -77,6 +94,8 @@ static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind) + * bytes to integer. + */ + uid = acpi_device_uid(ACPI_COMPANION(dev)); ++ if (!uid || strlen(uid) < 3) ++ return -EINVAL; + + return kstrtou16(uid + 2, 10, sock_ind); + } +@@ -104,7 +123,7 @@ static acpi_status hsmp_resource(struct acpi_resource *res, void *data) + return AE_OK; + } + +-static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) ++static int hsmp_read_acpi_dsd(struct device *dev, struct hsmp_socket *sock) + { + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *guid, *mailbox_package; +@@ -113,10 +132,10 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + int ret = 0; + int j; + +- status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL, ++ status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL, + &buf, ACPI_TYPE_PACKAGE); + if (ACPI_FAILURE(status)) { +- dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", ++ dev_err(dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", + acpi_format_exception(status)); + return -ENODEV; + } +@@ -139,7 +158,7 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + guid = &dsd->package.elements[0]; + mailbox_package = &dsd->package.elements[1]; + if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) { +- dev_err(sock->dev, "Invalid hsmp _DSD table data\n"); ++ dev_err(dev, "Invalid hsmp _DSD table data\n"); + ret = -EINVAL; + goto free_buf; + } +@@ -148,12 +167,18 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + union acpi_object *msgobj, *msgstr, *msgint; + + msgobj = &mailbox_package->package.elements[j]; +- msgstr = &msgobj->package.elements[0]; +- msgint = &msgobj->package.elements[1]; + + /* package should have 1 string and 1 integer object */ + if (msgobj->type != ACPI_TYPE_PACKAGE || +- msgstr->type != ACPI_TYPE_STRING || ++ msgobj->package.count < 2) { ++ ret = -EINVAL; ++ goto free_buf; ++ } ++ ++ msgstr = &msgobj->package.elements[0]; ++ msgint = &msgobj->package.elements[1]; ++ ++ if (msgstr->type != ACPI_TYPE_STRING || + msgint->type != ACPI_TYPE_INTEGER) { + ret = -EINVAL; + goto free_buf; +@@ -183,14 +208,14 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + return ret; + } + +-static int hsmp_read_acpi_crs(struct hsmp_socket *sock) ++static int hsmp_read_acpi_crs(struct device *dev, struct hsmp_socket *sock) + { + acpi_status status; + +- status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS, ++ status = acpi_walk_resources(ACPI_HANDLE(dev), METHOD_NAME__CRS, + hsmp_resource, sock); + if (ACPI_FAILURE(status)) { +- dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n", ++ dev_err(dev, "Failed to look up MP1 base address from CRS method, err: %s\n", + acpi_format_exception(status)); + return -EINVAL; + } +@@ -198,10 +223,10 @@ static int hsmp_read_acpi_crs(struct hsmp_socket *sock) + return -EINVAL; + + /* The mapped region should be un-cached */ +- sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr, ++ sock->virt_base_addr = devm_ioremap_uc(dev, sock->mbinfo.base_addr, + sock->mbinfo.size); + if (!sock->virt_base_addr) { +- dev_err(sock->dev, "Failed to ioremap MP1 base address\n"); ++ dev_err(dev, "Failed to ioremap MP1 base address\n"); + return -ENOMEM; + } + +@@ -215,7 +240,6 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) + int ret; + + sock->sock_ind = sock_ind; +- sock->dev = dev; + sock->amd_hsmp_rdwr = amd_hsmp_acpi_rdwr; + + sema_init(&sock->hsmp_sem, 1); +@@ -223,12 +247,27 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) + dev_set_drvdata(dev, sock); + + /* Read MP1 base address from CRS method */ +- ret = hsmp_read_acpi_crs(sock); ++ ret = hsmp_read_acpi_crs(dev, sock); + if (ret) + return ret; + + /* Read mailbox offsets from DSD table */ +- return hsmp_read_acpi_dsd(sock); ++ ret = hsmp_read_acpi_dsd(dev, sock); ++ if (ret) ++ return ret; ++ ++ /* ++ * Publish sock->dev last. hsmp_send_message() uses it (via ++ * smp_load_acquire()) as the readiness gate for the lock-free data ++ * plane, so it must become visible only after virt_base_addr, the ++ * mailbox offsets and the semaphore are fully initialized. On a ++ * multi-socket system socket 0 exposes /dev/hsmp before later sockets ++ * finish probing, so without this an ioctl aimed at a socket still in ++ * bring-up could pass the gate and dereference a NULL virt_base_addr. ++ */ ++ smp_store_release(&sock->dev, dev); ++ ++ return 0; + } + + static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj, +@@ -238,13 +277,31 @@ static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj + struct device *dev = container_of(kobj, struct device, kobj); + struct hsmp_socket *sock = dev_get_drvdata(dev); + ++ /* ++ * metrics_bin is a sysfs binary attribute and is capped at PAGE_SIZE. ++ * It can therefore only carry the protocol version 6 metric table ++ * (struct hsmp_metric_table). The larger tables defined from protocol ++ * version 7 onwards do not fit; userspace on those systems must read ++ * the snapshot through HSMP_IOCTL_GET_TELEMETRY_DATA on /dev/hsmp. ++ * Surface the unsupported case here as -EOPNOTSUPP rather than ++ * silently truncating the snapshot. ++ */ ++ if (hsmp_pdev->proto_ver != HSMP_PROTO_VER6) ++ return -EOPNOTSUPP; ++ + return hsmp_metric_tbl_read(sock, buf, count); + } + + static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj, + const struct bin_attribute *battr, int id) + { +- if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) ++ /* ++ * Keep metrics_bin visible on protocol version 7 and later as well, ++ * so that userspace which expects the file to exist gets a clear ++ * -EOPNOTSUPP from the read handler instead of -ENOENT, and is ++ * pointed at HSMP_IOCTL_GET_TELEMETRY_DATA as the supported path. ++ */ ++ if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) + return battr->attr.mode; + + return 0; +@@ -459,11 +516,20 @@ static ssize_t hsmp_freq_limit_source_show(struct device *dev, struct device_att + return len; + } + ++/* ++ * Bring up one ACPI HSMP socket: parse its ACPI table, run the mailbox ++ * handshake and register its sysfs/hwmon interfaces. ++ * ++ * Called with hsmp_sock_rwsem held for write by hsmp_acpi_probe(), so the ++ * per-socket bring-up cannot race a concurrent probe or remove. ++ */ + static int init_acpi(struct device *dev) + { + u16 sock_ind; + int ret; + ++ lockdep_assert_held_write(&hsmp_sock_rwsem); ++ + ret = hsmp_get_uid(dev, &sock_ind); + if (ret) + return ret; +@@ -491,7 +557,7 @@ static int init_acpi(struct device *dev) + return ret; + } + +- if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) { ++ if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) { + ret = hsmp_get_tbl_dram_base(sock_ind); + if (ret) + dev_info(dev, "Failed to init metric table\n"); +@@ -576,6 +642,60 @@ static const struct acpi_device_id amd_hsmp_acpi_ids[] = { + }; + MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids); + ++/* ++ * kref release: tear down the shared ACPI socket state once the last socket ++ * drops its reference. Deregister /dev/hsmp if it was registered, unmap any ++ * metric-table DRAM, destroy the per-socket mutexes and free the socket array. ++ * ++ * Runs from kref_put() with hsmp_sock_rwsem held for write, since the remove ++ * and probe-failure paths both drop their reference under that lock. The write ++ * lock has drained any in-flight hsmp_send_message(), so unmapping the mailbox ++ * and freeing the array cannot race the data plane. ++ */ ++static void hsmp_acpi_sock_release(struct kref *kref) ++{ ++ lockdep_assert_held_write(&hsmp_sock_rwsem); ++ ++ if (!IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) ++ hsmp_misc_deregister(); ++ hsmp_unmap_metric_tbls(hsmp_pdev); ++ hsmp_destroy_metric_read_locks(hsmp_pdev); ++ kfree(hsmp_pdev->sock); ++ hsmp_pdev->sock = NULL; ++ hsmp_pdev->num_sockets = 0; ++ hsmp_pdev->proto_ver = 0; ++} ++ ++/** ++ * hsmp_acpi_probe_failure_cleanup() - Undo a failed ACPI socket probe. ++ * @dev: ACPI companion device whose probe failed. ++ * ++ * This device already took a reference on entry to hsmp_acpi_probe(), so clear ++ * its sock->dev and drop that reference; the shared state is released if it was ++ * the last one. ++ * ++ * Clearing sock->dev matters on multi-socket systems: when a non-first socket ++ * fails, the array stays alive (owned by an already-probed socket) and ++ * remove() is never called for this device, yet devres unmaps its mailbox once ++ * probe() returns. Without clearing dev, a later message to this index would ++ * pass every gate in hsmp_send_message() and reach the unmapped mailbox. ++ * ++ * sock is NULL if probe failed before hsmp_parse_acpi_table() set the drvdata. ++ * ++ * Called from hsmp_acpi_probe(), which already holds hsmp_sock_rwsem for write. ++ */ ++static void hsmp_acpi_probe_failure_cleanup(struct device *dev) ++{ ++ struct hsmp_socket *sock = dev_get_drvdata(dev); ++ ++ lockdep_assert_held_write(&hsmp_sock_rwsem); ++ ++ if (sock) ++ sock->dev = NULL; ++ ++ kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); ++} ++ + static int hsmp_acpi_probe(struct platform_device *pdev) + { + int ret; +@@ -584,34 +704,61 @@ static int hsmp_acpi_probe(struct platform_device *pdev) + if (!hsmp_pdev) + return -ENOMEM; + +- if (!hsmp_pdev->is_probed) { ++ /* ++ * Multiple ACPI socket devices probe in parallel, but the one-time ++ * socket-array allocation and /dev/hsmp registration below must run ++ * exactly once. Hold the socket rwsem for write across the whole ++ * bring-up so it cannot race a concurrent probe or remove, and so the ++ * probe-failure teardown drains the data plane. ++ */ ++ guard(rwsem_write)(&hsmp_sock_rwsem); ++ ++ if (!hsmp_pdev->sock) { + hsmp_pdev->num_sockets = topology_max_packages(); + if (!hsmp_pdev->num_sockets) { + dev_err(&pdev->dev, "No CPU sockets detected\n"); + return -ENODEV; + } + +- hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets, +- sizeof(*hsmp_pdev->sock), +- GFP_KERNEL); ++ hsmp_pdev->sock = kcalloc(hsmp_pdev->num_sockets, ++ sizeof(*hsmp_pdev->sock), ++ GFP_KERNEL); + if (!hsmp_pdev->sock) + return -ENOMEM; ++ ++ hsmp_init_metric_read_locks(hsmp_pdev); ++ kref_init(&hsmp_acpi_sock_kref); ++ } else { ++ kref_get(&hsmp_acpi_sock_kref); + } + ++ /* ++ * This socket now holds a reference (kref_init on the first socket, ++ * kref_get afterwards). Every failure path below drops it via ++ * hsmp_acpi_probe_failure_cleanup(), and a successful probe hands it to ++ * hsmp_acpi_remove(). ++ */ + ret = init_acpi(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize HSMP interface.\n"); ++ hsmp_acpi_probe_failure_cleanup(&pdev->dev); + return ret; + } + +- if (!hsmp_pdev->is_probed) { +- ret = hsmp_misc_register(&pdev->dev); ++ if (IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) { ++ /* ++ * Register /dev/hsmp unparented. It is a singleton shared by all ++ * ACPI sockets and outlives all but the last of them, so ++ * parenting it to this socket's device would leave a dangling ++ * parent once that socket is unbound. ++ */ ++ ret = hsmp_misc_register(NULL); + if (ret) { + dev_err(&pdev->dev, "Failed to register misc device\n"); ++ hsmp_acpi_probe_failure_cleanup(&pdev->dev); + return ret; + } +- hsmp_pdev->is_probed = true; +- dev_dbg(&pdev->dev, "AMD HSMP ACPI is probed successfully\n"); ++ dev_dbg(&pdev->dev, "AMD HSMP ACPI misc device registered\n"); + } + + return 0; +@@ -619,14 +766,26 @@ static int hsmp_acpi_probe(struct platform_device *pdev) + + static void hsmp_acpi_remove(struct platform_device *pdev) + { ++ struct hsmp_socket *sock = dev_get_drvdata(&pdev->dev); ++ + /* +- * We register only one misc_device even on multi-socket system. +- * So, deregister should happen only once. ++ * Serialize the kref_put() and any release it triggers against a ++ * concurrent probe, and drain the data plane for the whole ++ * teardown: this covers the per-socket unbind, whose mailbox devres ++ * unmaps once we return, and the last unbind that frees the socket ++ * array in hsmp_acpi_sock_release(). + */ +- if (hsmp_pdev->is_probed) { +- hsmp_misc_deregister(); +- hsmp_pdev->is_probed = false; +- } ++ guard(rwsem_write)(&hsmp_sock_rwsem); ++ ++ /* ++ * Clear this socket's dev so hsmp_send_message() rejects it before ++ * devres unmaps the mailbox. On a non-final unbind the socket array ++ * stays alive, so without this a later message to this index would ++ * reach an unmapped iomem region. ++ */ ++ sock->dev = NULL; ++ ++ kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); + } + + static struct platform_driver amd_hsmp_driver = { +diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c +--- a/drivers/platform/x86/amd/hsmp/hsmp.c ++++ b/drivers/platform/x86/amd/hsmp/hsmp.c +@@ -10,10 +10,17 @@ + #include + + #include ++#include + #include + #include ++#include ++#include ++#include ++#include + #include ++#include + #include ++#include + + #include "hsmp.h" + +@@ -40,6 +47,17 @@ + + static struct hsmp_plat_device hsmp_pdev; + ++/* ++ * Gates the AMD HSMP data plane against socket bring-up and teardown. ++ * ++ * hsmp_send_message() takes it for read, so open /dev/hsmp fds and hwmon reads ++ * run concurrently. Probe and remove take it for write: probe brings sockets ++ * up (running the mailbox handshake via hsmp_send_message_locked()) and remove ++ * tears them down, both excluding and draining the data plane. ++ */ ++DECLARE_RWSEM(hsmp_sock_rwsem); ++EXPORT_SYMBOL_NS_GPL(hsmp_sock_rwsem, "AMD_HSMP"); ++ + /* + * Send a message to the HSMP port via PCI-e config space registers + * or by writing to MMIO space. +@@ -182,29 +200,33 @@ static int validate_message(struct hsmp_message *msg) + return -EINVAL; + + /* +- * Some older HSMP SET messages are updated to add GET in the same message. +- * In these messages, GET returns the current value and SET also returns +- * the successfully set value. To support this GET and SET in same message +- * while maintaining backward compatibility for the HSMP users, +- * hsmp_msg_desc_table[] indicates only maximum allowed response_sz. ++ * As the HSMP protocol evolves, newer platforms may define more ++ * response arguments for existing messages. Use an upper-bound ++ * check so that older userspace callers requesting fewer response ++ * words than what the current hsmp_msg_desc_table[] defines are ++ * still accepted, while rejecting requests that exceed the ++ * hardware capability. + */ +- if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET) { +- if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) +- return -EINVAL; +- } else { +- /* only HSMP_SET or HSMP_GET messages go through this strict check */ +- if (msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz) +- return -EINVAL; +- } ++ if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) ++ return -EINVAL; ++ + return 0; + } + +-int hsmp_send_message(struct hsmp_message *msg) ++/* ++ * Core message send. The caller must hold hsmp_sock_rwsem: the data plane ++ * takes it for read so many messages run concurrently, while the probe-time ++ * senders run under the write lock taken by probe. Holding it here serializes ++ * every message against socket teardown, which also holds it for write. ++ */ ++static int hsmp_send_message_locked(struct hsmp_message *msg) + { + struct hsmp_socket *sock; + unsigned int sock_ind; + int ret; + ++ lockdep_assert_held(&hsmp_sock_rwsem); ++ + if (!msg) + return -EINVAL; + ret = validate_message(msg); +@@ -223,6 +245,20 @@ int hsmp_send_message(struct hsmp_message *msg) + sock_ind = array_index_nospec(msg->sock_ind, hsmp_pdev.num_sockets); + sock = &hsmp_pdev.sock[sock_ind]; + ++ /* ++ * A slot exists for every possible socket, but it is only usable once ++ * that socket has actually been probed. Reject messages aimed at a ++ * socket that was never brought up or is still in bring-up, so we never ++ * operate on a zero-initialized semaphore or an unmapped mailbox. A ++ * non-NULL dev also guarantees virt_base_addr, the mailbox offsets and ++ * the semaphore are visible. ++ * ++ * Held under hsmp_sock_rwsem; pairs with smp_store_release(&sock->dev) ++ * in hsmp_parse_acpi_table(). ++ */ ++ if (!smp_load_acquire(&sock->dev)) ++ return -ENODEV; ++ + ret = down_interruptible(&sock->hsmp_sem); + if (ret < 0) + return ret; +@@ -233,6 +269,19 @@ int hsmp_send_message(struct hsmp_message *msg) + + return ret; + } ++ ++int hsmp_send_message(struct hsmp_message *msg) ++{ ++ /* ++ * Data-plane entry point: open /dev/hsmp fds and hwmon sysfs reads issue ++ * messages from here. Take hsmp_sock_rwsem for read so messages run ++ * concurrently with each other but are drained and kept out while ++ * probe/remove hold it for write to tear a socket down. ++ */ ++ guard(rwsem_read)(&hsmp_sock_rwsem); ++ ++ return hsmp_send_message_locked(msg); ++} + EXPORT_SYMBOL_NS_GPL(hsmp_send_message, "AMD_HSMP"); + + int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args) +@@ -273,7 +322,7 @@ int hsmp_test(u16 sock_ind, u32 value) + msg.args[0] = value; + msg.sock_ind = sock_ind; + +- ret = hsmp_send_message(&msg); ++ ret = hsmp_send_message_locked(&msg); + if (ret) + return ret; + +@@ -301,7 +350,7 @@ static bool is_get_msg(struct hsmp_message *msg) + return false; + } + +-long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++static long hsmp_ioctl_msg(struct file *fp, unsigned long arg) + { + int __user *arguser = (int __user *)arg; + struct hsmp_message msg = { 0 }; +@@ -370,11 +419,139 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) + return 0; + } + +-ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) ++static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, ++ size_t size); ++ ++/* ++ * Fetch the firmware metric (telemetry) table for the requested socket and ++ * copy it to the userspace buffer described by the request. ++ * ++ * The metric table size is variable across HSMP protocol versions and on ++ * Family 1Ah Model 50h-5Fh exceeds PAGE_SIZE. The request carries the buffer ++ * size, which may be anything up to the size firmware reported for this ++ * socket's table. ++ */ ++static long hsmp_ioctl_get_telemetry(struct file *fp, unsigned long arg) ++{ ++ void *kbuf __free(kvfree) = NULL; ++ void __user *arguser = (void __user *)arg; ++ struct hsmp_telemetry_data req; ++ struct hsmp_socket *sock; ++ void __user *user_buf; ++ size_t tbl_size; ++ unsigned int sock_ind; ++ int ret; ++ ++ /* Telemetry data is read-only; require read access on the fd. */ ++ if (!(fp->f_mode & FMODE_READ)) ++ return -EPERM; ++ ++ if (copy_from_user(&req, arguser, sizeof(req))) ++ return -EFAULT; ++ ++ /* ++ * Reserved fields must be zero so future kernels can safely ++ * repurpose them without breaking already-deployed userspace. ++ */ ++ if (req.reserved) ++ return -EINVAL; ++ ++ user_buf = u64_to_user_ptr(req.buf); ++ ++ /* ++ * /dev/hsmp is a singleton character device that outlives an individual ++ * socket unbind, so an ioctl on an already-open fd can run concurrently ++ * with socket teardown. Hold hsmp_sock_rwsem for read across the socket ++ * lookup, the checks on its metric-table state and the read itself: ++ * probe and remove take the same lock for write, so they cannot free the ++ * socket array, unmap the table or destroy the per-socket mutex while ++ * this runs. ++ * ++ * The lock is dropped before the copy_to_user() below. Faulting in the ++ * destination can block indefinitely on a userfaultfd-backed buffer, ++ * which would leave a socket unbind waiting for the write lock. ++ */ ++ scoped_guard(rwsem_read, &hsmp_sock_rwsem) { ++ if (!hsmp_pdev.sock || req.sock_ind >= hsmp_pdev.num_sockets) ++ return -ENODEV; ++ ++ /* ++ * Sanitize the user-controlled socket index against speculative ++ * execution. The bounds check above retires the out-of-range ++ * case with -ENODEV, but a mispredicted branch can still let the ++ * CPU speculatively use sock_ind as an index into ++ * hsmp_pdev.sock[] and pull arbitrary kernel memory into the ++ * cache (Spectre v1, CVE-2017-5753). array_index_nospec() turns ++ * the bounds check into a data-flow clamp so the speculative ++ * load is in-range too. ++ */ ++ sock_ind = array_index_nospec(req.sock_ind, hsmp_pdev.num_sockets); ++ sock = &hsmp_pdev.sock[sock_ind]; ++ if (!sock->metric_tbl_addr) ++ return -ENODEV; ++ ++ tbl_size = sock->metric_tbl_size; ++ if (!tbl_size) ++ return -ENODEV; ++ ++ /* ++ * A request shorter than the firmware table is served with the ++ * leading @size bytes of the snapshot, so userspace built ++ * against an older table layout keeps working on firmware that ++ * grew the table. Asking for more than firmware provides is ++ * rejected rather than short-written, so a caller can never ++ * mistake a partial copy for a full one. ++ */ ++ if (!req.size || req.size > tbl_size) ++ return -EINVAL; ++ ++ /* ++ * The bounce buffer is overwritten in full by memcpy_fromio() ++ * inside hsmp_metric_tbl_read_locked(); use kvmalloc() to avoid ++ * the zeroing cost of kvzalloc() on the ~13 KB allocation done ++ * on every ioctl call. ++ */ ++ kbuf = kvmalloc(tbl_size, GFP_KERNEL); ++ if (!kbuf) ++ return -ENOMEM; ++ ++ ret = hsmp_metric_tbl_read_locked(sock, kbuf, tbl_size); ++ } ++ ++ if (ret < 0) ++ return ret; ++ ++ if (copy_to_user(user_buf, kbuf, req.size)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ switch (cmd) { ++ case HSMP_IOCTL_CMD: ++ return hsmp_ioctl_msg(fp, arg); ++ case HSMP_IOCTL_GET_TELEMETRY_DATA: ++ return hsmp_ioctl_get_telemetry(fp, arg); ++ default: ++ return -ENOTTY; ++ } ++} ++ ++/* ++ * Caller must hold hsmp_sock_rwsem. It keeps @sock, its metric-table mapping ++ * and its metric_read_lock alive: probe and remove take the same lock for ++ * write while they bring sockets up and tear them down. ++ */ ++static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, ++ size_t size) + { + struct hsmp_message msg = { 0 }; + int ret; + ++ lockdep_assert_held(&hsmp_sock_rwsem); ++ + if (!sock || !buf) + return -EINVAL; + +@@ -383,8 +560,7 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) + return -ENOMEM; + } + +- /* Do not support lseek(), also don't allow more than the size of metric table */ +- if (size != sizeof(struct hsmp_metric_table)) { ++ if (size != sock->metric_tbl_size) { + dev_err(sock->dev, "Wrong buffer size\n"); + return -EINVAL; + } +@@ -392,27 +568,77 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) + msg.msg_id = HSMP_GET_METRIC_TABLE; + msg.sock_ind = sock->sock_ind; + +- ret = hsmp_send_message(&msg); ++ /* ++ * HSMP_GET_METRIC_TABLE makes firmware refill this socket's shared ++ * metric DRAM region, which is then copied out below. Hold the ++ * per-socket lock across the fill-and-copy so concurrent readers of the ++ * same socket cannot return a torn snapshot. ++ */ ++ guard(mutex)(&sock->metric_read_lock); ++ ++ ret = hsmp_send_message_locked(&msg); + if (ret) + return ret; + memcpy_fromio(buf, sock->metric_tbl_addr, size); + + return size; + } ++ ++ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) ++{ ++ guard(rwsem_read)(&hsmp_sock_rwsem); ++ ++ return hsmp_metric_tbl_read_locked(sock, buf, size); ++} + EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP"); + ++void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev) ++{ ++ u16 i; ++ ++ for (i = 0; i < pdev->num_sockets; i++) ++ mutex_init(&pdev->sock[i].metric_read_lock); ++} ++EXPORT_SYMBOL_NS_GPL(hsmp_init_metric_read_locks, "AMD_HSMP"); ++ ++void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev) ++{ ++ u16 i; ++ ++ for (i = 0; i < pdev->num_sockets; i++) ++ mutex_destroy(&pdev->sock[i].metric_read_lock); ++} ++EXPORT_SYMBOL_NS_GPL(hsmp_destroy_metric_read_locks, "AMD_HSMP"); ++ ++void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev) ++{ ++ struct hsmp_socket *sock; ++ u16 i; ++ ++ for (i = 0; i < pdev->num_sockets; i++) { ++ sock = &pdev->sock[i]; ++ if (sock->metric_tbl_addr) { ++ iounmap(sock->metric_tbl_addr); ++ sock->metric_tbl_addr = NULL; ++ } ++ sock->metric_tbl_size = 0; ++ } ++} ++EXPORT_SYMBOL_NS_GPL(hsmp_unmap_metric_tbls, "AMD_HSMP"); ++ + int hsmp_get_tbl_dram_base(u16 sock_ind) + { + struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind]; + struct hsmp_message msg = { 0 }; + phys_addr_t dram_addr; ++ size_t tbl_size; + int ret; + + msg.sock_ind = sock_ind; + msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz; + msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR; + +- ret = hsmp_send_message(&msg); ++ ret = hsmp_send_message_locked(&msg); + if (ret) + return ret; + +@@ -425,12 +651,33 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) + dev_err(sock->dev, "Invalid DRAM address for metric table\n"); + return -ENOMEM; + } +- sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr, +- sizeof(struct hsmp_metric_table)); ++ /* ++ * The ACPI socket array is shared across sockets and outlives a ++ * per-socket unbind, so metric_tbl_addr may hold a mapping from an ++ * earlier bind of this socket. Unmap it before remapping so an ++ * unbind/rebind cycle does not leak a metric-table mapping. This runs ++ * during probe before the metric sysfs attribute is exposed, so no ++ * reader can be using it. ++ */ ++ if (sock->metric_tbl_addr) { ++ iounmap(sock->metric_tbl_addr); ++ sock->metric_tbl_addr = NULL; ++ } ++ sock->metric_tbl_size = 0; ++ ++ /* SMU returns table size from Family 1Ah Model 50h and forward */ ++ if (msg.args[2]) ++ tbl_size = msg.args[2]; ++ else ++ tbl_size = sizeof(struct hsmp_metric_table); ++ ++ sock->metric_tbl_addr = ioremap(dram_addr, tbl_size); + if (!sock->metric_tbl_addr) { + dev_err(sock->dev, "Failed to ioremap metric table addr\n"); + return -ENOMEM; + } ++ sock->metric_tbl_size = tbl_size; ++ + return 0; + } + EXPORT_SYMBOL_NS_GPL(hsmp_get_tbl_dram_base, "AMD_HSMP"); +@@ -444,7 +691,7 @@ int hsmp_cache_proto_ver(u16 sock_ind) + msg.sock_ind = sock_ind; + msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz; + +- ret = hsmp_send_message(&msg); ++ ret = hsmp_send_message_locked(&msg); + if (!ret) + hsmp_pdev.proto_ver = msg.args[0]; + +@@ -463,6 +710,14 @@ int hsmp_misc_register(struct device *dev) + hsmp_pdev.mdev.name = HSMP_CDEV_NAME; + hsmp_pdev.mdev.minor = MISC_DYNAMIC_MINOR; + hsmp_pdev.mdev.fops = &hsmp_fops; ++ /* ++ * The caller chooses the parent. The platform driver has a single ++ * device whose lifetime matches /dev/hsmp and parents it there. The ++ * ACPI driver passes NULL: its /dev/hsmp is a singleton shared by ++ * per-socket devices that can be unbound individually and out of order, ++ * so parenting it to one would leave it attached to an already-removed ++ * device. ++ */ + hsmp_pdev.mdev.parent = dev; + hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME; + hsmp_pdev.mdev.mode = 0644; +@@ -474,6 +729,7 @@ EXPORT_SYMBOL_NS_GPL(hsmp_misc_register, "AMD_HSMP"); + void hsmp_misc_deregister(void) + { + misc_deregister(&hsmp_pdev.mdev); ++ hsmp_pdev.mdev.this_device = NULL; + } + EXPORT_SYMBOL_NS_GPL(hsmp_misc_deregister, "AMD_HSMP"); + +diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h +--- a/drivers/platform/x86/amd/hsmp/hsmp.h ++++ b/drivers/platform/x86/amd/hsmp/hsmp.h +@@ -15,9 +15,12 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include ++#include + + #define HSMP_METRICS_TABLE_NAME "metrics_bin" + +@@ -27,7 +30,7 @@ + #define HSMP_DEVNODE_NAME "hsmp" + #define ACPI_HSMP_DEVICE_HID "AMDI0097" + +-#define DRIVER_VERSION "2.5" ++#define DRIVER_VERSION "2.6" + + struct hsmp_mbaddr_info { + u32 base_addr; +@@ -41,8 +44,12 @@ struct hsmp_socket { + struct bin_attribute hsmp_attr; + struct hsmp_mbaddr_info mbinfo; + void __iomem *metric_tbl_addr; ++ /* Size of the region mapped at @metric_tbl_addr, as reported by SMU */ ++ size_t metric_tbl_size; + void __iomem *virt_base_addr; + struct semaphore hsmp_sem; ++ /* Serializes HSMP_GET_METRIC_TABLE fill-and-copy for this socket */ ++ struct mutex metric_read_lock; + char name[HSMP_ATTR_GRP_NAME_SIZE]; + struct device *dev; + u16 sock_ind; +@@ -54,7 +61,6 @@ struct hsmp_plat_device { + struct hsmp_socket *sock; + u32 proto_ver; + u16 num_sockets; +- bool is_probed; + }; + + int hsmp_cache_proto_ver(u16 sock_ind); +@@ -63,6 +69,9 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg); + void hsmp_misc_deregister(void); + int hsmp_misc_register(struct device *dev); + int hsmp_get_tbl_dram_base(u16 sock_ind); ++void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev); ++void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev); ++void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev); + ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size); + struct hsmp_plat_device *get_hsmp_pdev(void); + #if IS_ENABLED(CONFIG_HWMON) +@@ -71,4 +80,10 @@ int hsmp_create_sensor(struct device *dev, u16 sock_ind); + static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; } + #endif + int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args); ++ ++/* ++ * Gates the HSMP data plane: hsmp_send_message() takes it for read; probe and ++ * remove take it for write to bring sockets up and tear them down. ++ */ ++extern struct rw_semaphore hsmp_sock_rwsem; + #endif /* HSMP_H */ +diff --git a/drivers/platform/x86/amd/hsmp/hwmon.c b/drivers/platform/x86/amd/hsmp/hwmon.c +--- a/drivers/platform/x86/amd/hsmp/hwmon.c ++++ b/drivers/platform/x86/amd/hsmp/hwmon.c +@@ -31,6 +31,9 @@ static int hsmp_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + if (attr != hwmon_power_cap) + return -EOPNOTSUPP; + ++ if (val < 0) ++ return -EINVAL; ++ + msg.num_args = 1; + msg.args[0] = val / MICROWATT_PER_MILLIWATT; + msg.msg_id = HSMP_SET_SOCKET_POWER_LIMIT; +diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c +--- a/drivers/platform/x86/amd/hsmp/plat.c ++++ b/drivers/platform/x86/amd/hsmp/plat.c +@@ -13,12 +13,14 @@ + + #include + #include ++#include + #include + #include + #include + #include + #include + #include ++#include + #include + + #include +@@ -201,6 +203,25 @@ static int init_platform_device(struct device *dev) + return 0; + } + ++/* ++ * The socket array is devm-managed and freed by the driver core, but the ++ * metric-table DRAM regions are mapped with plain ioremap() during probe and ++ * the per-socket mutexes need an explicit mutex_destroy(), neither of which ++ * devres covers. ++ * ++ * Take the data-plane rwsem for write to drain any in-flight ++ * hsmp_send_message(), unmap the metric tables, destroy the mutexes and drop ++ * the global socket pointer, all before devres frees the array. Registered as ++ * a devres action so it runs on both remove and probe failure. ++ */ ++static void hsmp_pltdrv_release(void *data) ++{ ++ guard(rwsem_write)(&hsmp_sock_rwsem); ++ hsmp_unmap_metric_tbls(hsmp_pdev); ++ hsmp_destroy_metric_read_locks(hsmp_pdev); ++ hsmp_pdev->sock = NULL; ++} ++ + static int hsmp_pltdrv_probe(struct platform_device *pdev) + { + int ret; +@@ -211,7 +232,22 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev) + if (!hsmp_pdev->sock) + return -ENOMEM; + +- ret = init_platform_device(&pdev->dev); ++ hsmp_init_metric_read_locks(hsmp_pdev); ++ ++ ret = devm_add_action_or_reset(&pdev->dev, hsmp_pltdrv_release, NULL); ++ if (ret) ++ return ret; ++ ++ /* ++ * init_platform_device() runs the mailbox handshake via the probe-only ++ * senders, which issue messages through hsmp_send_message_locked() and ++ * so require hsmp_sock_rwsem held. Hold it for write, matching probe's ++ * role as a socket bring-up path. The lock is not held across ++ * devm_add_action_or_reset() above so the release action, which also ++ * takes it for write, does not deadlock if that registration fails. ++ */ ++ scoped_guard(rwsem_write, &hsmp_sock_rwsem) ++ ret = init_platform_device(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Failed to init HSMP mailbox\n"); + return ret; +diff --git a/drivers/platform/x86/amd/pmc/mp1_stb.c b/drivers/platform/x86/amd/pmc/mp1_stb.c +--- a/drivers/platform/x86/amd/pmc/mp1_stb.c ++++ b/drivers/platform/x86/amd/pmc/mp1_stb.c +@@ -299,10 +299,7 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) + if (!enable_stb) + return 0; + +- if (amd_is_stb_supported(dev)) { +- debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, +- &amd_stb_debugfs_fops_v2); +- } else { ++ if (!amd_is_stb_supported(dev)) { + debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, + &amd_stb_debugfs_fops); + return 0; +@@ -342,8 +339,18 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) + } + + dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size); +- if (!dev->stb_virt_addr) ++ if (!dev->stb_virt_addr) { + ret = -ENOMEM; ++ goto out; ++ } ++ ++ /* ++ * Only expose stb_read once the buffer is mapped; otherwise a read ++ * faults on a NULL dev->stb_virt_addr, now that a failed STB init no ++ * longer aborts probe. ++ */ ++ debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, ++ &amd_stb_debugfs_fops_v2); + + out: + /* Restore the default message port for subsequent SMU operations */ +diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c +--- a/drivers/platform/x86/amd/pmc/pmc-quirks.c ++++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c +@@ -71,6 +71,14 @@ static const struct dmi_system_id fwbug_list[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "20XK"), + } + }, ++ { ++ .ident = "T14 Gen2 AMD", ++ .driver_data = &quirk_s2idle_spurious_8042, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "20XL"), ++ } ++ }, + { + .ident = "T14 Gen1 AMD", + .driver_data = &quirk_s2idle_spurious_8042, +diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c +--- a/drivers/platform/x86/amd/pmc/pmc.c ++++ b/drivers/platform/x86/amd/pmc/pmc.c +@@ -939,19 +939,22 @@ static int amd_pmc_probe(struct platform_device *pdev) + } + + amd_pmc_dbgfs_register(dev); ++ ++ /* ++ * STB is an optional debug facility (enable_stb); a failure to set it ++ * up must not stop the rest of the driver - most importantly the s0i3 ++ * LPS0 handler - from working, so treat it as non-fatal. ++ */ + err = amd_stb_s2d_init(dev); + if (err) +- goto err_dbgfs_unregister; ++ dev_warn(dev->dev, "STB initialization failed (%d), continuing without STB support\n", ++ err); + + if (IS_ENABLED(CONFIG_AMD_MP2_STB)) + amd_mp2_stb_init(dev); + pm_report_max_hw_sleep(U64_MAX); + return 0; + +-err_dbgfs_unregister: +- amd_pmc_dbgfs_unregister(dev); +- if (IS_ENABLED(CONFIG_SUSPEND)) +- acpi_unregister_lps0_dev(&amd_pmc_s2idle_dev_ops); + err_pci_dev_put: + pci_dev_put(rdev); + return err; +diff --git a/drivers/platform/x86/amd/pmf/Kconfig b/drivers/platform/x86/amd/pmf/Kconfig +--- a/drivers/platform/x86/amd/pmf/Kconfig ++++ b/drivers/platform/x86/amd/pmf/Kconfig +@@ -30,3 +30,13 @@ config AMD_PMF_DEBUG + in the PMF config store. + + Say Y here to enable more debug logs and Say N here if you are not sure. ++ ++config AMD_PMF_UTIL_SUPPORT ++ bool "AMD PMF Util layer support" ++ depends on AMD_PMF ++ help ++ Enabling this option provides a character device for userspace to capture ++ PMF features (Smart PC Builder, Auto Mode, Static Power Slider, Dynamic ++ Power Slider AC/DC) along with PMF metrics from the AMD PMF driver. ++ ++ Say Y here to enable it and Say N here if you are not sure. +diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile +--- a/drivers/platform/x86/amd/pmf/Makefile ++++ b/drivers/platform/x86/amd/pmf/Makefile +@@ -7,4 +7,6 @@ + obj-$(CONFIG_AMD_PMF) += amd-pmf.o + amd-pmf-y := core.o acpi.o sps.o \ + auto-mode.o cnqf.o \ +- tee-if.o spc.o ++ tee-if.o spc.o metrics.o ++# Build util.c only when AMD_PMF_UTIL_SUPPORT is enabled ++amd-pmf-$(CONFIG_AMD_PMF_UTIL_SUPPORT) += util.o +diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c +--- a/drivers/platform/x86/amd/pmf/core.c ++++ b/drivers/platform/x86/amd/pmf/core.c +@@ -11,13 +11,13 @@ + #include + #include + #include ++#include + #include + #include + #include + #include + #include + #include +-#include + #include + #include "pmf.h" + +@@ -26,6 +26,13 @@ + #define AMD_PMF_REGISTER_RESPONSE 0xA78 + #define AMD_PMF_REGISTER_ARGUMENT 0xA58 + ++/* PMF-SMU communication registers for 1AH_M80H */ ++#define AMD_PMF_REGISTER_MESSAGE_V2 0xA04 ++#define AMD_PMF_REGISTER_RESPONSE_V2 0xA08 ++#define AMD_PMF_REGISTER_ARGUMENT0_V2 0xA0C ++#define AMD_PMF_REGISTER_ARGUMENT1_V2 0xAAC ++#define AMD_PMF_REGISTER_ARGUMENT2_V2 0xAB0 ++ + /* Base address of SMU for mapping physical address to virtual address */ + #define AMD_PMF_MAPPING_SIZE 0x01000 + #define AMD_PMF_BASE_ADDR_OFFSET 0x10000 +@@ -48,7 +55,7 @@ + #define DELAY_MAX_US 3000 + + /* override Metrics Table sample size time (in ms) */ +-static int metrics_table_loop_ms = 1000; ++int metrics_table_loop_ms = 1000; + module_param(metrics_table_loop_ms, int, 0644); + MODULE_PARM_DESC(metrics_table_loop_ms, "Metrics Table sample size time (default = 1000ms)"); + +@@ -61,7 +68,15 @@ static bool smart_pc_support = true; + module_param(smart_pc_support, bool, 0444); + MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)"); + +-static struct device *pmf_device; ++static bool amd_pmf_supports_accumulator_metrics(struct amd_pmf_dev *pdev) ++{ ++ switch (pdev->cpu_id) { ++ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: ++ return true; ++ default: ++ return false; ++ } ++} + + static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data) + { +@@ -131,37 +146,6 @@ int amd_pmf_get_power_source(void) + return POWER_SOURCE_DC; + } + +-static void amd_pmf_get_metrics(struct work_struct *work) +-{ +- struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work); +- ktime_t time_elapsed_ms; +- int socket_power; +- +- guard(mutex)(&dev->update_mutex); +- +- /* Transfer table contents */ +- memset(dev->buf, 0, sizeof(dev->m_table)); +- amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); +- memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table)); +- +- time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time; +- /* Calculate the avg SoC power consumption */ +- socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power; +- +- if (dev->amt_enabled) { +- /* Apply the Auto Mode transition */ +- amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms); +- } +- +- if (dev->cnqf_enabled) { +- /* Apply the CnQF transition */ +- amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms); +- } +- +- dev->start_time = ktime_to_ms(ktime_get()); +- schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms)); +-} +- + static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset) + { + return ioread32(dev->regbase + reg_offset); +@@ -176,13 +160,19 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev) + { + u32 value; + +- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_RESPONSE); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->resp_reg); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_RESPONSE:%x\n", value); + +- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT:%d\n", value); ++ if (amd_pmf_supports_accumulator_metrics(dev)) { ++ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[1]); ++ dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT1:%d\n", value); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[2]); ++ dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT2:%d\n", value); ++ } + +- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_MESSAGE); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->msg_reg); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value); + } + +@@ -208,7 +198,7 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + guard(mutex)(&dev->lock); + + /* Wait until we get a valid response */ +- rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE, ++ rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg, + val, val != 0, PMF_MSG_DELAY_MIN_US, + PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); + if (rc) { +@@ -217,16 +207,16 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + } + + /* Write zero to response register */ +- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_RESPONSE, 0); ++ amd_pmf_reg_write(dev, dev->smu_regs->resp_reg, 0); + + /* Write argument into argument register */ +- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_ARGUMENT, arg); ++ amd_pmf_reg_write(dev, dev->smu_regs->arg_reg[0], arg); + + /* Write message ID to message ID register */ +- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_MESSAGE, message); ++ amd_pmf_reg_write(dev, dev->smu_regs->msg_reg, message); + + /* Wait until we get a valid response */ +- rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE, ++ rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg, + val, val != 0, PMF_MSG_DELAY_MIN_US, + PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); + if (rc) { +@@ -239,7 +229,14 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + if (get) { + /* PMFW may take longer time to return back the data */ + usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US); +- *data = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT); ++ *data = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]); ++ if (amd_pmf_supports_accumulator_metrics(dev) && ++ message == GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR) { ++ dev->dram_addr.hi = amd_pmf_reg_read(dev, ++ dev->smu_regs->arg_reg[1]); ++ dev->dram_addr.size = amd_pmf_reg_read(dev, ++ dev->smu_regs->arg_reg[2]); ++ } + } + break; + case AMD_PMF_RESULT_CMD_REJECT_BUSY: +@@ -262,132 +259,32 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + return rc; + } + +-static const struct pci_device_id pmf_pci_ids[] = { +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RMB) }, +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PS) }, +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M20H_ROOT) }, +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M60H_ROOT) }, +- { } ++/* RMB, PS, 1AH_M20H and 1AH_M60H share the same v1 SMU mailbox registers */ ++static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v1 = { ++ .msg_reg = AMD_PMF_REGISTER_MESSAGE, ++ .resp_reg = AMD_PMF_REGISTER_RESPONSE, ++ .arg_reg = { AMD_PMF_REGISTER_ARGUMENT, 0, 0 }, + }; + +-int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer) +-{ +- u64 phys_addr; +- u32 hi, low; ++/* 1AH_M80H uses an extended mailbox with three argument registers */ ++static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v2 = { ++ .msg_reg = AMD_PMF_REGISTER_MESSAGE_V2, ++ .resp_reg = AMD_PMF_REGISTER_RESPONSE_V2, ++ .arg_reg = { ++ AMD_PMF_REGISTER_ARGUMENT0_V2, ++ AMD_PMF_REGISTER_ARGUMENT1_V2, ++ AMD_PMF_REGISTER_ARGUMENT2_V2, ++ }, ++}; + +- /* Get Metrics Table Address */ +- if (alloc_buffer) { +- switch (dev->cpu_id) { +- case AMD_CPU_ID_PS: +- case AMD_CPU_ID_RMB: +- dev->mtable_size = sizeof(dev->m_table); +- break; +- case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: +- case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: +- dev->mtable_size = sizeof(dev->m_table_v2); +- break; +- default: +- dev_err(dev->dev, "Invalid CPU id: 0x%x", dev->cpu_id); +- } +- +- dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL); +- if (!dev->buf) +- return -ENOMEM; +- } +- +- phys_addr = virt_to_phys(dev->buf); +- hi = phys_addr >> 32; +- low = phys_addr & GENMASK(31, 0); +- +- amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL); +- amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL); +- +- return 0; +-} +- +-int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev) +-{ +- int ret; +- +- INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics); +- +- ret = amd_pmf_set_dram_addr(dev, true); +- if (ret) +- return ret; +- +- /* +- * Start collecting the metrics data after a small delay +- * or else, we might end up getting stale values from PMFW. +- */ +- schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3)); +- +- return 0; +-} +- +-static int is_npu_metrics_supported(struct amd_pmf_dev *pdev) +-{ +- switch (pdev->cpu_id) { +- case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: +- case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: +- return 0; +- default: +- return -EOPNOTSUPP; +- } +-} +- +-static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data) +-{ +- int ret, i; +- +- guard(mutex)(&dev->metrics_mutex); +- +- ret = is_npu_metrics_supported(dev); +- if (ret) +- return ret; +- +- ret = amd_pmf_set_dram_addr(dev, true); +- if (ret) +- return ret; +- +- memset(dev->buf, 0, dev->mtable_size); +- +- /* Send SMU command to get NPU metrics */ +- ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); +- if (ret) { +- dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret); +- return ret; +- } +- +- memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size); +- +- data->npuclk_freq = dev->m_table_v2.npuclk_freq; +- for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++) +- data->npu_busy[i] = dev->m_table_v2.npu_busy[i]; +- data->npu_power = dev->m_table_v2.npu_power; +- data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq; +- data->npu_reads = dev->m_table_v2.npu_reads; +- data->npu_writes = dev->m_table_v2.npu_writes; +- +- return 0; +-} +- +-int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info) +-{ +- struct amd_pmf_dev *pdev; +- +- if (!info) +- return -EINVAL; +- +- if (!pmf_device) +- return -ENODEV; +- +- pdev = dev_get_drvdata(pmf_device); +- if (!pdev) +- return -ENODEV; +- +- return amd_pmf_get_smu_metrics(pdev, info); +-} +-EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF"); ++static const struct pci_device_id pmf_pci_ids[] = { ++ { PCI_DEVICE_DATA(AMD, CPU_ID_RMB, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, CPU_ID_PS, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, 1AH_M20H_ROOT, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, 1AH_M60H_ROOT, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, 1AH_M80H_ROOT, &amd_pmf_smu_regs_v2) }, ++ { } ++}; + + static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev) + { +@@ -536,6 +433,19 @@ static void amd_pmf_deinit_features(struct amd_pmf_dev *dev) + } + } + ++static int amd_pmf_get_smu_mb_offset(struct amd_pmf_dev *pdev, struct pci_dev *rdev) ++{ ++ const struct pci_device_id *id; ++ ++ id = pci_match_id(pmf_pci_ids, rdev); ++ if (!id) ++ return -ENODEV; ++ ++ pdev->smu_regs = (const struct amd_pmf_smu_regs *)id->driver_data; ++ ++ return 0; ++} ++ + static const struct acpi_device_id amd_pmf_acpi_ids[] = { + {"AMDI0100", 0x100}, + {"AMDI0102", 0}, +@@ -543,6 +453,7 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = { + {"AMDI0105", 0}, + {"AMDI0107", 0}, + {"AMDI0108", 0}, ++ {"AMDI0109", 0}, + { } + }; + MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids); +@@ -624,6 +535,17 @@ static int amd_pmf_probe(struct platform_device *pdev) + if (err) + return err; + ++ /* Populate smu_regs with SoC-specific SMU mailbox register offsets */ ++ err = amd_pmf_get_smu_mb_offset(dev, rdev); ++ if (err) ++ return err; ++ ++ if (amd_pmf_supports_accumulator_metrics(dev)) { ++ err = amd_pmf_get_tbl_dram_addr(dev); ++ if (err) ++ return err; ++ } ++ + apmf_acpi_init(dev); + platform_set_drvdata(pdev, dev); + amd_pmf_dbgfs_register(dev); +@@ -632,7 +554,11 @@ static int amd_pmf_probe(struct platform_device *pdev) + if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) + amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD); + +- pmf_device = dev->dev; ++ amd_pmf_set_device(dev->dev); ++ ++ err = amd_pmf_cdev_register(dev); ++ if (err) ++ dev_warn(dev->dev, "failed to register util interface: %d\n", err); + + dev_info(dev->dev, "registered PMF device successfully\n"); + +@@ -643,6 +569,7 @@ static void amd_pmf_remove(struct platform_device *pdev) + { + struct amd_pmf_dev *dev = platform_get_drvdata(pdev); + ++ amd_pmf_cdev_unregister(); + amd_pmf_deinit_features(dev); + if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) + amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_UNLOAD); +diff --git a/drivers/platform/x86/amd/pmf/metrics.c b/drivers/platform/x86/amd/pmf/metrics.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/amd/pmf/metrics.c +@@ -0,0 +1,330 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * AMD Platform Management Framework Driver - Metrics Support ++ * ++ * Copyright (c) 2026, Advanced Micro Devices, Inc. ++ * All Rights Reserved. ++ * ++ * Authors: Shyam Sundar S K ++ * Patil Rajesh Reddy ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmf.h" ++ ++static struct device *pmf_device; ++ ++static u16 amd_pmf_q10_acc_to_mW(u64 avg_acc) ++{ ++ u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024) * MILLIWATT_PER_WATT; ++ ++ return min_t(u16, val, U16_MAX); ++} ++ ++static u16 amd_pmf_q10_acc_to_int(u64 avg_acc) ++{ ++ u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024); ++ ++ return min_t(u16, val, U16_MAX); ++} ++ ++static u64 amd_pmf_avg_acc_metric(u32 curr_counter, u32 prev_counter, ++ u64 curr_value, u64 prev_value) ++{ ++ u32 counter_diff; ++ u64 val_diff; ++ ++ /* Check for counter reset */ ++ if (curr_counter <= prev_counter) ++ return 0; ++ ++ counter_diff = curr_counter - prev_counter; ++ ++ if (curr_value < prev_value) ++ return 0; ++ ++ val_diff = curr_value - prev_value; ++ ++ return DIV_U64_ROUND_CLOSEST(val_diff, counter_diff); ++} ++ ++int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR, GET_CMD, ++ ARG_NONE, &dev->dram_addr.lo); ++ if (ret) { ++ dev_err(dev->dev, "Failed to get DRAM address: %d\n", ret); ++ return ret; ++ } ++ ++ dev->metrics_table_phys = ((u64)dev->dram_addr.hi << 32) | dev->dram_addr.lo; ++ dev->mtable_size = dev->dram_addr.size; ++ ++ if (dev->mtable_size != sizeof(dev->mtable_v3)) { ++ dev_err(dev->dev, "Metrics table size mismatch: got %u, expected %zu\n", ++ dev->dram_addr.size, sizeof(dev->mtable_v3)); ++ return -EINVAL; ++ } ++ ++ dev->metrics_table_virt = devm_ioremap(dev->dev, dev->metrics_table_phys, dev->mtable_size); ++ if (!dev->metrics_table_virt) { ++ dev_err(dev->dev, "Failed to map DRAM address for PMF metrics table\n"); ++ dev->metrics_table_phys = 0; ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static int amd_pmf_get_metrics_table_log_sample(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ if (!dev->metrics_table_virt) { ++ dev_err(dev->dev, "Metrics DRAM not mapped\n"); ++ return -EINVAL; ++ } ++ ++ /* Send command to PMFW to update metrics table log sample */ ++ ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE, SET_CMD, ARG_NONE, NULL); ++ if (ret) { ++ dev_err(dev->dev, "Failed to request metrics log sample: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void amd_pmf_get_metrics(struct work_struct *work) ++{ ++ struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work); ++ ktime_t time_elapsed_ms; ++ int socket_power; ++ ++ guard(mutex)(&dev->update_mutex); ++ ++ /* Transfer table contents */ ++ memset(dev->buf, 0, sizeof(dev->m_table)); ++ amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); ++ memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table)); ++ ++ time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time; ++ /* Calculate the avg SoC power consumption */ ++ socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power; ++ ++ if (dev->amt_enabled) { ++ /* Apply the Auto Mode transition */ ++ amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms); ++ } ++ ++ if (dev->cnqf_enabled) { ++ /* Apply the CnQF transition */ ++ amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms); ++ } ++ ++ dev->start_time = ktime_to_ms(ktime_get()); ++ schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms)); ++} ++ ++int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer) ++{ ++ u64 phys_addr; ++ u32 hi, low; ++ ++ /* Get Metrics Table Address */ ++ if (alloc_buffer) { ++ switch (dev->cpu_id) { ++ case AMD_CPU_ID_PS: ++ case AMD_CPU_ID_RMB: ++ dev->mtable_size = sizeof(dev->m_table); ++ break; ++ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: ++ dev->mtable_size = sizeof(dev->m_table_v2); ++ break; ++ default: ++ dev_err(dev->dev, "Invalid CPU id: 0x%x\n", dev->cpu_id); ++ } ++ ++ dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL); ++ if (!dev->buf) ++ return -ENOMEM; ++ } ++ ++ phys_addr = virt_to_phys(dev->buf); ++ hi = upper_32_bits(phys_addr); ++ low = lower_32_bits(phys_addr); ++ ++ amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL); ++ amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL); ++ ++ return 0; ++} ++ ++int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics); ++ ++ ret = amd_pmf_set_dram_addr(dev, true); ++ if (ret) ++ return ret; ++ ++ /* ++ * Start collecting the metrics data after a small delay ++ * or else, we might end up getting stale values from PMFW. ++ */ ++ schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3)); ++ ++ return 0; ++} ++ ++void amd_pmf_set_device(struct device *p_device) ++{ ++ pmf_device = p_device; ++} ++ ++static int is_npu_metrics_supported(struct amd_pmf_dev *pdev) ++{ ++ switch (pdev->cpu_id) { ++ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: ++ return 0; ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static void amd_pmf_calculate_acc_npu_metrics(struct amd_pmf_dev *dev, ++ struct amd_pmf_npu_metrics *data) ++{ ++ struct amd_pmf_metrics_iod *curr, *prev; ++ u64 val; ++ int i; ++ ++ curr = &dev->mtable_v3.iod; ++ prev = &dev->prev_metrics.iod; ++ ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npu_temp_acc, prev->npu_temp_acc); ++ data->npu_temp = amd_pmf_q10_acc_to_int(val); ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npu_power_acc, prev->npu_power_acc); ++ data->npu_power = amd_pmf_q10_acc_to_mW(val); ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npuhclk_freq_eff_acc, ++ prev->npuhclk_freq_eff_acc); ++ data->mpnpuclk_freq = amd_pmf_q10_acc_to_int(val); ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->aieclk_freq_eff_acc, ++ prev->aieclk_freq_eff_acc); ++ data->npuclk_freq = amd_pmf_q10_acc_to_int(val); ++ ++ for (i = 0; i < ARRAY_SIZE(curr->npu_busy_acc); i++) { ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npu_busy_acc[i], ++ prev->npu_busy_acc[i]); ++ data->npu_busy[i] = amd_pmf_q10_acc_to_int(val); ++ } ++} ++ ++static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data) ++{ ++ int ret, i; ++ ++ guard(mutex)(&dev->metrics_mutex); ++ ++ ret = is_npu_metrics_supported(dev); ++ if (ret) ++ return ret; ++ ++ memset(data, 0, sizeof(*data)); ++ ++ switch (dev->cpu_id) { ++ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: ++ ret = amd_pmf_set_dram_addr(dev, true); ++ if (ret) ++ return ret; ++ ++ memset(dev->buf, 0, dev->mtable_size); ++ ++ /* Send SMU command to get NPU metrics */ ++ ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); ++ if (ret) { ++ dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret); ++ return ret; ++ } ++ ++ memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size); ++ ++ data->npuclk_freq = dev->m_table_v2.npuclk_freq; ++ for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++) ++ data->npu_busy[i] = dev->m_table_v2.npu_busy[i]; ++ data->npu_power = dev->m_table_v2.npu_power; ++ data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq; ++ data->npu_reads = dev->m_table_v2.npu_reads; ++ data->npu_writes = dev->m_table_v2.npu_writes; ++ break; ++ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: ++ ret = amd_pmf_get_metrics_table_log_sample(dev); ++ if (ret) ++ return ret; ++ ++ memcpy_fromio(&dev->mtable_v3, dev->metrics_table_virt, sizeof(dev->mtable_v3)); ++ ++ /* ++ * Ignore the first sample, as previous metrics is uninitialized (zero) ++ * Metrics are calculated as: ++ * metrics = current_metrics - previous_metrics ++ * Skipping the initial sample ensures accurate delta calculations. ++ */ ++ if (!dev->npu_metrics_have_prev) { ++ memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3)); ++ dev->npu_metrics_have_prev = true; ++ return 0; ++ } ++ ++ amd_pmf_calculate_acc_npu_metrics(dev, data); ++ memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3)); ++ break; ++ } ++ ++ return 0; ++} ++ ++int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info) ++{ ++ struct amd_pmf_dev *pdev; ++ ++ if (!info) ++ return -EINVAL; ++ ++ if (!pmf_device) ++ return -ENODEV; ++ ++ pdev = dev_get_drvdata(pmf_device); ++ if (!pdev) ++ return -ENODEV; ++ ++ return amd_pmf_get_smu_metrics(pdev, info); ++} ++EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF"); +diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h +--- a/drivers/platform/x86/amd/pmf/pmf.h ++++ b/drivers/platform/x86/amd/pmf/pmf.h +@@ -14,10 +14,13 @@ + #include + #include + #include ++#include ++#include + #include + #include + #include + #include ++#include + + #define POLICY_BUF_MAX_SZ 0x4b000 + #define POLICY_SIGN_COOKIE 0x31535024 +@@ -28,6 +31,11 @@ + #define AMD_CPU_ID_PS 0x14e8 + #define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507 + #define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122 ++#define PCI_DEVICE_ID_AMD_1AH_M80H_ROOT 0x115b ++ ++/* Aliases required by PCI_DEVICE_DATA() macro naming convention */ ++#define PCI_DEVICE_ID_AMD_CPU_ID_RMB AMD_CPU_ID_RMB ++#define PCI_DEVICE_ID_AMD_CPU_ID_PS AMD_CPU_ID_PS + + struct cookie_header { + u32 sign; +@@ -70,6 +78,10 @@ struct cookie_header { + #define SET_PMF_PPT 0x25 + #define SET_PMF_PPT_APU_ONLY 0x26 + ++/* Message IDs for 1AH_M80H platform */ ++#define GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE 0x0E ++#define GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR 0x0F ++ + /* OS slider update notification */ + #define DC_BEST_PERF 0 + #define DC_BETTER_PERF 1 +@@ -130,6 +142,14 @@ struct cookie_header { + #define GET_CMD true + + #define METRICS_TABLE_ID 7 ++#define BIOS_OUTPUT_MAX 10 ++ ++extern int metrics_table_loop_ms; ++ ++#define AMD_PMF_METRIC_CORE_TYPE_MAX 4 ++#define AMD_PMF_METRIC_CCX_MAX 4 ++#define AMD_PMF_NUM_CLK_DPM_LEVELS 8 ++#define AMD_PMF_NUM_MAX_CORES 12 + + typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data); + +@@ -242,6 +262,199 @@ struct apmf_fan_idx { + u32 fan_ctl_idx; + } __packed; + ++struct amd_pmf_metrics_iod { ++ u32 counter_acc; ++ /* Set Voltage */ ++ u64 vddcr_set_voltage; ++ u64 vddcr_soc_set_voltage; ++ u64 vddcr_npu_set_voltage; ++ u64 vddcr_lp_set_voltage; ++ u64 vddcr_gfx_set_voltage; ++ u64 vdd_misc_set_voltage; ++ /* Telemetry Voltages */ ++ u64 vddcr_telemetry_voltage; ++ u64 vddcr_soc_telemetry_voltage; ++ u64 vddcr_npu_telemetry_voltage; ++ u64 vddcr_lp_telemetry_voltage; ++ u64 vddcr_gfx_telemetry_voltage; ++ u64 vdd_misc_telemetry_voltage; ++ /* Telemetry Powers */ ++ u64 vddcr_telemetry_power; ++ u64 vddcr_soc_telemetry_power; ++ u64 vddcr_npu_telemetry_power; ++ u64 vddcr_lp_telemetry_power; ++ u64 vddcr_gfx_telemetry_power; ++ u64 vdd_misc_telemetry_power; ++ /* Throttlers - Fast PPT */ ++ u32 fppt_fused_limit; ++ u32 fppt_max_irm_limit; ++ u32 fppt_max_pbo_limit; ++ u32 fppt_limit; ++ u64 fppt_value_acc; ++ u32 fppt_residency_acc; ++ /* Throttlers - Slow PPT */ ++ u32 sppt_fused_limit; ++ u32 sppt_max_irm_limit; ++ u32 sppt_max_pbo_limit; ++ u32 sppt_limit; ++ u64 sppt_value_acc; ++ u32 sppt_residency_acc; ++ /* Throttlers - STAPM */ ++ u32 spl_fused_limit; ++ u32 spl_max_irm_limit; ++ u32 spl_max_pbo_limit; ++ u32 spl_limit; ++ u64 spl_value_acc; ++ u32 spl_residency_acc; ++ /* Throttlers - TDC VDDCR */ ++ u32 tdc_vddcr_fused_limit; ++ u32 tdc_vddcr_max_irm_limit; ++ u32 tdc_vddcr_max_pbo_limit; ++ u32 tdc_vddcr_limit; ++ u64 tdc_vddcr_value_acc; ++ u32 tdc_vddcr_residency_acc; ++ /* Throttlers - TDC VDDCR_SOC */ ++ u32 tdc_vddcr_soc_fused_limit; ++ u32 tdc_vddcr_soc_max_irm_limit; ++ u32 tdc_vddcr_soc_max_pbo_limit; ++ u32 tdc_vddcr_soc_limit; ++ u64 tdc_vddcr_soc_value_acc; ++ u32 tdc_vddcr_soc_residency_acc; ++ /* Throttlers - TDC VDDCR_NPU */ ++ u32 tdc_vddcr_npu_fused_limit; ++ u32 tdc_vddcr_npu_max_irm_limit; ++ u32 tdc_vddcr_npu_max_pbo_limit; ++ u32 tdc_vddcr_npu_limit; ++ u64 tdc_vddcr_npu_value_acc; ++ u32 tdc_vddcr_npu_residency_acc; ++ /* Throttlers - TDC VDDCR_LP */ ++ u32 tdc_vddcr_lp_fused_limit; ++ u32 tdc_vddcr_lp_max_irm_limit; ++ u32 tdc_vddcr_lp_max_pbo_limit; ++ u32 tdc_vddcr_lp_limit; ++ u64 tdc_vddcr_lp_value_acc; ++ u32 tdc_vddcr_lp_residency_acc; ++ /* Throttlers - TDC VDDCR_GFX */ ++ u32 tdc_vddcr_gfx_fused_limit; ++ u32 tdc_vddcr_gfx_max_irm_limit; ++ u32 tdc_vddcr_gfx_max_pbo_limit; ++ u32 tdc_vddcr_gfx_limit; ++ u64 tdc_vddcr_gfx_value_acc; ++ u32 tdc_vddcr_gfx_residency_acc; ++ /* Throttlers - EDC VDDCR */ ++ u32 edc_vddcr_fused_limit; ++ u32 edc_vddcr_max_irm_limit; ++ u32 edc_vddcr_max_pbo_limit; ++ u32 edc_vddcr_limit; ++ /* Throttlers - Thermal */ ++ u32 thm_fused_limit; ++ u32 thm_limit; ++ u64 thm_value_acc; ++ u32 thm_residency_acc; ++ u32 prochot_residency_acc; ++ u64 gfx_temp_acc; ++ u64 soc_temp_acc; ++ u32 p3t_fused_limit; ++ u64 p3t_value_acc; ++ /* Power */ ++ u64 system_power_acc; ++ u64 apu_power_acc; ++ u64 dgpu_power_acc; ++ u64 npu_power_acc; ++ /* Frequencies */ ++ u64 fclk_freq_eff_acc; ++ u64 memclk_freq_eff_acc; ++ u64 lclk_freq_eff_acc; ++ u64 gfxclk_freq_eff_acc; ++ u64 socclk_freq_eff_acc; ++ u64 vclk_freq_eff_acc; ++ u64 vpeclk_freq_eff_acc; ++ u64 aieclk_freq_eff_acc; ++ u64 npuhclk_freq_eff_acc; ++ /* Bandwidth */ ++ u64 dram_read_bandwidth; ++ u64 dram_write_bandwidth; ++ /* Activity Monitors */ ++ u64 gfx_busy_acc; ++ u64 vcn_busy_acc; ++ u64 npu_busy_acc[3]; ++ /* STT Limits */ ++ u32 stt_min_limit; ++ u64 stt_apu_hotspot_temp_acc; ++ u64 stt_hs2_hotspot_temp_acc; ++ u32 stt_apu_temp_limit; ++ u64 stt_apu_skin_temp_acc; ++ /* Residencies */ ++ u64 cpuoff_residency_ccx0; ++ u64 cpuoff_residency_ccx1; ++ u64 cpuoff_residency_ccx2; ++ u64 cpuoff_residency_ccx3; ++ /* DF-pstates */ ++ u32 fclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u32 uclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u32 ddr_rate_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u8 dfpstate_source[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ /* System */ ++ u8 gfx_disabled; ++ u8 spare2[3]; ++ u32 gfxclk_fmax; ++ u8 cclk_core_fuse_enable[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; ++ u8 cclk_core_enabled[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; ++ u32 cclk_fmax[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; ++ /* Overclock Capable */ ++ u8 cpu_precise_and_direct_oc_capable; ++ u8 gfx_precise_and_direct_oc_capable; ++ u8 pbo_basic_oc_capable; ++ u8 pbo_advanced_oc_capable; ++ u8 pbo_nitro_oc_capable; ++ u8 memory_and_fabric_oc_capable; ++ u8 misc_oc_capable; ++ u8 extreme_cold_oc_capable; ++ u8 down_config_control_capable; ++ u8 spare0[3]; ++ /* Overclock Status */ ++ u32 fit_limit_scalar; ++ u8 ln2_enabled; ++ u8 cpu_precise_and_direct_oc_enabled; ++ u8 gfx_precise_and_direct_oc_enabled; ++ u8 spare1[2]; ++ /* Voltage Guardband in PSM count */ ++ s8 psm_guardband[5][5][3]; ++ s32 core_power_limit_offset; ++ u32 max_freq_offset[5]; ++ u64 npu_temp_acc; ++ u64 dfpstate_residency_acc[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u32 cclk_fboost; ++ /* PMF */ ++ u32 pmf_fast_apu_ppt_limit; ++ u64 pmf_fast_apu_ppt_value_acc; ++ u32 pmf_fast_apu_ppt_residency_acc; ++ u32 pmf_slow_apu_ppt_limit; ++ u64 pmf_slow_apu_ppt_value_acc; ++ u32 pmf_slow_apu_ppt_residency_acc; ++ u32 pmf_fast_spm_limit; ++ u64 pmf_fast_spm_value_acc; ++ u32 pmf_fast_spm_residency_acc; ++ u32 pmf_slow_spm_limit; ++ u64 pmf_slow_spm_value_acc; ++ u32 pmf_slow_spm_residency_acc; ++ u32 spare3[5]; ++} __packed __aligned(4); ++ ++struct amd_pmf_metrics_ccx { ++ u64 core_c0[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_cc6[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_freq[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_freqeff[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_temp[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_power[AMD_PMF_NUM_MAX_CORES]; ++} __packed __aligned(4); ++ ++struct amd_pmf_metrics_v3 { ++ struct amd_pmf_metrics_iod iod; ++ struct amd_pmf_metrics_ccx ccx[AMD_PMF_METRIC_CCX_MAX]; ++} __packed __aligned(4); ++ + struct smu_pmf_metrics_v2 { + u16 core_frequency[16]; /* MHz */ + u16 core_power[16]; /* mW */ +@@ -391,6 +604,19 @@ struct pmf_cbi_ring_buffer { + int tail; + }; + ++/* SoC-specific SMU mailbox register offsets */ ++struct amd_pmf_smu_regs { ++ u32 msg_reg; ++ u32 resp_reg; ++ u32 arg_reg[3]; ++}; ++ ++struct amd_pmf_arg_data { ++ u32 lo; ++ u32 hi; ++ u32 size; ++}; ++ + struct amd_pmf_dev { + void __iomem *regbase; + void __iomem *smu_virt_addr; +@@ -442,6 +668,14 @@ struct amd_pmf_dev { + struct pmf_cbi_ring_buffer cbi_buf; + struct mutex cbi_mutex; /* Protects ring buffer access */ + struct mutex metrics_mutex; ++ u32 bios_output[BIOS_OUTPUT_MAX]; ++ const struct amd_pmf_smu_regs *smu_regs; ++ void __iomem *metrics_table_virt; /* Mapped DRAM virtual address for metrics table */ ++ phys_addr_t metrics_table_phys; /* DRAM physical address for metrics table */ ++ struct amd_pmf_metrics_v3 mtable_v3; /* IOD and CCX */ ++ struct amd_pmf_arg_data dram_addr; ++ struct amd_pmf_metrics_v3 prev_metrics; /* Previous metrics for delta calculation */ ++ bool npu_metrics_have_prev; + }; + + struct apmf_sps_prop_granular_v2 { +@@ -680,14 +914,6 @@ enum system_state { + SYSTEM_STATE_MAX, + }; + +-enum ta_slider { +- TA_BEST_BATTERY, +- TA_BETTER_BATTERY, +- TA_BETTER_PERFORMANCE, +- TA_BEST_PERFORMANCE, +- TA_MAX, +-}; +- + struct amd_pmf_pb_bitmap { + const char *name; + u32 bit_mask; +@@ -719,20 +945,6 @@ static const struct amd_pmf_pb_bitmap custom_bios_inputs_v1[] __used = { + {"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(16)}, + }; + +-enum platform_type { +- PTYPE_UNKNOWN = 0, +- LID_CLOSE, +- CLAMSHELL, +- FLAT, +- TENT, +- STAND, +- TABLET, +- BOOK, +- PRESENTATION, +- PULL_FWD, +- PTYPE_INVALID = 0xf, +-}; +- + /* Command ids for TA communication */ + enum ta_pmf_command { + TA_PMF_COMMAND_POLICY_BUILDER_INITIALIZE, +@@ -871,6 +1083,10 @@ 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); ++void amd_pmf_set_device(struct device *p_device); ++ ++/* Metrics layer */ ++int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev); + + /* SPS Layer */ + int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf); +@@ -923,9 +1139,19 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq + 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); ++u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index); + + int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid); + void amd_pmf_tee_deinit(struct amd_pmf_dev *dev); + int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev); + ++/* Util Layer */ ++#if IS_ENABLED(CONFIG_AMD_PMF_UTIL_SUPPORT) ++int amd_pmf_cdev_register(struct amd_pmf_dev *dev); ++void amd_pmf_cdev_unregister(void); ++#else ++static inline int amd_pmf_cdev_register(struct amd_pmf_dev *dev) { return 0; } ++static inline void amd_pmf_cdev_unregister(void) {} ++#endif ++ + #endif /* PMF_H */ +diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c +--- a/drivers/platform/x86/amd/pmf/spc.c ++++ b/drivers/platform/x86/amd/pmf/spc.c +@@ -10,6 +10,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -17,61 +18,7 @@ + #include "pmf.h" + + #ifdef CONFIG_AMD_PMF_DEBUG +-static const char *platform_type_as_str(u16 platform_type) +-{ +- switch (platform_type) { +- case CLAMSHELL: +- return "CLAMSHELL"; +- case FLAT: +- return "FLAT"; +- case TENT: +- return "TENT"; +- case STAND: +- return "STAND"; +- case TABLET: +- return "TABLET"; +- case BOOK: +- return "BOOK"; +- case PRESENTATION: +- return "PRESENTATION"; +- case PULL_FWD: +- return "PULL_FWD"; +- default: +- return "UNKNOWN"; +- } +-} +- +-static const char *laptop_placement_as_str(u16 device_state) +-{ +- switch (device_state) { +- case ON_TABLE: +- return "ON_TABLE"; +- case ON_LAP_MOTION: +- return "ON_LAP_MOTION"; +- case IN_BAG: +- return "IN_BAG"; +- case OUT_OF_BAG: +- return "OUT_OF_BAG"; +- default: +- return "UNKNOWN"; +- } +-} +- +-static const char *ta_slider_as_str(unsigned int state) +-{ +- switch (state) { +- case TA_BEST_PERFORMANCE: +- return "PERFORMANCE"; +- case TA_BETTER_PERFORMANCE: +- return "BALANCED"; +- case TA_BEST_BATTERY: +- return "POWER_SAVER"; +- default: +- return "Unknown TA Slider State"; +- } +-} +- +-static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) ++u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) + { + switch (index) { + case 0 ... 1: +@@ -82,13 +29,15 @@ static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int + return 0; + } + } ++EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs); + + 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, "Slider State: %s\n", ++ amd_pmf_get_slider_position(in->ev_info.power_slider)); + dev_dbg(dev->dev, "Power Source: %s\n", amd_pmf_source_as_str(in->ev_info.power_source)); + dev_dbg(dev->dev, "Battery Percentage: %u\n", in->ev_info.bat_percentage); + dev_dbg(dev->dev, "Designed Battery Capacity: %u\n", in->ev_info.bat_design); +@@ -102,9 +51,10 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table * + dev_dbg(dev->dev, "LID State: %s\n", in->ev_info.lid_state ? "close" : "open"); + dev_dbg(dev->dev, "User Presence: %s\n", in->ev_info.user_present ? "Present" : "Away"); + dev_dbg(dev->dev, "Ambient Light: %d\n", in->ev_info.ambient_light); +- dev_dbg(dev->dev, "Platform type: %s\n", platform_type_as_str(in->ev_info.platform_type)); ++ dev_dbg(dev->dev, "Platform type: %s\n", ++ amd_pmf_get_platform_type(in->ev_info.platform_type)); + dev_dbg(dev->dev, "Laptop placement: %s\n", +- laptop_placement_as_str(in->ev_info.device_state)); ++ amd_pmf_get_laptop_placement(in->ev_info.device_state)); + 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)); +@@ -287,14 +237,14 @@ static int amd_pmf_get_slider_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_ + switch (dev->current_profile) { + case PLATFORM_PROFILE_PERFORMANCE: + case PLATFORM_PROFILE_BALANCED_PERFORMANCE: +- val = TA_BEST_PERFORMANCE; ++ val = AMD_PMF_TA_BEST_PERFORMANCE; + break; + case PLATFORM_PROFILE_BALANCED: +- val = TA_BETTER_PERFORMANCE; ++ val = AMD_PMF_TA_BETTER_PERFORMANCE; + break; + case PLATFORM_PROFILE_LOW_POWER: + case PLATFORM_PROFILE_QUIET: +- val = TA_BEST_BATTERY; ++ val = AMD_PMF_TA_BEST_BATTERY; + break; + default: + dev_err(dev->dev, "Unknown Platform Profile.\n"); +diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c +--- a/drivers/platform/x86/amd/pmf/tee-if.c ++++ b/drivers/platform/x86/amd/pmf/tee-if.c +@@ -8,7 +8,9 @@ + * Author: Shyam Sundar S K + */ + ++#include + #include ++#include + #include + #include + #include "pmf.h" +@@ -97,11 +99,18 @@ static int amd_pmf_get_bios_output_idx(u32 action_idx) + + static void amd_pmf_update_bios_output(struct amd_pmf_dev *pdev, struct ta_pmf_action *action) + { +- u32 bios_idx; ++ int bios_idx; ++ int ret; + + bios_idx = amd_pmf_get_bios_output_idx(action->action_index); ++ if (bios_idx < 0 || bios_idx >= ARRAY_SIZE(pdev->bios_output)) { ++ dev_warn(pdev->dev, "BIOS output index %d out of bounds\n", bios_idx); ++ return; ++ } + +- amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx); ++ ret = amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx); ++ if (!ret) ++ pdev->bios_output[bios_idx] = action->value; + } + + static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_result *out) +diff --git a/drivers/platform/x86/amd/pmf/util.c b/drivers/platform/x86/amd/pmf/util.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/amd/pmf/util.c +@@ -0,0 +1,153 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * AMD Platform Management Framework Util Layer ++ * ++ * Copyright (c) 2026, Advanced Micro Devices, Inc. ++ * All Rights Reserved. ++ * ++ * Authors: Shyam Sundar S K ++ * Sanket Goswami ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmf.h" ++ ++static struct amd_pmf_dev *pmf_dev_handle; ++static DEFINE_MUTEX(pmf_util_lock); ++ ++static int amd_pmf_populate_data(struct amd_pmf_dev *pdev, struct amd_pmf_info *info) ++{ ++ struct ta_pmf_shared_memory *ta_sm = NULL; ++ struct ta_pmf_enact_table *in = NULL; ++ int idx; ++ ++ if (!pdev || !info) ++ return -EINVAL; ++ ++ if (!pdev->shbuf) ++ return -EINVAL; ++ ++ ta_sm = pdev->shbuf; ++ in = &ta_sm->pmf_input.enact_table; ++ ++ /* Set size */ ++ info->size = sizeof(*info); ++ ++ /* PMF Feature support flags */ ++ if (is_apmf_func_supported(pdev, APMF_FUNC_AUTO_MODE)) ++ info->features_supported |= AMD_PMF_FEAT_AUTO_MODE; ++ if (is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) ++ info->features_supported |= AMD_PMF_FEAT_STATIC_POWER_SLIDER; ++ if (pdev->smart_pc_enabled) ++ info->features_supported |= AMD_PMF_FEAT_POLICY_BUILDER; ++ if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_AC)) ++ info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC; ++ if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_DC)) ++ info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC; ++ ++ /* Device States */ ++ info->platform_type = in->ev_info.platform_type; ++ info->laptop_placement = in->ev_info.device_state; ++ info->lid_state = in->ev_info.lid_state; ++ info->user_presence = in->ev_info.user_present; ++ info->slider_position = in->ev_info.power_slider; ++ ++ /* Thermal and Power Metrics */ ++ info->power_source = in->ev_info.power_source; ++ info->skin_temp = in->ev_info.skin_temperature; ++ info->gfx_busy = in->ev_info.gfx_busy; ++ info->ambient_light = in->ev_info.ambient_light; ++ info->avg_c0_residency = in->ev_info.avg_c0residency; ++ info->max_c0_residency = in->ev_info.max_c0residency; ++ info->socket_power = in->ev_info.socket_power; ++ ++ /* Custom BIOS input parameters */ ++ for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++) ++ info->bios_input[idx] = amd_pmf_get_ta_custom_bios_inputs(in, idx); ++ ++ /* BIOS output parameters */ ++ for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++) ++ info->bios_output[idx] = pdev->bios_output[idx]; ++ ++ return 0; ++} ++ ++static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) ++{ ++ struct amd_pmf_dev *pdev = filp->private_data; ++ void __user *argp = (void __user *)arg; ++ struct amd_pmf_info info = {}; ++ size_t copy_size; ++ __u64 user_size; ++ int ret; ++ ++ if (cmd != IOCTL_AMD_PMF_POPULATE_DATA) ++ return -ENOTTY; ++ ++ /* First read just the size field from userspace */ ++ if (copy_from_user(&user_size, argp, sizeof(user_size))) ++ return -EFAULT; ++ ++ guard(mutex)(&pmf_util_lock); ++ ret = amd_pmf_populate_data(pdev, &info); ++ if (ret) ++ return ret; ++ ++ copy_size = min_t(size_t, user_size, sizeof(info)); ++ ++ /* Set actual size being copied */ ++ info.size = copy_size; ++ ++ if (copy_to_user(argp, &info, copy_size)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static int amd_pmf_open(struct inode *inode, struct file *filp) ++{ ++ guard(mutex)(&pmf_util_lock); ++ if (!pmf_dev_handle) ++ return -ENODEV; ++ ++ filp->private_data = pmf_dev_handle; ++ return 0; ++} ++ ++static const struct file_operations pmf_if_ops = { ++ .owner = THIS_MODULE, ++ .open = amd_pmf_open, ++ .unlocked_ioctl = amd_pmf_set_ioctl, ++}; ++ ++static struct miscdevice amd_pmf_util_if = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = "amdpmf_interface", ++ .fops = &pmf_if_ops, ++}; ++ ++int amd_pmf_cdev_register(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ guard(mutex)(&pmf_util_lock); ++ pmf_dev_handle = dev; ++ ret = misc_register(&amd_pmf_util_if); ++ if (ret) ++ pmf_dev_handle = NULL; ++ ++ return ret; ++} ++ ++void amd_pmf_cdev_unregister(void) ++{ ++ guard(mutex)(&pmf_util_lock); ++ if (pmf_dev_handle) ++ misc_deregister(&amd_pmf_util_if); ++ pmf_dev_handle = NULL; ++} +diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c +--- a/drivers/platform/x86/asus-armoury.c ++++ b/drivers/platform/x86/asus-armoury.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -93,6 +94,8 @@ struct asus_armoury_priv { + + u32 mini_led_dev_id; + u32 gpu_mux_dev_id; ++ ++ bool requires_fan_curve; + }; + + static struct asus_armoury_priv asus_armoury = { +@@ -216,6 +219,22 @@ static int armoury_set_devstate(struct kobj_attribute *attr, + u32 result; + int err; + ++ /* On some models, PPT changes require an active fan curve */ ++ if (asus_armoury.requires_fan_curve) { ++ switch (dev_id) { ++ case ASUS_WMI_DEVID_PPT_PL1_SPL: ++ case ASUS_WMI_DEVID_PPT_PL2_SPPT: ++ case ASUS_WMI_DEVID_PPT_PL3_FPPT: ++ case ASUS_WMI_DEVID_PPT_APU_SPPT: ++ case ASUS_WMI_DEVID_PPT_PLAT_SPPT: ++ if (!asus_wmi_custom_fan_curve_is_enabled()) { ++ pr_warn_once("PPT change requires an active fan curve on this model. Enable a custom fan curve first.\n"); ++ return -EBUSY; ++ } ++ break; ++ } ++ } ++ + /* + * Prevent developers from bricking devices or issuing dangerous + * commands that can be difficult or impossible to recover from. +@@ -989,10 +1008,11 @@ static int asus_fw_attr_add(void) + /* Init / exit ****************************************************************/ + + /* Set up the min/max and defaults for ROG tunables */ +-static void init_rog_tunables(void) ++static int init_rog_tunables(void) + { + const struct power_limits *ac_limits, *dc_limits; +- struct rog_tunables *ac_rog_tunables = NULL, *dc_rog_tunables = NULL; ++ struct rog_tunables *ac_rog_tunables __free(kfree) = NULL; ++ struct rog_tunables *dc_rog_tunables __free(kfree) = NULL; + const struct power_data *power_data; + const struct dmi_system_id *dmi_id; + +@@ -1000,24 +1020,25 @@ static void init_rog_tunables(void) + dmi_id = dmi_first_match(power_limits); + if (!dmi_id) { + pr_warn("No matching power limits found for this system\n"); +- return; ++ return 0; + } + + /* Get the power data for this system */ + power_data = dmi_id->driver_data; + if (!power_data) { + pr_info("No power data available for this system\n"); +- return; ++ return 0; + } + ++ asus_armoury.requires_fan_curve = power_data->requires_fan_curve; ++ + /* Initialize AC power tunables */ + ac_limits = power_data->ac_data; + if (ac_limits) { +- ac_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]); ++ ac_rog_tunables = kzalloc_obj(*ac_rog_tunables); + if (!ac_rog_tunables) +- goto err_nomem; ++ return -ENOMEM; + +- asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = ac_rog_tunables; + ac_rog_tunables->power_limits = ac_limits; + + /* Set initial AC values */ +@@ -1060,13 +1081,10 @@ static void init_rog_tunables(void) + /* Initialize DC power tunables */ + dc_limits = power_data->dc_data; + if (dc_limits) { +- dc_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]); +- if (!dc_rog_tunables) { +- kfree(ac_rog_tunables); +- goto err_nomem; +- } ++ dc_rog_tunables = kzalloc_obj(*dc_rog_tunables); ++ if (!dc_rog_tunables) ++ return -ENOMEM; + +- asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = dc_rog_tunables; + dc_rog_tunables->power_limits = dc_limits; + + /* Set initial DC values */ +@@ -1106,15 +1124,16 @@ static void init_rog_tunables(void) + pr_debug("No DC PPT limits defined\n"); + } + +- return; ++ asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = no_free_ptr(ac_rog_tunables); ++ asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = no_free_ptr(dc_rog_tunables); + +-err_nomem: +- pr_err("Failed to allocate memory for tunables\n"); ++ return 0; + } + + static int __init asus_fw_init(void) + { + char *wmi_uid; ++ int err; + + wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID); + if (!wmi_uid) +@@ -1127,10 +1146,21 @@ static int __init asus_fw_init(void) + if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI)) + return -ENODEV; + +- init_rog_tunables(); ++ err = init_rog_tunables(); ++ if (err) ++ return err; + + /* Must always be last step to ensure data is available */ +- return asus_fw_attr_add(); ++ err = asus_fw_attr_add(); ++ if (err) ++ goto err_free_tunables; ++ ++ return 0; ++ ++err_free_tunables: ++ kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]); ++ kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]); ++ return err; + } + + static void __exit asus_fw_exit(void) +diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h +--- a/drivers/platform/x86/asus-armoury.h ++++ b/drivers/platform/x86/asus-armoury.h +@@ -369,6 +369,38 @@ static const struct dmi_system_id power_limits[] = { + }, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA401KM"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .nv_dynamic_boost_max = 15, ++ .nv_dynamic_boost_min = 5, ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .nv_tgp_max = 95, ++ .nv_tgp_min = 55, ++ .ppt_pl1_spl_max = 80, ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl2_sppt_max = 80, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl3_fppt_max = 80, ++ .ppt_pl3_fppt_min = 35, ++ }, ++ .dc_data = &(struct power_limits) { ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .ppt_pl1_spl_max = 35, ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl2_sppt_max = 44, ++ .ppt_pl2_sppt_min = 31, ++ .ppt_pl3_fppt_max = 65, ++ .ppt_pl3_fppt_min = 45, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA401UM"), +@@ -754,6 +786,40 @@ static const struct dmi_system_id power_limits[] = { + }, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA608WV"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .nv_dynamic_boost_max = 25, ++ .nv_dynamic_boost_min = 5, ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .nv_tgp_max = 115, ++ .nv_tgp_min = 55, ++ .ppt_pl1_spl_max = 90, ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl2_sppt_max = 90, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl3_fppt_max = 90, ++ .ppt_pl3_fppt_min = 35, ++ }, ++ .dc_data = &(struct power_limits) { ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .ppt_pl1_spl_def = 45, ++ .ppt_pl1_spl_max = 65, ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl2_sppt_def = 54, ++ .ppt_pl2_sppt_max = 65, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl3_fppt_max = 65, ++ .ppt_pl3_fppt_min = 35, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA617NS"), +@@ -909,6 +975,32 @@ static const struct dmi_system_id power_limits[] = { + .requires_fan_curve = true, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FX517ZR"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_max = 85, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 135, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 45, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_max = 60, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FX607VU"), +@@ -2079,6 +2171,35 @@ static const struct dmi_system_id power_limits[] = { + .requires_fan_curve = true, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "G635LX"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_def = 140, ++ .ppt_pl1_spl_max = 175, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 175, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ .nv_tgp_min = 80, ++ .nv_tgp_max = 150, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 55, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 70, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G713PV"), +@@ -2322,6 +2443,35 @@ static const struct dmi_system_id power_limits[] = { + }, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "HN7306EA"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 35, ++ .ppt_pl1_spl_def = 60, ++ .ppt_pl1_spl_max = 85, ++ .ppt_pl2_sppt_min = 40, ++ .ppt_pl2_sppt_def = 70, ++ .ppt_pl2_sppt_max = 95, ++ .ppt_pl3_fppt_min = 50, ++ .ppt_pl3_fppt_def = 85, ++ .ppt_pl3_fppt_max = 115, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 35, ++ .ppt_pl1_spl_def = 45, ++ .ppt_pl1_spl_max = 60, ++ .ppt_pl2_sppt_min = 40, ++ .ppt_pl2_sppt_def = 55, ++ .ppt_pl2_sppt_max = 70, ++ .ppt_pl3_fppt_min = 50, ++ .ppt_pl3_fppt_def = 65, ++ .ppt_pl3_fppt_max = 85, ++ }, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "RC71"), +diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c +--- a/drivers/platform/x86/asus-laptop.c ++++ b/drivers/platform/x86/asus-laptop.c +@@ -42,8 +42,6 @@ + #define ASUS_LAPTOP_VERSION "0.42" + + #define ASUS_LAPTOP_NAME "Asus Laptop Support" +-#define ASUS_LAPTOP_CLASS "hotkey" +-#define ASUS_LAPTOP_DEVICE_NAME "Hotkey" + #define ASUS_LAPTOP_FILE KBUILD_MODNAME + #define ASUS_LAPTOP_PREFIX "\\_SB.ATKD." + +@@ -1524,9 +1522,8 @@ static void asus_acpi_notify(acpi_handle handle, u32 event, void *data) + + /* TODO Find a better way to handle events count. */ + count = asus->event_count[event % 128]++; +- acpi_bus_generate_netlink_event(asus->device->pnp.device_class, +- dev_name(&asus->device->dev), event, +- count); ++ acpi_bus_generate_netlink_event("hotkey", dev_name(&asus->device->dev), ++ event, count); + + if (event >= ATKD_BRNUP_MIN && event <= ATKD_BRNUP_MAX) + event = ATKD_BRNUP; +@@ -1840,8 +1837,6 @@ static int asus_acpi_probe(struct platform_device *pdev) + if (!asus) + return -ENOMEM; + asus->handle = device->handle; +- strscpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME); +- strscpy(acpi_device_class(device), ASUS_LAPTOP_CLASS); + asus->device = device; + + asus_dmi_check(); +diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c +--- a/drivers/platform/x86/asus-nb-wmi.c ++++ b/drivers/platform/x86/asus-nb-wmi.c +@@ -632,6 +632,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = { + { KE_KEY, 0x86, { KEY_PROG1 } }, /* MyASUS Key */ + { KE_KEY, 0x88, { KEY_RFKILL } }, /* Radio Toggle Key */ + { KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */ ++ { KE_KEY, 0x8B, { KEY_PROG3 } }, /* ProArt key (PX13/HN7306) */ + { KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */ + { KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */ + { KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */ +diff --git a/drivers/platform/x86/asus-wireless.c b/drivers/platform/x86/asus-wireless.c +--- a/drivers/platform/x86/asus-wireless.c ++++ b/drivers/platform/x86/asus-wireless.c +@@ -132,6 +132,10 @@ static int asus_wireless_probe(struct platform_device *pdev) + const struct acpi_device_id *id; + int err; + ++ id = acpi_match_acpi_device(device_ids, adev); ++ if (!id) ++ return -ENODEV; ++ + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; +@@ -139,6 +143,7 @@ static int asus_wireless_probe(struct platform_device *pdev) + platform_set_drvdata(pdev, data); + + data->adev = adev; ++ data->hswc_params = (const struct hswc_params *)id->driver_data; + + data->idev = devm_input_allocate_device(&pdev->dev); + if (!data->idev) +@@ -153,12 +158,6 @@ static int asus_wireless_probe(struct platform_device *pdev) + if (err) + return err; + +- id = acpi_match_acpi_device(device_ids, adev); +- if (!id) +- return 0; +- +- data->hswc_params = (const struct hswc_params *)id->driver_data; +- + data->wq = create_singlethread_workqueue("asus_wireless_workqueue"); + if (!data->wq) + return -ENOMEM; +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -4073,6 +4073,30 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus) + return 0; + } + ++/* ++ * Returns true if at least one custom fan curve is active ++ * ++ * Used by asus-armoury to check if PPT writes will be accepted by the BIOS ++ * on models that require an active fan curve for TDP changes. ++ */ ++bool asus_wmi_custom_fan_curve_is_enabled(void) ++{ ++ struct fan_curve_data *curves; ++ struct asus_wmi *asus; ++ ++ guard(spinlock_irqsave)(&asus_ref.lock); ++ asus = asus_ref.asus; ++ if (!asus) ++ return false; ++ ++ curves = asus->custom_fan_curves; ++ ++ return (asus->cpu_fan_curve_available && curves[FAN_CURVE_DEV_CPU].enabled) || ++ (asus->gpu_fan_curve_available && curves[FAN_CURVE_DEV_GPU].enabled) || ++ (asus->mid_fan_curve_available && curves[FAN_CURVE_DEV_MID].enabled); ++} ++EXPORT_SYMBOL_NS_GPL(asus_wmi_custom_fan_curve_is_enabled, "ASUS_WMI"); ++ + /* Throttle thermal policy ****************************************************/ + static int throttle_thermal_policy_write(struct asus_wmi *asus) + { +@@ -5244,20 +5268,20 @@ static int asus_wmi_add(struct platform_device *pdev) + return 0; + + fail_wmi_handler: ++ asus_screenpad_exit(asus); ++fail_screenpad: + asus_wmi_backlight_exit(asus); + fail_backlight: + asus_wmi_rfkill_exit(asus); +-fail_screenpad: +- asus_screenpad_exit(asus); + fail_rfkill: + asus_wmi_led_exit(asus); + fail_leds: ++fail_custom_fan_curve: + fail_hwmon: + asus_wmi_input_exit(asus); + fail_input: + asus_wmi_sysfs_exit(asus->platform_device); + fail_sysfs: +-fail_custom_fan_curve: + fail_platform_profile_setup: + fail_fan_boost_mode: + fail_platform: +diff --git a/drivers/platform/x86/dell/dell-smbios-base.c b/drivers/platform/x86/dell/dell-smbios-base.c +--- a/drivers/platform/x86/dell/dell-smbios-base.c ++++ b/drivers/platform/x86/dell/dell-smbios-base.c +@@ -40,7 +40,7 @@ struct smbios_device { + struct list_head list; + struct device *device; + int priority; +- int (*call_fn)(struct calling_interface_buffer *arg); ++ smbios_callback_fn_t call_fn; + }; + + struct smbios_call { +@@ -146,7 +146,7 @@ int dell_smbios_error(int value) + } + EXPORT_SYMBOL_GPL(dell_smbios_error); + +-int dell_smbios_register_device(struct device *d, int priority, void *call_fn) ++int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn) + { + struct smbios_device *priv; + +@@ -312,7 +312,7 @@ int dell_smbios_call(struct calling_interface_buffer *buffer) + goto out_smbios_call; + } + +- ret = selected->call_fn(buffer); ++ ret = selected->call_fn(selected->device, buffer); + + out_smbios_call: + mutex_unlock(&smbios_mutex); +diff --git a/drivers/platform/x86/dell/dell-smbios-smm.c b/drivers/platform/x86/dell/dell-smbios-smm.c +--- a/drivers/platform/x86/dell/dell-smbios-smm.c ++++ b/drivers/platform/x86/dell/dell-smbios-smm.c +@@ -49,7 +49,7 @@ static void find_cmd_address(const struct dmi_header *dm, void *dummy) + } + } + +-static int dell_smbios_smm_call(struct calling_interface_buffer *input) ++static int dell_smbios_smm_call(struct device *dev, struct calling_interface_buffer *input) + { + struct smi_cmd command; + size_t size; +@@ -70,7 +70,7 @@ static int dell_smbios_smm_call(struct calling_interface_buffer *input) + } + + /* When enabled this indicates that SMM won't work */ +-static bool test_wsmt_enabled(void) ++static bool test_wsmt_enabled(struct device *dev) + { + struct calling_interface_token *wsmt; + +@@ -88,7 +88,7 @@ static bool test_wsmt_enabled(void) + memset(buffer, 0, sizeof(struct calling_interface_buffer)); + buffer->input[0] = wsmt->location; + buffer->output[0] = 99; +- dell_smbios_smm_call(buffer); ++ dell_smbios_smm_call(dev, buffer); + if (buffer->output[0] == 99) + return true; + +@@ -109,7 +109,7 @@ int init_dell_smbios_smm(void) + + dmi_walk(find_cmd_address, NULL); + +- if (test_wsmt_enabled()) { ++ if (test_wsmt_enabled(&platform_device->dev)) { + pr_debug("Disabling due to WSMT enabled\n"); + ret = -ENODEV; + goto fail_wsmt; +diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c +--- a/drivers/platform/x86/dell/dell-smbios-wmi.c ++++ b/drivers/platform/x86/dell/dell-smbios-wmi.c +@@ -6,21 +6,20 @@ + */ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + ++#include + #include + #include + #include +-#include + #include + #include + #include ++#include + #include + #include + #include + #include "dell-smbios.h" + #include "dell-wmi-descriptor.h" + +-static DEFINE_MUTEX(call_mutex); +-static DEFINE_MUTEX(list_mutex); + static int wmi_supported; + + struct misc_bios_flags_structure { +@@ -32,21 +31,16 @@ struct misc_bios_flags_structure { + #define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" + + struct wmi_smbios_priv { ++ struct mutex call_lock; /* Protects the content of the SMBIOS buffer */ + struct dell_wmi_smbios_buffer *buf; +- struct list_head list; + struct wmi_device *wdev; + struct device *child; + u64 req_buf_size; + struct miscdevice char_dev; + }; +-static LIST_HEAD(wmi_list); + +-static inline struct wmi_smbios_priv *get_first_smbios_priv(void) +-{ +- return list_first_entry_or_null(&wmi_list, +- struct wmi_smbios_priv, +- list); +-} ++static DECLARE_RWSEM(chardev_lock); /* Protects chardev_priv */ ++static struct wmi_smbios_priv *chardev_priv; + + static int run_smbios_call(struct wmi_device *wdev) + { +@@ -80,50 +74,36 @@ static int run_smbios_call(struct wmi_device *wdev) + return 0; + } + +-static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) ++static int dell_smbios_wmi_call(struct device *dev, struct calling_interface_buffer *buffer) + { +- struct wmi_smbios_priv *priv; ++ struct wmi_smbios_priv *priv = dev_get_drvdata(dev); + size_t difference; + size_t size; + int ret; + +- mutex_lock(&call_mutex); +- priv = get_first_smbios_priv(); +- if (!priv) { +- ret = -ENODEV; +- goto out_wmi_call; +- } +- + size = sizeof(struct calling_interface_buffer); + difference = priv->req_buf_size - sizeof(u64) - size; + ++ guard(mutex)(&priv->call_lock); ++ + memset(&priv->buf->ext, 0, difference); + memcpy(&priv->buf->std, buffer, size); + ret = run_smbios_call(priv->wdev); + memcpy(buffer, &priv->buf->std, size); +-out_wmi_call: +- mutex_unlock(&call_mutex); + + return ret; + } + +-static int dell_smbios_wmi_open(struct inode *inode, struct file *filp) +-{ +- struct wmi_smbios_priv *priv; +- +- priv = container_of(filp->private_data, struct wmi_smbios_priv, char_dev); +- filp->private_data = priv; +- +- return nonseekable_open(inode, filp); +-} +- + static ssize_t dell_smbios_wmi_read(struct file *filp, char __user *buffer, size_t length, + loff_t *offset) + { +- struct wmi_smbios_priv *priv = filp->private_data; ++ guard(rwsem_read)(&chardev_lock); + +- return simple_read_from_buffer(buffer, length, offset, &priv->req_buf_size, +- sizeof(priv->req_buf_size)); ++ if (!chardev_priv) ++ return -ENODEV; ++ ++ return simple_read_from_buffer(buffer, length, offset, &chardev_priv->req_buf_size, ++ sizeof(chardev_priv->req_buf_size)); + } + + static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv, +@@ -167,22 +147,22 @@ static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv, + static long dell_smbios_wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) + { + struct dell_wmi_smbios_buffer __user *input = (struct dell_wmi_smbios_buffer __user *)arg; +- struct wmi_smbios_priv *priv = filp->private_data; +- long ret; + + if (cmd != DELL_WMI_SMBIOS_CMD) + return -ENOIOCTLCMD; + +- mutex_lock(&call_mutex); +- ret = dell_smbios_wmi_do_ioctl(priv, input); +- mutex_unlock(&call_mutex); ++ guard(rwsem_read)(&chardev_lock); + +- return ret; ++ if (!chardev_priv) ++ return -ENODEV; ++ ++ guard(mutex)(&chardev_priv->call_lock); ++ ++ return dell_smbios_wmi_do_ioctl(chardev_priv, input); + } + + static const struct file_operations dell_smbios_wmi_fops = { + .owner = THIS_MODULE, +- .open = dell_smbios_wmi_open, + .read = dell_smbios_wmi_read, + .unlocked_ioctl = dell_smbios_wmi_ioctl, + .compat_ioctl = compat_ptr_ioctl, +@@ -195,10 +175,29 @@ static void dell_smbios_wmi_unregister_chardev(void *data) + misc_deregister(char_dev); + } + ++static void dell_smbios_wmi_clear_chardev(void *data) ++{ ++ guard(rwsem_write)(&chardev_lock); ++ ++ chardev_priv = NULL; ++} ++ + static int dell_smbios_wmi_register_chardev(struct wmi_smbios_priv *priv) + { + int ret; + ++ scoped_guard(rwsem_write, &chardev_lock) { ++ /* We can only have a single chardev at a time */ ++ if (chardev_priv) ++ return -EBUSY; ++ ++ chardev_priv = priv; ++ } ++ ++ ret = devm_add_action_or_reset(&priv->wdev->dev, dell_smbios_wmi_clear_chardev, NULL); ++ if (ret < 0) ++ return ret; ++ + priv->char_dev.minor = MISC_DYNAMIC_MINOR; + priv->char_dev.name = "wmi/dell-smbios"; + priv->char_dev.fops = &dell_smbios_wmi_fops; +@@ -254,31 +253,20 @@ static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context) + if (!priv->buf) + return -ENOMEM; + ++ ret = devm_mutex_init(&wdev->dev, &priv->call_lock); ++ if (ret) ++ return ret; ++ + ret = dell_smbios_wmi_register_chardev(priv); + if (ret) + return ret; + +- ret = dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call); +- if (ret) +- return ret; +- +- mutex_lock(&list_mutex); +- list_add_tail(&priv->list, &wmi_list); +- mutex_unlock(&list_mutex); +- +- return 0; ++ return dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call); + } + + static void dell_smbios_wmi_remove(struct wmi_device *wdev) + { +- struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev); +- +- mutex_lock(&call_mutex); +- mutex_lock(&list_mutex); +- list_del(&priv->list); +- mutex_unlock(&list_mutex); + dell_smbios_unregister_device(&wdev->dev); +- mutex_unlock(&call_mutex); + } + + static const struct wmi_device_id dell_smbios_wmi_id_table[] = { +@@ -316,6 +304,7 @@ static struct wmi_driver dell_smbios_wmi_driver = { + .probe = dell_smbios_wmi_probe, + .remove = dell_smbios_wmi_remove, + .id_table = dell_smbios_wmi_id_table, ++ .no_singleton = true, + }; + + int init_dell_smbios_wmi(void) +diff --git a/drivers/platform/x86/dell/dell-smbios.h b/drivers/platform/x86/dell/dell-smbios.h +--- a/drivers/platform/x86/dell/dell-smbios.h ++++ b/drivers/platform/x86/dell/dell-smbios.h +@@ -47,6 +47,8 @@ + + struct notifier_block; + ++typedef int (*smbios_callback_fn_t)(struct device *dev, struct calling_interface_buffer *buffer); ++ + struct calling_interface_token { + u16 tokenID; + u16 location; +@@ -64,7 +66,7 @@ struct calling_interface_structure { + struct calling_interface_token tokens[]; + } __packed; + +-int dell_smbios_register_device(struct device *d, int priority, void *call_fn); ++int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn); + void dell_smbios_unregister_device(struct device *d); + + int dell_smbios_error(int value); +diff --git a/drivers/platform/x86/dell/dell-wmi-base.c b/drivers/platform/x86/dell/dell-wmi-base.c +--- a/drivers/platform/x86/dell/dell-wmi-base.c ++++ b/drivers/platform/x86/dell/dell-wmi-base.c +@@ -456,7 +456,7 @@ static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, __l + key++; + used = 1; + } else if (type == 0x0012 && code == 0x000d && remaining > 0) { +- value = (le16_to_cpu(buffer[2]) == 2); ++ value = (le16_to_cpu(buffer[0]) == 2); + used = 1; + } + +@@ -843,9 +843,22 @@ static int __init dell_wmi_init(void) + + err = dell_privacy_register_driver(); + if (err) +- return err; ++ goto out_smbios; + +- return wmi_driver_register(&dell_wmi_driver); ++ err = wmi_driver_register(&dell_wmi_driver); ++ if (err) ++ goto out_privacy; ++ ++ return 0; ++ ++out_privacy: ++ dell_privacy_unregister_driver(); ++ ++out_smbios: ++ if (wmi_requires_smbios_request) ++ dell_wmi_events_set_enabled(false); ++ ++ return err; + } + late_initcall(dell_wmi_init); + +diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c +--- a/drivers/platform/x86/dell/dell-wmi-ddv.c ++++ b/drivers/platform/x86/dell/dell-wmi-ddv.c +@@ -196,40 +196,30 @@ static int dell_wmi_ddv_query_integer(struct wmi_device *wdev, enum dell_ddv_met + static int dell_wmi_ddv_query_buffer(struct wmi_device *wdev, enum dell_ddv_method method, + u32 arg, struct dell_wmi_buffer **result) + { +- struct dell_wmi_buffer *buffer; + struct wmi_buffer output; + size_t buffer_size; + int ret; + +- ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(*buffer)); ++ ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(struct dell_wmi_buffer)); + if (ret < 0) + return ret; + +- buffer = output.data; +- if (!le32_to_cpu(buffer->raw_size)) { +- ret = -ENODATA; ++ struct dell_wmi_buffer *buffer __free(kfree) = output.data; + +- goto err_free; +- } ++ if (!le32_to_cpu(buffer->raw_size)) ++ return -ENODATA; + + buffer_size = struct_size(buffer, raw_data, le32_to_cpu(buffer->raw_size)); + if (buffer_size > output.length) { + dev_warn(&wdev->dev, + FW_WARN "Dell WMI buffer size (%zu) exceeds WMI buffer size (%zu)\n", + buffer_size, output.length); +- ret = -EMSGSIZE; +- +- goto err_free; ++ return -EMSGSIZE; + } + +- *result = buffer; ++ *result = no_free_ptr(buffer); + + return 0; +- +-err_free: +- kfree(output.data); +- +- return ret; + } + + static ssize_t dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_method method, +diff --git a/drivers/platform/x86/dell/dell-wmi-privacy.c b/drivers/platform/x86/dell/dell-wmi-privacy.c +--- a/drivers/platform/x86/dell/dell-wmi-privacy.c ++++ b/drivers/platform/x86/dell/dell-wmi-privacy.c +@@ -92,11 +92,11 @@ bool dell_privacy_has_mic_mute(void) + { + struct privacy_wmi_data *priv; + +- mutex_lock(&list_mutex); ++ guard(mutex)(&list_mutex); ++ + priv = list_first_entry_or_null(&wmi_list, + struct privacy_wmi_data, + list); +- mutex_unlock(&list_mutex); + + return priv && (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)); + } +diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h +--- a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h ++++ b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h +@@ -107,7 +107,7 @@ enum { + static int get_##type##_instance_id(struct kobject *kobj) \ + { \ + int i; \ +- for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \ ++ for (i = 0; i < wmi_priv.type##_instances_count; i++) { \ + if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\ + return i; \ + } \ +diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c +--- a/drivers/platform/x86/eeepc-laptop.c ++++ b/drivers/platform/x86/eeepc-laptop.c +@@ -34,8 +34,6 @@ + #define EEEPC_LAPTOP_NAME "Eee PC Hotkey Driver" + #define EEEPC_LAPTOP_FILE "eeepc" + +-#define EEEPC_ACPI_CLASS "hotkey" +-#define EEEPC_ACPI_DEVICE_NAME "Hotkey" + #define EEEPC_ACPI_HID "ASUS010" + + MODULE_AUTHOR("Corentin Chary, Eric Cooper"); +@@ -1214,8 +1212,7 @@ static void eeepc_acpi_notify(acpi_handle handle, u32 event, void *data) + if (event > ACPI_MAX_SYS_NOTIFY) + return; + count = eeepc->event_count[event % 128]++; +- acpi_bus_generate_netlink_event(device->pnp.device_class, +- dev_name(&device->dev), event, ++ acpi_bus_generate_netlink_event("hotkey", dev_name(&device->dev), event, + count); + + /* Brightness events are special */ +@@ -1376,8 +1373,6 @@ static int eeepc_acpi_probe(struct platform_device *pdev) + if (!eeepc) + return -ENOMEM; + eeepc->handle = device->handle; +- strscpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME); +- strscpy(acpi_device_class(device), EEEPC_ACPI_CLASS); + eeepc->device = device; + + platform_set_drvdata(pdev, eeepc); +diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c +--- a/drivers/platform/x86/fujitsu-laptop.c ++++ b/drivers/platform/x86/fujitsu-laptop.c +@@ -56,7 +56,6 @@ + + #define FUJITSU_LCD_N_LEVELS 8 + +-#define ACPI_FUJITSU_CLASS "fujitsu" + #define ACPI_FUJITSU_BL_HID "FUJ02B1" + #define ACPI_FUJITSU_BL_DRIVER_NAME "Fujitsu laptop FUJ02B1 ACPI brightness driver" + #define ACPI_FUJITSU_BL_DEVICE_NAME "Fujitsu FUJ02B1" +@@ -466,7 +465,7 @@ static int acpi_fujitsu_bl_input_setup(struct device *dev) + snprintf(priv->phys, sizeof(priv->phys), "%s/video/input0", + acpi_device_hid(device)); + +- priv->input->name = acpi_device_name(device); ++ priv->input->name = ACPI_FUJITSU_BL_DEVICE_NAME; + priv->input->phys = priv->phys; + priv->input->id.bustype = BUS_HOST; + priv->input->id.product = 0x06; +@@ -546,13 +545,11 @@ static int acpi_fujitsu_bl_probe(struct platform_device *pdev) + return -ENOMEM; + + fujitsu_bl = priv; +- strscpy(acpi_device_name(device), ACPI_FUJITSU_BL_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); + + platform_set_drvdata(pdev, priv); + +- pr_info("ACPI: %s [%s]\n", +- acpi_device_name(device), acpi_device_bid(device)); ++ pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_BL_DEVICE_NAME, ++ acpi_device_bid(device)); + + if (get_max_brightness(&pdev->dev) <= 0) + priv->max_brightness = FUJITSU_LCD_N_LEVELS; +@@ -681,7 +678,7 @@ static int acpi_fujitsu_laptop_input_setup(struct device *dev) + snprintf(priv->phys, sizeof(priv->phys), "%s/input0", + acpi_device_hid(device)); + +- priv->input->name = acpi_device_name(device); ++ priv->input->name = ACPI_FUJITSU_LAPTOP_DEVICE_NAME; + priv->input->phys = priv->phys; + priv->input->id.bustype = BUS_HOST; + +@@ -1012,9 +1009,6 @@ static int acpi_fujitsu_laptop_probe(struct platform_device *pdev) + WARN_ONCE(fext, "More than one FUJ02E3 ACPI device was found. Driver may not work as intended."); + fext = &pdev->dev; + +- strscpy(acpi_device_name(device), ACPI_FUJITSU_LAPTOP_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); +- + platform_set_drvdata(pdev, priv); + + /* kfifo */ +@@ -1024,8 +1018,8 @@ static int acpi_fujitsu_laptop_probe(struct platform_device *pdev) + if (ret) + return ret; + +- pr_info("ACPI: %s [%s]\n", +- acpi_device_name(device), acpi_device_bid(device)); ++ pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_LAPTOP_DEVICE_NAME, ++ acpi_device_bid(device)); + + while (call_fext_func(fext, FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0 && + i++ < MAX_HOTKEY_RINGBUFFER_SIZE) +diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujitsu-tablet.c +--- a/drivers/platform/x86/fujitsu-tablet.c ++++ b/drivers/platform/x86/fujitsu-tablet.c +@@ -22,8 +22,6 @@ + + #define MODULENAME "fujitsu-tablet" + +-#define ACPI_FUJITSU_CLASS "fujitsu" +- + #define INVERT_TABLET_MODE_BIT 0x01 + #define INVERT_DOCK_STATE_BIT 0x02 + #define FORCE_TABLET_MODE_IF_UNDOCK 0x04 +@@ -160,6 +158,7 @@ static struct { + struct fujitsu_config config; + unsigned long prev_keymask; + ++ char name[17]; + char phys[21]; + + int irq; +@@ -458,14 +457,10 @@ static int acpi_fujitsu_probe(struct platform_device *pdev) + if (ACPI_FAILURE(status) || !fujitsu.irq || !fujitsu.io_base) + return -ENODEV; + +- sprintf(acpi_device_name(adev), "Fujitsu %s", acpi_device_hid(adev)); +- sprintf(acpi_device_class(adev), "%s", ACPI_FUJITSU_CLASS); ++ scnprintf(fujitsu.name, sizeof(fujitsu.name), "Fujitsu %s", acpi_device_hid(adev)); ++ scnprintf(fujitsu.phys, sizeof(fujitsu.phys), "%s/input0", acpi_device_hid(adev)); + +- snprintf(fujitsu.phys, sizeof(fujitsu.phys), +- "%s/input0", acpi_device_hid(adev)); +- +- error = input_fujitsu_setup(&pdev->dev, +- acpi_device_name(adev), fujitsu.phys); ++ error = input_fujitsu_setup(&pdev->dev, fujitsu.name, fujitsu.phys); + if (error) + return error; + +diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +--- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c ++++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +@@ -353,6 +353,11 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor + case PSWD_ENCODINGS: + size = min_t(u32, password_data->encodings_size, MAX_ENCODINGS_SIZE); + for (pos_values = 0; pos_values < size; pos_values++) { ++ if (elem + pos_values >= password_obj_count) { ++ pr_err("Error elem-objects package is too small\n"); ++ return -EINVAL; ++ } ++ + ret = hp_convert_hexstr_to_str(password_obj[elem + pos_values].string.pointer, + password_obj[elem + pos_values].string.length, + &str_value, &value_len); +diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c +--- a/drivers/platform/x86/hp/hp-wmi.c ++++ b/drivers/platform/x86/hp/hp-wmi.c +@@ -14,6 +14,8 @@ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + + #include ++#include ++#include + #include + #include + #include +@@ -23,6 +25,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -58,6 +61,12 @@ enum hp_ec_offsets { + #define HP_FAN_SPEED_AUTOMATIC 0x00 + #define HP_POWER_LIMIT_DEFAULT 0x00 + #define HP_POWER_LIMIT_NO_CHANGE 0xFF ++#define HPWMI_MUX_MODE_UMA BIT(0) ++#define HPWMI_MUX_MODE_HYBRID BIT(1) ++#define HPWMI_MUX_MODE_DISCRETE BIT(2) ++#define HPWMI_MUX_MODE_OPTIMUS BIT(3) ++#define HPWMI_MUX_MODE_MASK GENMASK(6, 0) ++#define HPWMI_MUX_LEGACY_MASK (HPWMI_MUX_MODE_HYBRID | HPWMI_MUX_MODE_DISCRETE) + + #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required + +@@ -133,11 +142,52 @@ static const struct thermal_profile_params omen_v1_no_ec_thermal_params = { + .ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET, + }; + +-/* +- * A generic pointer for the currently-active board's thermal profile +- * parameters. +- */ +-static struct thermal_profile_params *active_thermal_profile_params; ++struct hp_wmi_fan_profile_params { ++ int (*get_fan_speed)(int fan); ++ bool fan_table; ++}; ++ ++struct hp_wmi_board_params { ++ const struct thermal_profile_params *thermal_profile; ++ const struct hp_wmi_fan_profile_params *fan_profile; ++}; ++ ++static int hp_wmi_get_fan_speed_victus_s(int fan); ++ ++static const struct hp_wmi_fan_profile_params victus_s_fan_profile_params = { ++ .get_fan_speed = hp_wmi_get_fan_speed_victus_s, ++ .fan_table = true, ++}; ++ ++static const struct hp_wmi_board_params victus_s_board_params = { ++ .thermal_profile = &victus_s_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, ++}; ++ ++static const struct hp_wmi_board_params omen_v1_board_params = { ++ .thermal_profile = &omen_v1_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, ++}; ++ ++static const struct hp_wmi_board_params omen_v1_legacy_board_params = { ++ .thermal_profile = &omen_v1_legacy_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, ++}; ++ ++static const struct hp_wmi_board_params omen_v1_no_ec_board_params = { ++ .thermal_profile = &omen_v1_no_ec_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, ++}; ++ ++static const struct hp_wmi_board_params *active_board_params; ++ ++static const struct thermal_profile_params *hp_wmi_thermal_profile(void) ++{ ++ if (!active_board_params) ++ return NULL; ++ ++ return active_board_params->thermal_profile; ++} + + /* DMI board names of devices that should use the omen specific path for + * thermal profiles. +@@ -157,7 +207,7 @@ static const char * const omen_thermal_profile_boards[] = { + "886B", "886C", "88C8", "88CB", "88D1", "88D2", "88F4", "88F5", "88F6", + "88F7", "88FD", "88FE", "88FF", + "8900", "8901", "8902", "8912", "8917", "8918", "8949", "894A", "89EB", +- "8A15", "8A42", ++ "8A15", "8A42", "8A43", + "8BAD", + "8C58", + "8E41", +@@ -187,83 +237,103 @@ static const char * const victus_thermal_profile_boards[] = { + "8A25", + }; + +-/* DMI Board names of Victus 16-r and Victus 16-s laptops */ +-static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = { ++/* DMI board-specific feature data for Omen and Victus laptops. */ ++static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, ++ }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A3D") }, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A44") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, ++ }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAA") }, ++ .driver_data = (void *)&omen_v1_board_params, ++ }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BA9") }, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAB") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, ++ }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BB3") }, ++ .driver_data = (void *)&omen_v1_no_ec_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BC2") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCA") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCD") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C76") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C77") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D26") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") }, +- .driver_data = (void *)&omen_v1_no_ec_thermal_params, ++ .driver_data = (void *)&omen_v1_no_ec_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") }, +- .driver_data = (void *)&omen_v1_no_ec_thermal_params, ++ .driver_data = (void *)&omen_v1_no_ec_board_params, ++ }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D88") }, ++ .driver_data = (void *)&omen_v1_no_ec_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD6") }, +@@ -271,7 +341,7 @@ static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, + }, + {}, + }; +@@ -336,6 +406,7 @@ enum hp_wmi_commandtype { + HPWMI_POSTCODEERROR_QUERY = 0x2a, + HPWMI_SYSTEM_DEVICE_MODE = 0x40, + HPWMI_THERMAL_PROFILE_QUERY = 0x4c, ++ HPWMI_GRAPHICS_MUX_QUERY = 0x52, + }; + + struct victus_power_limits { +@@ -504,9 +575,9 @@ struct hp_wmi_hwmon_priv { + struct mutex lock; /* protects mode, pwm */ + u8 min_rpm; + u8 max_rpm; +- int gpu_delta; + u8 mode; +- u8 pwm; ++ u8 cpu_pwm; ++ u8 gpu_pwm; + struct delayed_work keep_alive_dwork; + }; + +@@ -833,24 +904,20 @@ static int hp_wmi_fan_speed_max_set(int enabled) + return enabled; + } + +-static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) ++static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv) + { + u8 fan_speed[2]; +- int gpu_speed, ret; ++ int ret; + +- fan_speed[CPU_FAN] = speed; +- fan_speed[GPU_FAN] = speed; ++ if (priv->cpu_pwm == HP_FAN_SPEED_AUTOMATIC) ++ fan_speed[CPU_FAN] = HP_FAN_SPEED_AUTOMATIC; ++ else ++ fan_speed[CPU_FAN] = pwm_to_rpm(priv->cpu_pwm, priv); + +- /* +- * GPU fan speed is always a little higher than CPU fan speed, we fetch +- * this delta value from the fan table during hwmon init. +- * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to +- * automatic mode. +- */ +- if (speed != HP_FAN_SPEED_AUTOMATIC) { +- gpu_speed = speed + priv->gpu_delta; +- fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX); +- } ++ if (priv->gpu_pwm == HP_FAN_SPEED_AUTOMATIC) ++ fan_speed[GPU_FAN] = HP_FAN_SPEED_AUTOMATIC; ++ else ++ fan_speed[GPU_FAN] = pwm_to_rpm(priv->gpu_pwm, priv); + + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) +@@ -867,7 +934,9 @@ static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) + + static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv) + { +- return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC); ++ priv->cpu_pwm = HP_FAN_SPEED_AUTOMATIC; ++ priv->gpu_pwm = HP_FAN_SPEED_AUTOMATIC; ++ return hp_wmi_fan_speed_set(priv); + } + + static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv) +@@ -1128,12 +1197,134 @@ static int camera_shutter_input_setup(void) + return err; + } + ++static const u8 mux_bitmask_map[] = { ++ [0] = HPWMI_MUX_MODE_HYBRID, ++ [1] = HPWMI_MUX_MODE_DISCRETE, ++ [2] = HPWMI_MUX_MODE_OPTIMUS, ++ [3] = HPWMI_MUX_MODE_UMA, ++}; ++ ++static int hp_wmi_get_mux_supported_modes(u8 *supported) ++{ ++ u8 legacy_buffer[4] = {}; ++ u8 buffer[128] = {}; ++ u32 req_packet = 0; ++ int ret; ++ ++ if (!supported) ++ return -EINVAL; ++ ++ /* Try modern BIOS design data query (128-byte buffer) */ ++ ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM, ++ buffer, zero_if_sup(req_packet), sizeof(buffer)); ++ if (ret == 0) { ++ *supported = buffer[7]; ++ return 0; ++ } ++ ++ /* ++ * (Fallback): Legacy BIOS behavior based on Omen Gaming Hub. ++ * If the modern query is not supported, check if the MUX query endpoint ++ * responds to a read request. If it succeeds, the hardware has MUX ++ * capability but lacks the mode map, defaulting to Hybrid + Discrete. ++ */ ++ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ, ++ legacy_buffer, sizeof(legacy_buffer), 0); ++ if (ret < 0) ++ return ret; ++ if (ret > 0) ++ return -EINVAL; ++ ++ *supported = HPWMI_MUX_LEGACY_MASK; ++ return 0; ++} ++ ++static int hp_wmi_get_mux_mode(u8 *mode) ++{ ++ u8 buffer[4] = {}; ++ int ret; ++ ++ if (!mode) ++ return -EINVAL; ++ ++ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ, ++ buffer, sizeof(buffer), sizeof(buffer)); ++ if (ret < 0) ++ return ret; ++ if (ret > 0) ++ return -EINVAL; ++ ++ /* Mask the highest bit, which might be used as a BIOS status flag */ ++ *mode = buffer[0] & HPWMI_MUX_MODE_MASK; ++ return 0; ++} ++ ++static int hp_wmi_set_mux_mode(u8 mode) ++{ ++ u8 buffer[4] = { mode, 0x00, 0x00, 0x00 }; ++ int ret; ++ ++ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_WRITE, ++ buffer, sizeof(buffer), sizeof(buffer)); ++ if (ret < 0) ++ return ret; ++ if (ret > 0) ++ return -EINVAL; ++ ++ return 0; ++} ++ ++static ssize_t gpu_mux_mode_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ u8 mode; ++ int ret; ++ ++ ret = hp_wmi_get_mux_mode(&mode); ++ if (ret) ++ return ret; ++ ++ return sysfs_emit(buf, "%u\n", mode); ++} ++ ++static ssize_t gpu_mux_mode_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, ++ size_t count) ++{ ++ u32 requested; ++ u8 supported; ++ int ret; ++ ++ ret = kstrtou32(buf, 0, &requested); ++ if (ret) ++ return ret; ++ if (requested >= ARRAY_SIZE(mux_bitmask_map)) ++ return -EINVAL; ++ ++ ret = hp_wmi_get_mux_supported_modes(&supported); ++ if (ret) ++ return ret; ++ ++ /* Verify if the requested mode is allowed by the hardware mask */ ++ if (!(supported & mux_bitmask_map[requested])) ++ return -EOPNOTSUPP; ++ ++ ret = hp_wmi_set_mux_mode(requested); ++ if (ret) ++ return ret; ++ ++ return count; ++} ++ + static DEVICE_ATTR_RO(display); + static DEVICE_ATTR_RO(hddtemp); + static DEVICE_ATTR_RW(als); + static DEVICE_ATTR_RO(dock); + static DEVICE_ATTR_RO(tablet); + static DEVICE_ATTR_RW(postcode); ++static DEVICE_ATTR_RW(gpu_mux_mode); + + static struct attribute *hp_wmi_attrs[] = { + &dev_attr_display.attr, +@@ -1142,6 +1333,7 @@ static struct attribute *hp_wmi_attrs[] = { + &dev_attr_dock.attr, + &dev_attr_tablet.attr, + &dev_attr_postcode.attr, ++ &dev_attr_gpu_mux_mode.attr, + NULL, + }; + ATTRIBUTE_GROUPS(hp_wmi); +@@ -1793,6 +1985,38 @@ static bool is_victus_s_thermal_profile(void) + return is_victus_s_board; + } + ++static const struct hp_wmi_fan_profile_params *hp_wmi_fan_profile(void) ++{ ++ if (!active_board_params) ++ return NULL; ++ ++ return active_board_params->fan_profile; ++} ++ ++static bool hp_wmi_fan_control_supported(void) ++{ ++ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); ++ ++ return params && params->get_fan_speed; ++} ++ ++static bool hp_wmi_fan_table_supported(void) ++{ ++ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); ++ ++ return params && params->fan_table; ++} ++ ++static int hp_wmi_get_active_fan_speed(int fan) ++{ ++ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); ++ ++ if (!params || !params->get_fan_speed) ++ return -EOPNOTSUPP; ++ ++ return params->get_fan_speed(fan); ++} ++ + static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable, + bool *ppab_enable, + u8 *dstate, +@@ -1878,7 +2102,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil + u8 current_dstate, current_gpu_slowdown_temp, tp; + const struct thermal_profile_params *params; + +- params = active_thermal_profile_params; ++ params = hp_wmi_thermal_profile(); ++ if (!params) ++ return -ENODEV; ++ + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || + params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { + *profile = active_platform_profile; +@@ -1890,10 +2117,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil + return ret; + + /* +- * We cannot use active_thermal_profile_params here, because boards +- * like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically +- * it should have tp == 0x30 || tp == 0x31, as corrected by the Omen +- * Gaming Hub on windows. Hence accept both of these values. ++ * Boards like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, ++ * but logically it should have tp == 0x30 || tp == 0x31, as ++ * corrected by the Omen Gaming Hub on windows. Hence accept both ++ * of these values. + */ + if (tp == victus_s_thermal_params.performance || + tp == omen_v1_thermal_params.performance) { +@@ -1928,12 +2155,12 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil + + static int platform_profile_victus_s_set_ec(enum platform_profile_option profile) + { +- struct thermal_profile_params *params; ++ const struct thermal_profile_params *params; + bool gpu_ctgp_enable, gpu_ppab_enable; + u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */ + int err, tp; + +- params = active_thermal_profile_params; ++ params = hp_wmi_thermal_profile(); + if (!params) + return -ENODEV; + +@@ -2199,6 +2426,7 @@ static const struct platform_profile_ops hp_wmi_platform_profile_ops = { + static int thermal_profile_setup(struct platform_device *device) + { + const struct platform_profile_ops *ops; ++ const struct thermal_profile_params *params; + int err, tp; + + if (is_omen_thermal_profile()) { +@@ -2230,13 +2458,17 @@ static int thermal_profile_setup(struct platform_device *device) + + ops = &platform_profile_victus_ops; + } else if (is_victus_s_thermal_profile()) { ++ params = hp_wmi_thermal_profile(); ++ if (!params) ++ return -ENODEV; ++ + /* + * For an unknown EC layout board, platform_profile_victus_s_get_ec(), + * behaves like a wrapper around active_platform_profile, to avoid using + * uninitialized data, we default to PLATFORM_PROFILE_BALANCED. + */ +- if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || +- active_thermal_profile_params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { ++ if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || ++ params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { + active_platform_profile = PLATFORM_PROFILE_BALANCED; + } else { + err = platform_profile_victus_s_get_ec(&active_platform_profile); +@@ -2404,7 +2636,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) + + switch (priv->mode) { + case PWM_MODE_MAX: +- if (is_victus_s_thermal_profile()) { ++ if (hp_wmi_fan_control_supported()) { + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) + return ret; +@@ -2416,16 +2648,16 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) + secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); + return 0; + case PWM_MODE_MANUAL: +- if (!is_victus_s_thermal_profile()) ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; +- ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv)); ++ ret = hp_wmi_fan_speed_set(priv); + if (ret < 0) + return ret; + mod_delayed_work(system_dfl_wq, &priv->keep_alive_dwork, + secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); + return 0; + case PWM_MODE_AUTO: +- if (is_victus_s_thermal_profile()) { ++ if (hp_wmi_fan_control_supported()) { + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) + return ret; +@@ -2449,12 +2681,12 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, + { + switch (type) { + case hwmon_pwm: +- if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile()) ++ if (attr == hwmon_pwm_input && !hp_wmi_fan_control_supported()) + return 0; + return 0644; + case hwmon_fan: +- if (is_victus_s_thermal_profile()) { +- if (hp_wmi_get_fan_speed_victus_s(channel) >= 0) ++ if (hp_wmi_fan_control_supported()) { ++ if (hp_wmi_get_active_fan_speed(channel) >= 0) + return 0444; + } else { + if (hp_wmi_get_fan_speed(channel) >= 0) +@@ -2478,8 +2710,8 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + priv = dev_get_drvdata(dev); + switch (type) { + case hwmon_fan: +- if (is_victus_s_thermal_profile()) +- ret = hp_wmi_get_fan_speed_victus_s(channel); ++ if (hp_wmi_fan_control_supported()) ++ ret = hp_wmi_get_active_fan_speed(channel); + else + ret = hp_wmi_get_fan_speed(channel); + if (ret < 0) +@@ -2488,10 +2720,10 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + return 0; + case hwmon_pwm: + if (attr == hwmon_pwm_input) { +- if (!is_victus_s_thermal_profile()) ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; + +- rpm = hp_wmi_get_fan_speed_victus_s(channel); ++ rpm = hp_wmi_get_active_fan_speed(channel); + if (rpm < 0) + return rpm; + *val = rpm_to_pwm(rpm / 100, priv); +@@ -2518,14 +2750,15 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) + { + struct hp_wmi_hwmon_priv *priv; +- int rpm; ++ int cpu_rpm, gpu_rpm; + + priv = dev_get_drvdata(dev); + guard(mutex)(&priv->lock); + switch (type) { + case hwmon_pwm: + if (attr == hwmon_pwm_input) { +- if (!is_victus_s_thermal_profile()) ++ int rpm; ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; + /* PWM input is invalid when not in manual mode */ + if (priv->mode != PWM_MODE_MANUAL) +@@ -2534,7 +2767,10 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + /* ensure PWM input is within valid fan speeds */ + rpm = pwm_to_rpm(val, priv); + rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm); +- priv->pwm = rpm_to_pwm(rpm, priv); ++ if (channel == CPU_FAN) ++ priv->cpu_pwm = rpm_to_pwm(rpm, priv); ++ else if (channel == GPU_FAN) ++ priv->gpu_pwm = rpm_to_pwm(rpm, priv); + return hp_wmi_apply_fan_settings(priv); + } + switch (val) { +@@ -2542,16 +2778,20 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + priv->mode = PWM_MODE_MAX; + return hp_wmi_apply_fan_settings(priv); + case PWM_MODE_MANUAL: +- if (!is_victus_s_thermal_profile()) ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; + /* + * When switching to manual mode, set fan speed to + * current RPM values to ensure a smooth transition. + */ +- rpm = hp_wmi_get_fan_speed_victus_s(channel); +- if (rpm < 0) +- return rpm; +- priv->pwm = rpm_to_pwm(rpm / 100, priv); ++ cpu_rpm = hp_wmi_get_active_fan_speed(CPU_FAN); ++ if (cpu_rpm < 0) ++ return cpu_rpm; ++ gpu_rpm = hp_wmi_get_active_fan_speed(GPU_FAN); ++ if (gpu_rpm < 0) ++ return gpu_rpm; ++ priv->cpu_pwm = rpm_to_pwm(cpu_rpm / 100, priv); ++ priv->gpu_pwm = rpm_to_pwm(gpu_rpm / 100, priv); + priv->mode = PWM_MODE_MANUAL; + return hp_wmi_apply_fan_settings(priv); + case PWM_MODE_AUTO: +@@ -2567,7 +2807,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + + static const struct hwmon_channel_info * const info[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), +- HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT), ++ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT, HWMON_PWM_INPUT), + NULL + }; + +@@ -2608,14 +2848,14 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) + struct victus_s_fan_table *fan_table; + u8 min_rpm, max_rpm; + u8 cpu_rpm, gpu_rpm, noise_db; +- int gpu_delta, i, num_entries, ret; ++ int i, num_entries, ret; + size_t header_size, entry_size; + + /* Default behaviour on hwmon init is automatic mode */ + priv->mode = PWM_MODE_AUTO; + +- /* Bypass all non-Victus S devices */ +- if (!is_victus_s_thermal_profile()) ++ /* Bypass devices without fan control support. */ ++ if (!hp_wmi_fan_table_supported()) + return 0; + + ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY, +@@ -2654,10 +2894,8 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) + if (min_rpm == U8_MAX || max_rpm == 0) + return -EINVAL; + +- gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm; + priv->min_rpm = min_rpm; + priv->max_rpm = max_rpm; +- priv->gpu_delta = gpu_delta; + + return 0; + } +@@ -2697,24 +2935,25 @@ static int hp_wmi_hwmon_init(void) + return 0; + } + +-static void __init setup_active_thermal_profile_params(void) ++static void __init setup_active_board_params(void) + { + const struct dmi_system_id *id; ++ const struct thermal_profile_params *params; + +- /* +- * Currently only victus_s devices use the +- * active_thermal_profile_params +- */ +- id = dmi_first_match(victus_s_thermal_profile_boards); ++ id = dmi_first_match(hp_wmi_feature_boards); + if (id) { ++ active_board_params = id->driver_data; ++ params = hp_wmi_thermal_profile(); ++ if (!params) ++ return; ++ + /* + * Marking this boolean is required to ensure that + * is_victus_s_thermal_profile() behaves like a valid + * wrapper. + */ + is_victus_s_board = true; +- active_thermal_profile_params = id->driver_data; +- if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { ++ if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { + pr_warn("Unknown EC layout for board %s. Thermal profile readback will be disabled. Please report this to platform-driver-x86@vger.kernel.org\n", + dmi_get_system_info(DMI_BOARD_NAME)); + } +@@ -2749,10 +2988,10 @@ static int __init hp_wmi_init(void) + } + + /* +- * Setup active board's thermal profile parameters before +- * starting platform driver probe. ++ * Setup active board feature data before starting platform ++ * driver probe. + */ +- setup_active_thermal_profile_params(); ++ setup_active_board_params(); + err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup); + if (err) + goto err_unregister_device; +diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c +--- a/drivers/platform/x86/huawei-wmi.c ++++ b/drivers/platform/x86/huawei-wmi.c +@@ -6,6 +6,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -527,11 +528,104 @@ static void huawei_wmi_battery_exit(struct device *dev) + + /* Fn lock */ + ++/* GFRS byte[1] / SFRS byte[2] (FRSR) fn-lock state values */ ++#define FN_LOCK_ACPI_OFF 1 ++#define FN_LOCK_ACPI_ON 2 ++#define FN_LOCK_ACPI_STAT_OK 0 ++ ++/* ++ * Newer Huawei models (e.g. HUAWEI FLMH-XX / MateBook 14 2024) use direct ++ * ACPI methods \GFRS / \SFRS (Get/Set Fn key Reversal Status) to control ++ * Fn-lock via EC registers 0x6B (read) and 0x6C (write). ++ * ++ * GFRS response buffer layout: ++ * byte[0] = STAT (FN_LOCK_ACPI_STAT_OK = success) ++ * byte[1] = FN_LOCK_ACPI_OFF (fn-lock off) or FN_LOCK_ACPI_ON (fn-lock on) ++ * ++ * SFRS argument layout (CreateByteField(Arg0, 0x02, FRSR)): ++ * Value is read from byte[2] of the integer argument, so it must be ++ * passed as (value << 16): ++ * (FN_LOCK_ACPI_OFF << 16) = fn-lock off (writes 0x55 to EC 0x6C) ++ * (FN_LOCK_ACPI_ON << 16) = fn-lock on (writes 0x5A to EC 0x6C) ++ */ ++ ++static int huawei_acpi_fn_lock_get(int *on) ++{ ++ union acpi_object acpi_arg; ++ struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg }; ++ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; ++ acpi_status status; ++ ++ acpi_arg.type = ACPI_TYPE_INTEGER; ++ acpi_arg.integer.value = 0; ++ ++ status = acpi_evaluate_object(NULL, "\\GFRS", &arg_list, &output); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ union acpi_object *obj __free(kfree) = output.pointer; ++ if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 2) ++ return -ENODATA; ++ ++ /* byte[0] = STAT, byte[1] = fn-lock state */ ++ if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK) ++ return -EIO; ++ ++ switch (obj->buffer.pointer[1]) { ++ case FN_LOCK_ACPI_OFF: ++ if (on) ++ *on = 0; ++ break; ++ case FN_LOCK_ACPI_ON: ++ if (on) ++ *on = 1; ++ break; ++ default: ++ return -ENODATA; ++ } ++ ++ return 0; ++} ++ ++static int huawei_acpi_fn_lock_set(int on) ++{ ++ union acpi_object acpi_arg; ++ struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg }; ++ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; ++ acpi_status status; ++ ++ /* ++ * SFRS reads byte[2] of its argument via CreateByteField(Arg0, 0x02). ++ * on=0 → FRSR=FN_LOCK_ACPI_OFF → EC gets 0x55 (fn-lock off) ++ * on=1 → FRSR=FN_LOCK_ACPI_ON → EC gets 0x5A (fn-lock on) ++ */ ++ acpi_arg.type = ACPI_TYPE_INTEGER; ++ acpi_arg.integer.value = (on ? FN_LOCK_ACPI_ON : FN_LOCK_ACPI_OFF) << 16; ++ ++ status = acpi_evaluate_object(NULL, "\\SFRS", &arg_list, &output); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ union acpi_object *obj __free(kfree) = output.pointer; ++ if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 1) ++ return -ENODATA; ++ ++ if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK) ++ return -EIO; ++ ++ return 0; ++} ++ + static int huawei_wmi_fn_lock_get(int *on) + { + u8 ret[0x100] = { 0 }; + int err, i; + ++ /* Newer models: use direct ACPI \GFRS method */ ++ if (acpi_has_method(NULL, "\\GFRS")) ++ return huawei_acpi_fn_lock_get(on); ++ ++ /* WMI fallback */ + err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100); + if (err) + return err; +@@ -550,6 +644,11 @@ static int huawei_wmi_fn_lock_set(int on) + { + union hwmi_arg arg; + ++ /* Newer models: use direct ACPI \SFRS method */ ++ if (acpi_has_method(NULL, "\\SFRS")) ++ return huawei_acpi_fn_lock_set(on); ++ ++ /* WMI fallback */ + arg.cmd = FN_LOCK_SET; + arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on. + +diff --git a/drivers/platform/x86/intel/int1092/intel_sar.c b/drivers/platform/x86/intel/int1092/intel_sar.c +--- a/drivers/platform/x86/intel/int1092/intel_sar.c ++++ b/drivers/platform/x86/intel/int1092/intel_sar.c +@@ -91,10 +91,10 @@ static acpi_status parse_package(struct wwan_sar_context *context, union acpi_ob + item->package.count <= data->total_dev_mode) + return AE_ERROR; + +- data->device_mode_info = devm_kmalloc_array(&context->sar_device->dev, +- data->total_dev_mode, +- sizeof(*data->device_mode_info), +- GFP_KERNEL); ++ data->device_mode_info = devm_kcalloc(&context->sar_device->dev, ++ data->total_dev_mode, ++ sizeof(*data->device_mode_info), ++ GFP_KERNEL); + if (!data->device_mode_info) + return AE_ERROR; + +diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c +--- a/drivers/platform/x86/intel/pmc/core.c ++++ b/drivers/platform/x86/intel/pmc/core.c +@@ -1583,7 +1583,7 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel + { + const u8 *lpm_indices; + int num_maps, mode_offset = 0; +- int ret, lpm_size; ++ int ret = 0, lpm_size; + u8 mode; + + lpm_indices = pmc->map->lpm_reg_index; +diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig +--- a/drivers/platform/x86/lenovo/Kconfig ++++ b/drivers/platform/x86/lenovo/Kconfig +@@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA + To compile this driver as a module, choose M here: the module + will be called lenovo-wmi-camera. + ++config LENOVO_YB9_KBDOCK ++ tristate "Lenovo Yoga Book 9 keyboard dock detection" ++ depends on ACPI_WMI ++ depends on DMI ++ depends on INPUT ++ help ++ Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9 ++ 14IAH10. The detachable Bluetooth keyboard magnetically attaches to ++ either screen; this driver reports SW_TABLET_MODE input events based ++ on the attachment state and exposes the raw position in sysfs. ++ ++ To compile this driver as a module, choose M here: the module will be ++ called lenovo-yb9-kbdock. ++ + config LENOVO_YMC + tristate "Lenovo Yoga Tablet Mode Control" + depends on ACPI_WMI +diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile +--- a/drivers/platform/x86/lenovo/Makefile ++++ b/drivers/platform/x86/lenovo/Makefile +@@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o + obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o + + lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o ++lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o + lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o + lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o + lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o +diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c +--- a/drivers/platform/x86/lenovo/ideapad-laptop.c ++++ b/drivers/platform/x86/lenovo/ideapad-laptop.c +@@ -2564,8 +2564,8 @@ module_init(ideapad_laptop_init) + + static void __exit ideapad_laptop_exit(void) + { +- ideapad_wmi_driver_unregister(); + platform_driver_unregister(&ideapad_acpi_driver); ++ ideapad_wmi_driver_unregister(); + } + module_exit(ideapad_laptop_exit) + +diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c +--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c ++++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c +@@ -38,6 +38,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -67,6 +68,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -186,6 +188,7 @@ enum tpacpi_hkey_event_t { + TP_HKEY_EV_AMT_TOGGLE = 0x131a, /* Toggle AMT on/off */ + TP_HKEY_EV_CAMERASHUTTER_TOGGLE = 0x131b, /* Toggle Camera Shutter */ + TP_HKEY_EV_DOUBLETAP_TOGGLE = 0x131c, /* Toggle trackpoint doubletap on/off */ ++ TP_HKEY_EV_USB_C_SECURITY = 0x131e, /* USB C Security (Fn+U, Fn+S) */ + TP_HKEY_EV_PROFILE_TOGGLE = 0x131f, /* Toggle platform profile in 2024 systems */ + TP_HKEY_EV_PROFILE_TOGGLE2 = 0x1401, /* Toggle platform profile in 2025 + systems */ + +@@ -309,6 +312,7 @@ struct tp_acpi_drv_struct { + + struct ibm_struct { + char *name; ++ acpi_device_class device_class; + + int (*read) (struct seq_file *); + int (*write) (char *); +@@ -374,6 +378,8 @@ static struct { + u32 has_adaptive_kbd:1; + u32 kbd_lang:1; + u32 trackpoint_doubletap_enable:1; ++ u32 usbc_security_supported:1; ++ bool usbc_security_enabled; + struct quirk_entry *quirks; + } tp_features; + +@@ -838,9 +844,8 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm) + } + + ibm->acpi->device->driver_data = ibm; +- scnprintf(acpi_device_class(ibm->acpi->device), +- sizeof(acpi_device_class(ibm->acpi->device)), +- "%s/%s", TPACPI_ACPI_EVENT_PREFIX, ibm->name); ++ scnprintf(ibm->device_class, sizeof(ibm->device_class), "%s/%s", ++ TPACPI_ACPI_EVENT_PREFIX, ibm->name); + + status = acpi_install_notify_handler(*ibm->acpi->handle, + ibm->acpi->type, dispatch_acpi_notify, ibm); +@@ -3869,7 +3874,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) + pr_err("unknown HKEY notification event %d\n", event); + /* forward it to userspace, maybe it knows how to handle it */ + acpi_bus_generate_netlink_event( +- ibm->acpi->device->pnp.device_class, ++ ibm->device_class, + dev_name(&ibm->acpi->device->dev), + event, 0); + return; +@@ -3949,7 +3954,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) + /* netlink events */ + if (send_acpi_ev) { + acpi_bus_generate_netlink_event( +- ibm->acpi->device->pnp.device_class, ++ ibm->device_class, + dev_name(&ibm->acpi->device->dev), + event, hkey); + } +@@ -8840,6 +8845,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = { + TPACPI_Q_LNV3('R', '0', 'T', TPACPI_FAN_NS), /* 11e Gen5 GL */ + TPACPI_Q_LNV3('R', '1', 'D', TPACPI_FAN_NS), /* 11e Gen5 GL-R */ + TPACPI_Q_LNV3('R', '0', 'V', TPACPI_FAN_NS), /* 11e Gen5 KL-Y */ ++ TPACPI_Q_LNV('H', '3', TPACPI_FAN_NS), /* Edge E330 */ + TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */ + TPACPI_Q_LNV3('R', '0', 'Q', TPACPI_FAN_DECRPM),/* L480 */ + TPACPI_Q_LNV('8', 'F', TPACPI_FAN_TPR), /* ThinkPad x120e */ +@@ -11285,6 +11291,114 @@ static struct ibm_struct hwdd_driver_data = { + .name = "hwdd", + }; + ++/************************************************************************* ++ * USB-C Security subdriver ++ * ++ * HKEY.USCS(0) is a read-only ACPI method; its argument is ignored. ++ * It always returns: ++ * bit 16 - USB-C security capability present on this SKU or not ++ * bit 0 - USB-C Security state (enable or disable) ++ * ++ * Hotkey ++ * ------ ++ * 0x131e (Fn+U, Fn+S): firmware toggles USBS before firing the event. ++ * The driver reads back the new state and notifies the sysfs attribute. ++ */ ++ ++/* USCS() return word bit layout */ ++#define USCS_CAP_BIT BIT(16) /* capability: feature present on SKU */ ++#define USCS_STATUS_BIT BIT(0) /* current security state */ ++ ++/* Protects USCS() ACPI method calls in usbc_security_query() */ ++static DEFINE_MUTEX(usbc_security_mutex); ++ ++/** ++ * usbc_security_query - read current USB-C security state via USCS() ++ * @enabled: out - true when security is ON (data connections blocked) ++ * ++ * Returns: ++ * 0 success, @enabled contains the current state ++ * -EIO ACPI evaluation failed ++ * -ENODEV capability bit absent; feature not present on this SKU* ++ */ ++static int usbc_security_query(bool *enabled) ++{ ++ int status; ++ ++ guard(mutex)(&usbc_security_mutex); ++ if (!acpi_evalf(hkey_handle, &status, "USCS", "dd", 0)) ++ return -EIO; ++ ++ if (!(status & USCS_CAP_BIT)) { ++ pr_debug("USCS cap bit absent (raw=0x%x)\n", status); ++ return -ENODEV; ++ } ++ ++ *enabled = status & USCS_STATUS_BIT; ++ return 0; ++} ++ ++/* sysfs: /sys/devices/platform/thinkpad_acpi/usb_c_security ---------- */ ++static ssize_t usb_c_security_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return sysfs_emit(buf, "%s\n", ++ str_enabled_disabled(tp_features.usbc_security_enabled)); ++} ++ ++static DEVICE_ATTR_RO(usb_c_security); ++ ++static struct attribute *usbc_security_attributes[] = { ++ &dev_attr_usb_c_security.attr, ++ NULL, ++}; ++ ++static umode_t usbc_security_attr_is_visible(struct kobject *kobj, ++ struct attribute *attr, int n) ++{ ++ return tp_features.usbc_security_supported ? attr->mode : 0; ++} ++ ++static const struct attribute_group usbc_security_attr_group = { ++ .is_visible = usbc_security_attr_is_visible, ++ .attrs = usbc_security_attributes, ++}; ++ ++static int tpacpi_usbc_security_init(struct ibm_init_struct *iibm) ++{ ++ int err; ++ ++ if (!acpi_has_method(hkey_handle, "USCS")) ++ return -ENODEV; ++ ++ err = usbc_security_query(&tp_features.usbc_security_enabled); ++ if (err == -ENODEV) ++ return 0; ++ if (err) ++ return err; ++ ++ tp_features.usbc_security_supported = true; ++ return 0; ++} ++ ++/* tpacpi_usbc_security_hotkey - handle Fn+U Fn+S hotkey (0x131e) */ ++static bool tpacpi_usbc_security_hotkey(void) ++{ ++ if (!tp_features.usbc_security_supported) ++ return false; ++ ++ if (usbc_security_query(&tp_features.usbc_security_enabled)) ++ return false; ++ ++ sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "usb_c_security"); ++ return true; ++} ++ ++static struct ibm_struct usbc_security_driver_data = { ++ .name = "usbc_security", ++}; ++ + /* --------------------------------------------------------------------- */ + + static struct attribute *tpacpi_driver_attributes[] = { +@@ -11345,6 +11459,7 @@ static const struct attribute_group *tpacpi_groups[] = { + &dprc_attr_group, + &auxmac_attr_group, + &hwdd_attr_group, ++ &usbc_security_attr_group, + NULL, + }; + +@@ -11499,6 +11614,8 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) + case TP_HKEY_EV_PROFILE_TOGGLE2: + platform_profile_cycle(); + return true; ++ case TP_HKEY_EV_USB_C_SECURITY: ++ return tpacpi_usbc_security_hotkey(); + } + + return false; +@@ -11964,6 +12081,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { + .init = tpacpi_hwdd_init, + .data = &hwdd_driver_data, + }, ++ { ++ .init = tpacpi_usbc_security_init, ++ .data = &usbc_security_driver_data, ++ }, + }; + + static int __init set_ibm_param(const char *val, const struct kernel_param *kp) +diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/lenovo/yb9-kbdock.c +@@ -0,0 +1,323 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Lenovo Yoga Book 9 keyboard-dock detection ++ * ++ * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically ++ * attaches to the bottom screen in one of two positions. The EC tracks ++ * attachment state in a 2-bit field called BKBD and signals changes via WMI ++ * event 0xEB on the WM10 ACPI device (_UID "GMZN"). ++ * ++ * BKBD values: ++ * 0 = keyboard detached ++ * 1 = keyboard docked on the top half of the bottom screen ++ * 2 = keyboard docked on the bottom half of the bottom screen ++ * 3 = reserved / not observed ++ * ++ * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes): ++ * ++ * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...) ++ * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly. ++ * The notify callback receives BKBD as an integer; no separate query needed. ++ * ++ * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...) ++ * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector) ++ * WmiDataId(2) uint32 Status = BKBD value ++ * Used on probe and resume to read initial state. ++ * ++ * The event driver (LENOVO_BTKBD_EVENT) fires a notifier chain on each WMI ++ * event. The block driver (LENOVO_FEATURE_STATUS_DATA) owns the input_dev ++ * and registers a notifier_block to receive those events, eliminating the ++ * need for shared global state or a mutex. ++ * ++ * SW_TABLET_MODE=1 is reported when the keyboard is detached; ++ * SW_TABLET_MODE=0 when docked in either position (keyboard present). ++ * The raw BKBD value is exposed via the sysfs attribute "keyboard_position". ++ * ++ * Copyright (C) 2026 Dave Carey ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285" ++#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6" ++ ++/* BKBD encoding */ ++#define BKBD_DETACHED 0 ++ ++/* LENOVO_FEATURE_STATUS_DATA feature selector */ ++#define YB9_FEATURE_STATUS_ID 0x00060000u ++ ++/* ++ * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {uint32 IDs, uint32 Status}. ++ * IDs is always 0x00060000; Status holds the BKBD value (0–3). ++ */ ++struct lenovo_feature_status { ++ __le32 id; ++ __le32 status; ++} __packed; ++ ++/* ------------------------------------------------------------------ ++ * Notifier chain — event driver fires it, block driver listens ++ * ------------------------------------------------------------------ */ ++ ++static BLOCKING_NOTIFIER_HEAD(yb9_kbdock_chain_head); ++ ++static void devm_yb9_kbdock_unregister_notifier(void *data) ++{ ++ struct notifier_block *nb = data; ++ ++ blocking_notifier_chain_unregister(&yb9_kbdock_chain_head, nb); ++} ++ ++static int devm_yb9_kbdock_register_notifier(struct device *dev, ++ struct notifier_block *nb) ++{ ++ int ret; ++ ++ ret = blocking_notifier_chain_register(&yb9_kbdock_chain_head, nb); ++ if (ret < 0) ++ return ret; ++ ++ return devm_add_action_or_reset(dev, devm_yb9_kbdock_unregister_notifier, nb); ++} ++ ++/* ------------------------------------------------------------------ ++ * Block WMI driver — LENOVO_FEATURE_STATUS_DATA ++ * (owns input_dev, sysfs, PM resume) ++ * ------------------------------------------------------------------ */ ++ ++struct yb9_kbdock_data { ++ struct wmi_device *wdev; ++ struct input_dev *input_dev; ++ struct notifier_block nb; ++ spinlock_t lock; /* protects input_report_switch + input_sync */ ++}; ++ ++static int yb9_kbdock_query(struct yb9_kbdock_data *d, u32 *bkbd) ++{ ++ struct wmi_buffer out; ++ int ret; ++ ++ ret = wmidev_query_block(d->wdev, 0, &out, ++ sizeof(struct lenovo_feature_status)); ++ if (ret) ++ return ret; ++ ++ struct lenovo_feature_status *fs __free(kfree) = out.data; ++ ++ if (le32_to_cpu(fs->id) != YB9_FEATURE_STATUS_ID) ++ return -EIO; ++ ++ *bkbd = le32_to_cpu(fs->status); ++ return 0; ++} ++ ++static void yb9_kbdock_report(struct yb9_kbdock_data *d, u32 bkbd) ++{ ++ int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0; ++ ++ spin_lock(&d->lock); ++ input_report_switch(d->input_dev, SW_TABLET_MODE, tablet); ++ input_sync(d->input_dev); ++ spin_unlock(&d->lock); ++ dev_dbg(&d->wdev->dev, "BKBD=%u SW_TABLET_MODE=%d\n", bkbd, tablet); ++} ++ ++static int yb9_kbdock_sync(struct yb9_kbdock_data *d) ++{ ++ u32 bkbd; ++ int ret; ++ ++ ret = yb9_kbdock_query(d, &bkbd); ++ if (ret) ++ return ret; ++ ++ yb9_kbdock_report(d, bkbd); ++ return 0; ++} ++ ++static int yb9_kbdock_nb_call(struct notifier_block *nb, ++ unsigned long bkbd, void *unused) ++{ ++ struct yb9_kbdock_data *d = ++ container_of(nb, struct yb9_kbdock_data, nb); ++ ++ yb9_kbdock_report(d, bkbd); ++ return NOTIFY_DONE; ++} ++ ++static ssize_t keyboard_position_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct yb9_kbdock_data *d = dev_get_drvdata(dev); ++ u32 bkbd; ++ int ret; ++ ++ ret = yb9_kbdock_query(d, &bkbd); ++ if (ret) ++ return ret; ++ return sysfs_emit(buf, "%u\n", bkbd); ++} ++static DEVICE_ATTR_RO(keyboard_position); ++ ++static const struct attribute * const yb9_kbdock_attrs[] = { ++ &dev_attr_keyboard_position.attr, ++ NULL, ++}; ++ATTRIBUTE_GROUPS(yb9_kbdock); ++ ++static int yb9_kbdock_resume(struct device *dev) ++{ ++ struct yb9_kbdock_data *d = dev_get_drvdata(dev); ++ ++ return yb9_kbdock_sync(d); ++} ++static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume); ++ ++static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx) ++{ ++ struct yb9_kbdock_data *d; ++ struct input_dev *input_dev; ++ int ret; ++ ++ d = devm_kzalloc(&wdev->dev, sizeof(*d), GFP_KERNEL); ++ if (!d) ++ return -ENOMEM; ++ ++ d->wdev = wdev; ++ spin_lock_init(&d->lock); ++ ++ input_dev = devm_input_allocate_device(&wdev->dev); ++ if (!input_dev) ++ return -ENOMEM; ++ ++ input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch"; ++ input_dev->phys = YB9_KBDOCK_QUERY_GUID "/input0"; ++ input_dev->id.bustype = BUS_HOST; ++ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); ++ ++ ret = input_register_device(input_dev); ++ if (ret) ++ return ret; ++ ++ d->input_dev = input_dev; ++ d->nb.notifier_call = yb9_kbdock_nb_call; ++ ++ ret = devm_yb9_kbdock_register_notifier(&wdev->dev, &d->nb); ++ if (ret) ++ return ret; ++ ++ dev_set_drvdata(&wdev->dev, d); ++ return yb9_kbdock_sync(d); ++} ++ ++static const struct wmi_device_id yb9_kbdock_block_id_table[] = { ++ { .guid_string = YB9_KBDOCK_QUERY_GUID }, ++ { } ++}; ++ ++static struct wmi_driver yb9_kbdock_block_driver = { ++ .driver = { ++ .name = "lenovo-yb9-kbdock", ++ .dev_groups = yb9_kbdock_groups, ++ .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops), ++ }, ++ .id_table = yb9_kbdock_block_id_table, ++ .no_singleton = true, ++ .probe = yb9_kbdock_block_probe, ++}; ++ ++/* ------------------------------------------------------------------ ++ * Event WMI driver — LENOVO_BTKBD_EVENT ++ * (fires the notifier chain on each WMI event) ++ * ------------------------------------------------------------------ */ ++ ++static void yb9_kbdock_notify_new(struct wmi_device *wdev, ++ const struct wmi_buffer *data) ++{ ++ /* ++ * _WED(0xEB) returns EC.BKBD directly as a 32-bit integer ++ * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status). ++ * Short-buffer guard is handled by .min_event_size below. ++ */ ++ u32 bkbd = le32_to_cpu(*(const __le32 *)data->data); ++ ++ blocking_notifier_call_chain(&yb9_kbdock_chain_head, bkbd, NULL); ++} ++ ++static const struct wmi_device_id yb9_kbdock_event_id_table[] = { ++ { .guid_string = YB9_KBDOCK_EVENT_GUID }, ++ { } ++}; ++MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table); ++ ++static struct wmi_driver yb9_kbdock_event_driver = { ++ .driver = { ++ .name = "lenovo-yb9-kbdock-event", ++ }, ++ .id_table = yb9_kbdock_event_id_table, ++ .no_singleton = true, ++ .notify_new = yb9_kbdock_notify_new, ++ .min_event_size = sizeof(__le32), ++}; ++ ++/* ------------------------------------------------------------------ ++ * Module init / exit ++ * ------------------------------------------------------------------ */ ++ ++static const struct dmi_system_id yb9_kbdock_dmi_table[] __initconst = { ++ { ++ /* Lenovo Yoga Book 9 14IAH10 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), ++ }, ++ }, ++ { } ++}; ++ ++static int __init yb9_kbdock_init(void) ++{ ++ int ret; ++ ++ if (!dmi_check_system(yb9_kbdock_dmi_table)) ++ return -ENODEV; ++ ++ ret = wmi_driver_register(&yb9_kbdock_event_driver); ++ if (ret) ++ return ret; ++ ++ ret = wmi_driver_register(&yb9_kbdock_block_driver); ++ if (ret) { ++ wmi_driver_unregister(&yb9_kbdock_event_driver); ++ return ret; ++ } ++ ++ return 0; ++} ++module_init(yb9_kbdock_init); ++ ++static void __exit yb9_kbdock_exit(void) ++{ ++ wmi_driver_unregister(&yb9_kbdock_block_driver); ++ wmi_driver_unregister(&yb9_kbdock_event_driver); ++} ++module_exit(yb9_kbdock_exit); ++ ++MODULE_AUTHOR("Dave Carey "); ++MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c +--- a/drivers/platform/x86/lenovo/ymc.c ++++ b/drivers/platform/x86/lenovo/ymc.c +@@ -28,6 +28,22 @@ static bool force; + module_param(force, bool, 0444); + MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type"); + ++static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = { ++ { ++ /* ++ * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the ++ * yb9-kbdock driver. Suppress lenovo-ymc on this machine to ++ * avoid userspace seeing two input nodes that both advertise ++ * the SW_TABLET_MODE capability. ++ */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), ++ }, ++ }, ++ { } ++}; ++ + static const struct dmi_system_id allowed_chasis_types_dmi_table[] = { + { + .matches = { +@@ -107,6 +123,9 @@ static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx) + struct input_dev *input_dev; + int err; + ++ if (dmi_check_system(lenovo_ymc_nosupport_dmi_table)) ++ return -ENODEV; ++ + if (!dmi_check_system(allowed_chasis_types_dmi_table)) { + if (force) + dev_info(&wdev->dev, "Force loading Lenovo YMC support\n"); +diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c +--- a/drivers/platform/x86/lg-laptop.c ++++ b/drivers/platform/x86/lg-laptop.c +@@ -8,8 +8,11 @@ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + + #include ++#include + #include + #include ++#include ++#include + #include + #include + #include +@@ -17,10 +20,12 @@ + #include + #include + #include ++#include + #include + #include + #include + #include ++#include + + #include + +@@ -57,13 +62,18 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); + #define LG_ADDRESS_SPACE_DEBUG_MSG_START_ADR 0x3E8 + #define LG_ADDRESS_SPACE_DEBUG_MSG_END_ADR 0x5E8 + +-#define WMI_EVENT_GUID0 "E4FB94F9-7F2B-4173-AD1A-CD1D95086248" +-#define WMI_EVENT_GUID1 "023B133E-49D1-4E10-B313-698220140DC2" +-#define WMI_EVENT_GUID2 "37BE1AC0-C3F2-4B1F-BFBE-8FDEAF2814D6" +-#define WMI_EVENT_GUID3 "911BAD44-7DF8-4FBB-9319-BABA1C4B293B" +-#define WMI_METHOD_WMAB "C3A72B38-D3EF-42D3-8CBB-D5A57049F66D" +-#define WMI_METHOD_WMBB "2B4F501A-BD3C-4394-8DCF-00A7D2BC8210" +-#define WMI_EVENT_GUID WMI_EVENT_GUID0 ++#define LG_NOTIFY_TABLET_MODE_OFF 0x50 ++#define LG_NOTIFY_TABLET_MODE_ON 0x51 ++#define LG_NOTIFY_HOTKEY 0x80 ++#define LG_NOTIFY_THERMAL 0x81 ++#define LG_NOTIFY_MISC 0x82 ++ ++#define LG_OREP_READ_EC 0 ++#define LG_OREP_WRITE_EC 1 ++#define LG_OREP_DEBUG 2 ++#define LG_OREP_UPDATE_SYSTEM_STATE 3 ++#define LG_OREP_INTERCEPT_WMI_EVENTS 4 ++#define LG_OREP_WAKE_ON_LAN 6 + + #define SB_GGOV_METHOD "\\_SB.GGOV" + #define GOV_TLED 0x2020008 +@@ -78,42 +88,95 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); + #define WMBB_USB_CHARGE 0x10B + #define WMBB_BATT_LIMIT 0x10C + ++#define KBD_LED_BRIGHTNESS_MASK GENMASK(4, 0) ++#define KBD_LED_BRIGHTNESS_OFF 0x0 ++#define KBD_LED_BRIGHTNESS_HALF 0x2 ++#define KBD_LED_BRIGHTNESS_FULL 0x4 ++#define KBD_LED_MODE_MASK GENMASK(6, 5) ++#define KBD_LED_MODE_OFF 0x0 ++#define KBD_LED_MODE_ON 0x1 ++#define KBD_LED_STATUS BIT(7) ++#define KBD_LED_MAGIC_MASK GENMASK(15, 8) ++/* Exact purpose is unknown, maybe some sort of brightness limit? */ ++#define KBD_LED_MAGIC 0x05 ++ + #define FAN_MODE_LOWER GENMASK(1, 0) + #define FAN_MODE_UPPER GENMASK(5, 4) + + #define PLATFORM_NAME "lg-laptop" + +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID0); +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID1); +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID2); +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID3); +-MODULE_ALIAS("wmi:" WMI_METHOD_WMAB); +-MODULE_ALIAS("wmi:" WMI_METHOD_WMBB); ++struct lg_wmab_buffer_result { ++ __le32 value; ++ __le32 status; ++} __packed; + + static struct platform_device *pf_device; +-static struct input_dev *wmi_input_dev; +- +-static u32 inited; +-#define INIT_INPUT_WMI_0 0x01 +-#define INIT_INPUT_WMI_2 0x02 +-#define INIT_INPUT_ACPI 0x04 +-#define INIT_SPARSE_KEYMAP 0x80 + + static int battery_limit_use_wmbb; ++static bool kbd_backlight_available; + static struct led_classdev kbd_backlight; + static enum led_brightness get_kbd_backlight_level(struct device *dev); + + static const struct key_entry wmi_keymap[] = { +- {KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */ +- {KE_KEY, 0x74, {KEY_F21} }, /* Touchpad toggle (F5) */ +- {KE_KEY, 0xf020000, {KEY_F14} }, /* Read mode (F9) */ +- {KE_KEY, 0x10000000, {KEY_F16} },/* Keyboard backlight (F8) - pressing +- * this key both sends an event and +- * changes backlight level. +- */ ++ /* Placeholder value send by multiple hotkeys handled by ACPI */ ++ { KE_IGNORE, 0x0, { KEY_UNKNOWN }}, ++ /* LG control panel */ ++ { KE_KEY, 0x70, { KEY_F15 }}, ++ /* Touchpad toggle */ ++ { KE_KEY, 0x74, { KEY_F21 }}, ++ /* Mute Audio, already handled by ACPI */ ++ { KE_IGNORE, 0x78, { KEY_MUTE }}, ++ /* Read mode */ ++ { KE_KEY, 0xf020000, { KEY_F14 }}, ++ /* Open settings */ ++ { KE_KEY, 0xf070002, { KEY_CONFIG }}, ++ /* Keyboard backlight - pressing this key both sends an event and changes backlight level */ ++ { KE_KEY, 0x10000000, { KEY_F16 }}, ++ /* Hotkey combination pressed */ ++ { KE_IGNORE, 0x30010000, { KEY_UNKNOWN }}, ++ /* Hotkey combination released */ ++ { KE_IGNORE, 0x30010001, { KEY_UNKNOWN }}, ++ /* Change fan mode */ ++ { KE_KEY, 0x30010051, { KEY_PERFORMANCE }}, ++ /* Disable camera */ ++ { KE_KEY, 0x40020000, { KEY_CAMERA_ACCESS_TOGGLE }}, ++ /* Mute microphone */ ++ { KE_KEY, 0x40020001, { KEY_MICMUTE }}, ++ /* Fn-Lock */ ++ { KE_KEY, 0x40030001, { KEY_FN_ESC }}, + {KE_END, 0} + }; + ++static int lg_laptop_execute_orep(acpi_handle handle, u64 command, u64 value, ++ unsigned long long *result) ++{ ++ union acpi_object objs[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = command, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = value, ++ }, ++ } ++ }; ++ struct acpi_object_list args = { ++ .count = ARRAY_SIZE(objs), ++ .pointer = objs, ++ }; ++ acpi_status status; ++ ++ status = acpi_evaluate_integer(handle, "OREP", &args, result); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ return 0; ++} ++ + static int ggov(u32 arg0) + { + union acpi_object args[1]; +@@ -154,30 +217,97 @@ static int ggov(u32 arg0) + return res; + } + +-static union acpi_object *lg_wmab(struct device *dev, u32 method, u32 arg1, u32 arg2) ++static int lg_wmab_get(struct device *dev, u32 method, u32 *result) + { +- union acpi_object args[3]; +- acpi_status status; +- struct acpi_object_list arg; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; ++ union acpi_object in[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = method, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = WM_GET, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = 0, ++ }, ++ }, ++ }; ++ struct lg_wmab_buffer_result *buffer_result; ++ struct acpi_object_list input = { ++ .count = ARRAY_SIZE(in), ++ .pointer = in, ++ }; ++ acpi_status status; + +- args[0].type = ACPI_TYPE_INTEGER; +- args[0].integer.value = method; +- args[1].type = ACPI_TYPE_INTEGER; +- args[1].integer.value = arg1; +- args[2].type = ACPI_TYPE_INTEGER; +- args[2].integer.value = arg2; ++ status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, &buffer); ++ if (ACPI_FAILURE(status)) ++ return -EIO; + +- arg.count = 3; +- arg.pointer = args; ++ union acpi_object *obj __free(kfree) = buffer.pointer; + +- status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &arg, &buffer); +- if (ACPI_FAILURE(status)) { +- dev_err(dev, "WMAB: call failed.\n"); +- return NULL; ++ if (!obj) ++ return -ENODATA; ++ ++ switch (obj->type) { ++ case ACPI_TYPE_INTEGER: ++ *result = obj->integer.value; ++ return 0; ++ case ACPI_TYPE_BUFFER: ++ if (obj->buffer.length != sizeof(*buffer_result)) ++ return -EPROTO; ++ ++ buffer_result = (struct lg_wmab_buffer_result *)obj->buffer.pointer; ++ if (get_unaligned_le32(&buffer_result->status)) ++ return -EIO; ++ ++ *result = get_unaligned_le32(&buffer_result->value); ++ return 0; ++ default: ++ return -EPROTO; + } ++} + +- return buffer.pointer; ++static int lg_wmab_set(struct device *dev, u32 method, u32 arg) ++{ ++ union acpi_object in[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = method, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = WM_SET, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = arg, ++ }, ++ }, ++ }; ++ struct acpi_object_list input = { ++ .count = ARRAY_SIZE(in), ++ .pointer = in, ++ }; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, NULL); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ return 0; + } + + static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u32 arg2) +@@ -211,72 +341,11 @@ static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u + return (union acpi_object *)buffer.pointer; + } + +-static void wmi_notify(union acpi_object *obj, void *context) +-{ +- long data = (long)context; +- +- pr_debug("event guid %li\n", data); +- if (!obj) +- return; +- +- if (obj->type == ACPI_TYPE_INTEGER) { +- int eventcode = obj->integer.value; +- struct key_entry *key; +- +- if (eventcode == 0x10000000) { +- led_classdev_notify_brightness_hw_changed( +- &kbd_backlight, get_kbd_backlight_level(kbd_backlight.dev->parent)); +- } else { +- key = sparse_keymap_entry_from_scancode( +- wmi_input_dev, eventcode); +- if (key && key->type == KE_KEY) +- sparse_keymap_report_entry(wmi_input_dev, +- key, 1, true); +- } +- } +- +- pr_debug("Type: %i Eventcode: 0x%llx\n", obj->type, +- obj->integer.value); +-} +- +-static void wmi_input_setup(void) +-{ +- acpi_status status; +- +- wmi_input_dev = input_allocate_device(); +- if (wmi_input_dev) { +- wmi_input_dev->name = "LG WMI hotkeys"; +- wmi_input_dev->phys = "wmi/input0"; +- wmi_input_dev->id.bustype = BUS_HOST; +- +- if (sparse_keymap_setup(wmi_input_dev, wmi_keymap, NULL) || +- input_register_device(wmi_input_dev)) { +- pr_info("Cannot initialize input device"); +- input_free_device(wmi_input_dev); +- return; +- } +- +- inited |= INIT_SPARSE_KEYMAP; +- status = wmi_install_notify_handler(WMI_EVENT_GUID0, wmi_notify, +- (void *)0); +- if (ACPI_SUCCESS(status)) +- inited |= INIT_INPUT_WMI_0; +- +- status = wmi_install_notify_handler(WMI_EVENT_GUID2, wmi_notify, +- (void *)2); +- if (ACPI_SUCCESS(status)) +- inited |= INIT_INPUT_WMI_2; +- } else { +- pr_info("Cannot allocate input device"); +- } +-} +- + static ssize_t fan_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) + { + unsigned long value; +- union acpi_object *r; + int ret; + + ret = kstrtoul(buffer, 10, &value); +@@ -285,10 +354,10 @@ static ssize_t fan_mode_store(struct device *dev, + if (value >= 3) + return -EINVAL; + +- r = lg_wmab(dev, WM_FAN_MODE, WM_SET, +- FIELD_PREP(FAN_MODE_LOWER, value) | +- FIELD_PREP(FAN_MODE_UPPER, value)); +- kfree(r); ++ ret = lg_wmab_set(dev, WM_FAN_MODE, FIELD_PREP(FAN_MODE_LOWER, value) | ++ FIELD_PREP(FAN_MODE_UPPER, value)); ++ if (ret < 0) ++ return ret; + + return count; + } +@@ -296,22 +365,14 @@ static ssize_t fan_mode_store(struct device *dev, + static ssize_t fan_mode_show(struct device *dev, + struct device_attribute *attr, char *buffer) + { +- unsigned int mode; +- union acpi_object *r; ++ u32 mode; ++ int ret; + +- r = lg_wmab(dev, WM_FAN_MODE, WM_GET, 0); +- if (!r) +- return -EIO; ++ ret = lg_wmab_get(dev, WM_FAN_MODE, &mode); ++ if (ret < 0) ++ return ret; + +- if (r->type != ACPI_TYPE_INTEGER) { +- kfree(r); +- return -EIO; +- } +- +- mode = FIELD_GET(FAN_MODE_LOWER, r->integer.value); +- kfree(r); +- +- return sysfs_emit(buffer, "%d\n", mode); ++ return sysfs_emit(buffer, "%lu\n", FIELD_GET(FAN_MODE_LOWER, mode)); + } + + static ssize_t usb_charge_store(struct device *dev, +@@ -361,41 +422,30 @@ static ssize_t reader_mode_store(struct device *dev, + const char *buffer, size_t count) + { + bool value; +- union acpi_object *r; + int ret; + + ret = kstrtobool(buffer, &value); + if (ret) + return ret; + +- r = lg_wmab(dev, WM_READER_MODE, WM_SET, value); +- if (!r) +- return -EIO; ++ ret = lg_wmab_set(dev, WM_READER_MODE, value); ++ if (ret < 0) ++ return ret; + +- kfree(r); + return count; + } + + static ssize_t reader_mode_show(struct device *dev, + struct device_attribute *attr, char *buffer) + { +- unsigned int status; +- union acpi_object *r; ++ u32 status; ++ int ret; + +- r = lg_wmab(dev, WM_READER_MODE, WM_GET, 0); +- if (!r) +- return -EIO; ++ ret = lg_wmab_get(dev, WM_READER_MODE, &status); ++ if (ret < 0) ++ return ret; + +- if (r->type != ACPI_TYPE_INTEGER) { +- kfree(r); +- return -EIO; +- } +- +- status = !!r->integer.value; +- +- kfree(r); +- +- return sysfs_emit(buffer, "%d\n", status); ++ return sysfs_emit(buffer, "%d\n", !!status); + } + + static ssize_t fn_lock_store(struct device *dev, +@@ -403,40 +453,30 @@ static ssize_t fn_lock_store(struct device *dev, + const char *buffer, size_t count) + { + bool value; +- union acpi_object *r; + int ret; + + ret = kstrtobool(buffer, &value); + if (ret) + return ret; + +- r = lg_wmab(dev, WM_FN_LOCK, WM_SET, value); +- if (!r) +- return -EIO; ++ ret = lg_wmab_set(dev, WM_FN_LOCK, value); ++ if (ret < 0) ++ return ret; + +- kfree(r); + return count; + } + + static ssize_t fn_lock_show(struct device *dev, + struct device_attribute *attr, char *buffer) + { +- unsigned int status; +- union acpi_object *r; ++ u32 status; ++ int ret; + +- r = lg_wmab(dev, WM_FN_LOCK, WM_GET, 0); +- if (!r) +- return -EIO; ++ ret = lg_wmab_get(dev, WM_FN_LOCK, &status); ++ if (ret < 0) ++ return ret; + +- if (r->type != ACPI_TYPE_BUFFER) { +- kfree(r); +- return -EIO; +- } +- +- status = !!r->buffer.pointer[0]; +- kfree(r); +- +- return sysfs_emit(buffer, "%d\n", status); ++ return sysfs_emit(buffer, "%d\n", !!status); + } + + static ssize_t charge_control_end_threshold_store(struct device *dev, +@@ -453,14 +493,18 @@ static ssize_t charge_control_end_threshold_store(struct device *dev, + if (value == 100 || value == 80) { + union acpi_object *r; + +- if (battery_limit_use_wmbb) ++ if (battery_limit_use_wmbb) { + r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_SET, value); +- else +- r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_SET, value); +- if (!r) +- return -EIO; ++ if (!r) ++ return -EIO; ++ ++ kfree(r); ++ } else { ++ ret = lg_wmab_set(&pf_device->dev, WM_BATT_LIMIT, value); ++ if (ret < 0) ++ return ret; ++ } + +- kfree(r); + return count; + } + +@@ -471,8 +515,9 @@ static ssize_t charge_control_end_threshold_show(struct device *device, + struct device_attribute *attr, + char *buf) + { +- unsigned int status; + union acpi_object *r; ++ u32 status; ++ int ret; + + if (battery_limit_use_wmbb) { + r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_GET, 0); +@@ -485,19 +530,13 @@ static ssize_t charge_control_end_threshold_show(struct device *device, + } + + status = r->buffer.pointer[0x10]; ++ kfree(r); + } else { +- r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_GET, 0); +- if (!r) +- return -EIO; +- +- if (r->type != ACPI_TYPE_INTEGER) { +- kfree(r); +- return -EIO; +- } +- +- status = r->integer.value; ++ ret = lg_wmab_get(&pf_device->dev, WM_BATT_LIMIT, &status); ++ if (ret < 0) ++ return ret; + } +- kfree(r); ++ + if (status != 80 && status != 100) + status = 0; + +@@ -563,10 +602,7 @@ static const struct attribute_group dev_attribute_group = { + static void tpad_led_set(struct led_classdev *cdev, + enum led_brightness brightness) + { +- union acpi_object *r; +- +- r = lg_wmab(cdev->dev->parent, WM_TLED, WM_SET, brightness > LED_OFF); +- kfree(r); ++ lg_wmab_set(cdev->dev->parent, WM_TLED, brightness > LED_OFF); + } + + static enum led_brightness tpad_led_get(struct led_classdev *cdev) +@@ -579,47 +615,50 @@ static LED_DEVICE(tpad_led, 1, 0); + static void kbd_backlight_set(struct led_classdev *cdev, + enum led_brightness brightness) + { +- u32 val; +- union acpi_object *r; ++ /* Must always be written */ ++ u32 value = KBD_LED_STATUS; ++ u32 mode, bright; + +- val = 0x22; +- if (brightness <= LED_OFF) +- val = 0; +- if (brightness >= LED_FULL) +- val = 0x24; +- r = lg_wmab(cdev->dev->parent, WM_KEY_LIGHT, WM_SET, val); +- kfree(r); ++ if (brightness <= LED_OFF) { ++ mode = KBD_LED_MODE_OFF; ++ bright = KBD_LED_BRIGHTNESS_OFF; ++ } else { ++ mode = KBD_LED_MODE_ON; ++ if (brightness >= LED_FULL) ++ bright = KBD_LED_BRIGHTNESS_FULL; ++ else ++ bright = KBD_LED_BRIGHTNESS_HALF; ++ } ++ ++ value |= FIELD_PREP(KBD_LED_BRIGHTNESS_MASK, bright); ++ value |= FIELD_PREP(KBD_LED_MODE_MASK, mode); ++ ++ lg_wmab_set(cdev->dev->parent, WM_KEY_LIGHT, value); + } + + static enum led_brightness get_kbd_backlight_level(struct device *dev) + { +- union acpi_object *r; +- int val; ++ u32 value; ++ int ret; + +- r = lg_wmab(dev, WM_KEY_LIGHT, WM_GET, 0); +- +- if (!r) ++ ret = lg_wmab_get(dev, WM_KEY_LIGHT, &value); ++ if (ret < 0) + return LED_OFF; + +- if (r->type != ACPI_TYPE_BUFFER || r->buffer.pointer[1] != 0x05) { +- kfree(r); ++ if (FIELD_GET(KBD_LED_MAGIC_MASK, value) != KBD_LED_MAGIC) + return LED_OFF; +- } + +- switch (r->buffer.pointer[0] & 0x27) { +- case 0x24: +- val = LED_FULL; +- break; +- case 0x22: +- val = LED_HALF; +- break; ++ if (FIELD_GET(KBD_LED_MODE_MASK, value) == KBD_LED_MODE_OFF) ++ return LED_OFF; ++ ++ switch (FIELD_GET(KBD_LED_BRIGHTNESS_MASK, value)) { ++ case KBD_LED_BRIGHTNESS_FULL: ++ return LED_FULL; ++ case KBD_LED_BRIGHTNESS_HALF: ++ return LED_HALF; + default: +- val = LED_OFF; ++ return LED_OFF; + } +- +- kfree(r); +- +- return val; + } + + static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) +@@ -629,26 +668,163 @@ static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) + + static LED_DEVICE(kbd_backlight, 255, LED_BRIGHT_HW_CHANGED); + +-static void wmi_input_destroy(void) +-{ +- if (inited & INIT_INPUT_WMI_2) +- wmi_remove_notify_handler(WMI_EVENT_GUID2); +- +- if (inited & INIT_INPUT_WMI_0) +- wmi_remove_notify_handler(WMI_EVENT_GUID0); +- +- if (inited & INIT_SPARSE_KEYMAP) +- input_unregister_device(wmi_input_dev); +- +- inited &= ~(INIT_INPUT_WMI_0 | INIT_INPUT_WMI_2 | INIT_SPARSE_KEYMAP); +-} +- + static struct platform_driver pf_driver = { + .driver = { + .name = PLATFORM_NAME, + } + }; + ++static int lg_laptop_get_event_data(acpi_handle handle, u32 value, u32 *data) ++{ ++ union acpi_object objs[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = value, ++ }, ++ } ++ }; ++ struct acpi_object_list args = { ++ .count = ARRAY_SIZE(objs), ++ .pointer = objs, ++ }; ++ unsigned long long result; ++ acpi_status status; ++ ++ status = acpi_evaluate_integer(handle, "_WED", &args, &result); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ if (result > U32_MAX) ++ return -EPROTO; ++ ++ *data = result; ++ ++ return 0; ++} ++ ++static void lg_laptop_handle_input_event(struct input_dev *input_dev, u32 value, u32 data) ++{ ++ unsigned int kbd_brightness; ++ ++ switch (value) { ++ case LG_NOTIFY_HOTKEY: ++ sparse_keymap_report_event(input_dev, data, 1, true); ++ break; ++ case LG_NOTIFY_THERMAL: ++ /* Currently not supported */ ++ break; ++ case LG_NOTIFY_MISC: ++ switch (data) { ++ case 0x10000000: ++ if (!kbd_backlight_available) ++ break; ++ ++ kbd_brightness = get_kbd_backlight_level(kbd_backlight.dev->parent); ++ led_classdev_notify_brightness_hw_changed(&kbd_backlight, kbd_brightness); ++ break; ++ default: ++ sparse_keymap_report_event(input_dev, data, 1, true); ++ } ++ break; ++ default: ++ break; ++ } ++} ++ ++static void lg_laptop_notify_handler(acpi_handle handle, u32 value, void *context) ++{ ++ struct input_dev *input_dev = context; ++ u32 data; ++ int ret; ++ ++ switch (value) { ++ case LG_NOTIFY_TABLET_MODE_OFF: ++ case LG_NOTIFY_TABLET_MODE_ON: ++ /* Already handled by intel-hid */ ++ return; ++ case LG_NOTIFY_HOTKEY: ++ case LG_NOTIFY_THERMAL: ++ case LG_NOTIFY_MISC: ++ ret = lg_laptop_get_event_data(handle, value, &data); ++ if (ret < 0) { ++ dev_notice(input_dev->dev.parent, "Failed to get event data: %d\n", ret); ++ return; ++ } ++ ++ dev_dbg(input_dev->dev.parent, "Received event %u (%u)\n", value, data); ++ ++ lg_laptop_handle_input_event(input_dev, value, data); ++ return; ++ default: ++ dev_notice(input_dev->dev.parent, "Received unknown event %u\n", value); ++ } ++}; ++ ++static void lg_laptop_remove_notify_handler(void *context) ++{ ++ acpi_handle handle = context; ++ ++ acpi_remove_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler); ++} ++ ++static void lg_laptop_reenable_wmi_events(void *context) ++{ ++ acpi_handle handle = context; ++ unsigned long long dummy; ++ ++ lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 0, &dummy); ++} ++ ++static int lg_laptop_input_init(struct device *dev, acpi_handle handle) ++{ ++ struct input_dev *input_dev; ++ unsigned long long result; ++ acpi_status status; ++ int ret; ++ ++ if (!acpi_has_method(handle, "_WED")) ++ return 0; ++ ++ input_dev = devm_input_allocate_device(dev); ++ if (!input_dev) ++ return -ENOMEM; ++ ++ input_dev->name = "LG WMI hotkeys"; ++ input_dev->phys = "wmi/input0"; ++ input_dev->id.bustype = BUS_HOST; ++ ret = sparse_keymap_setup(input_dev, wmi_keymap, NULL); ++ if (ret < 0) ++ return ret; ++ ++ ret = input_register_device(input_dev); ++ if (ret < 0) ++ return ret; ++ ++ status = acpi_install_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler, ++ input_dev); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ ret = devm_add_action_or_reset(dev, lg_laptop_remove_notify_handler, handle); ++ if (ret < 0) ++ return ret; ++ ++ if (acpi_has_method(handle, "OREP")) { ++ ret = lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 1, &result); ++ if (ret < 0) ++ return ret; ++ if (result) ++ return -EIO; ++ ++ ret = devm_add_action_or_reset(dev, lg_laptop_reenable_wmi_events, handle); ++ if (ret < 0) ++ return ret; ++ } ++ ++ return 0; ++} ++ + static acpi_status lg_laptop_address_space_write(struct device *dev, acpi_physical_address address, + size_t size, u64 value) + { +@@ -860,15 +1036,23 @@ static int acpi_probe(struct platform_device *pdev) + if (year >= 2019) + battery_limit_use_wmbb = 1; + ++ /* LEDs are optional */ ++ ret = devm_led_classdev_register(&pdev->dev, &kbd_backlight); ++ if (ret < 0) ++ kbd_backlight_available = false; ++ else ++ kbd_backlight_available = true; ++ ++ devm_led_classdev_register(&pdev->dev, &tpad_led); ++ ++ ret = lg_laptop_input_init(&pdev->dev, device->handle); ++ if (ret < 0) ++ goto out_platform_device; ++ + ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); + if (ret) + goto out_platform_device; + +- /* LEDs are optional */ +- led_classdev_register(&pf_device->dev, &kbd_backlight); +- led_classdev_register(&pf_device->dev, &tpad_led); +- +- wmi_input_setup(); + battery_hook_register(&battery_hook); + + return 0; +@@ -884,11 +1068,7 @@ static void acpi_remove(struct platform_device *pdev) + { + sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group); + +- led_classdev_unregister(&tpad_led); +- led_classdev_unregister(&kbd_backlight); +- + battery_hook_unregister(&battery_hook); +- wmi_input_destroy(); + platform_device_unregister(pf_device); + pf_device = NULL; + platform_driver_unregister(&pf_driver); +diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c +--- a/drivers/platform/x86/msi-ec.c ++++ b/drivers/platform/x86/msi-ec.c +@@ -1055,6 +1055,7 @@ static struct msi_ec_conf CONF12 __initdata = { + + static const char * const ALLOWED_FW_13[] __initconst = { + "1594EMS1.109", // MSI Prestige 16 Studio A13VE ++ "17L1EMS1.107", // MSI Katana GF76 11UEK + NULL + }; + +@@ -1130,6 +1131,86 @@ static struct msi_ec_conf CONF13 __initdata = { + }, + }; + ++static const char * const ALLOWED_FW_14[] __initconst = { ++ "1824EMS1.108", // Raider 18 HX AI A2XWJG (MS-1824) ++ "182LIMS1.108", // Vector A18 HX A9WHG ++ "182LIMS1.111", // MSI Raider A18 HX A9WJG / Vector A18 HX A9WHG ++ "182KIMS1.113", // Raider A18 HX A7VIG ++ NULL ++}; ++ ++static struct msi_ec_conf CONF14 __initdata = { ++ .allowed_fw = ALLOWED_FW_14, ++ .charge_control = { ++ .address = 0xd7, ++ .offset_start = 0x8a, ++ .offset_end = 0x80, ++ .range_min = 0x8a, ++ .range_max = 0xe4, ++ }, ++ .webcam = { ++ .address = 0x2e, ++ .block_address = 0x2f, ++ .bit = 1, ++ }, ++ .fn_win_swap = { ++ .address = 0xe8, ++ .bit = 4, ++ }, ++ .cooler_boost = { ++ .address = 0x98, ++ .bit = 7, ++ }, ++ .shift_mode = { ++ .address = 0xd2, ++ .modes = { ++ { SM_ECO_NAME, 0xc2 }, ++ { SM_COMFORT_NAME, 0xc1 }, ++ { SM_TURBO_NAME, 0xc4 }, ++ MSI_EC_MODE_NULL ++ }, ++ }, ++ .super_battery = { ++ .address = 0xeb, ++ .mask = 0x0f, ++ }, ++ .fan_mode = { ++ .address = 0xd4, ++ .modes = { ++ { FM_AUTO_NAME, 0x0d }, ++ { FM_SILENT_NAME, 0x1d }, ++ { FM_ADVANCED_NAME, 0x8d }, ++ MSI_EC_MODE_NULL ++ }, ++ }, ++ .cpu = { ++ .rt_temp_address = 0x68, ++ .rt_fan_speed_address = 0x71, ++ .rt_fan_speed_base_min = 0x00, ++ .rt_fan_speed_base_max = 0x96, ++ .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, ++ .bs_fan_speed_base_min = 0x00, ++ .bs_fan_speed_base_max = 0x0f, ++ }, ++ .gpu = { ++ .rt_temp_address = 0x80, ++ .rt_fan_speed_address = 0x89, ++ }, ++ .leds = { ++ .micmute_led_address = 0x2c, ++ .mute_led_address = 0x2d, ++ .bit = 1, ++ }, ++ .kbd_bl = { ++ .bl_mode_address = MSI_EC_ADDR_UNSUPP, ++ .bl_modes = { 0x00, 0x08 }, ++ .max_mode = 1, ++ .bl_state_address = MSI_EC_ADDR_UNSUPP, ++ .state_base_value = 0x80, ++ .max_state = 3, ++ }, ++}; ++ + static struct msi_ec_conf *CONFIGS[] __initdata = { + &CONF0, + &CONF1, +@@ -1145,6 +1226,7 @@ static struct msi_ec_conf *CONFIGS[] __initdata = { + &CONF11, + &CONF12, + &CONF13, ++ &CONF14, + NULL + }; + +diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c +--- a/drivers/platform/x86/msi-wmi.c ++++ b/drivers/platform/x86/msi-wmi.c +@@ -46,6 +46,12 @@ enum msi_scancodes { + WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */ + WIND_KEY_TURBO, /* Fn+F10 turbo mode toggle */ + WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */ ++ /* MSI Claw keys */ ++ CLAW_KEY_VOLUMEDOWN = 0x21, ++ CLAW_KEY_CENTER = 0x29, /* MSI M-Center main menu */ ++ CLAW_KEY_QUICK_LONG = 0x2a, /* MSI M-Center quick access long hold */ ++ CLAW_KEY_VOLUMEUP = 0x32, ++ CLAW_KEY_QUICK_SHORT = 0x58, /* MSI M-Center quick access short press */ + }; + static struct key_entry msi_wmi_keymap[] = { + { KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, +@@ -69,6 +75,15 @@ static struct key_entry msi_wmi_keymap[] = { + { KE_KEY, WIND_KEY_TURBO, {KEY_PROG1} }, + { KE_KEY, WIND_KEY_ECO, {KEY_PROG2} }, + ++ /* These are MSI Claw keys, used for MSI M-Center in Windows */ ++ { KE_KEY, CLAW_KEY_CENTER, {KEY_F15} }, ++ ++ /* These MSI Claw keys work without WMI. Ignore them to avoid double keycodes */ ++ { KE_IGNORE, CLAW_KEY_QUICK_SHORT }, ++ { KE_IGNORE, CLAW_KEY_QUICK_LONG }, ++ { KE_IGNORE, CLAW_KEY_VOLUMEUP }, ++ { KE_IGNORE, CLAW_KEY_VOLUMEDOWN }, ++ + { KE_END, 0 } + }; + +@@ -172,44 +187,62 @@ static const struct backlight_ops msi_backlight_ops = { + + static void msi_wmi_notify(union acpi_object *obj, void *context) + { +- struct key_entry *key; ++ struct key_entry *key = NULL; ++ int eventcode = 0; + +- if (obj && obj->type == ACPI_TYPE_INTEGER) { +- int eventcode = obj->integer.value; ++ if (!obj) ++ return; ++ ++ switch (obj->type) { ++ case ACPI_TYPE_INTEGER: ++ eventcode = obj->integer.value; + pr_debug("Eventcode: 0x%x\n", eventcode); +- key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, +- eventcode); +- if (!key) { +- pr_info("Unknown key pressed - %x\n", eventcode); ++ break; ++ case ACPI_TYPE_BUFFER: ++ /* Field returns u8[2] here, but is u32 by spec. Allow "oversized" buffers. */ ++ if (obj->buffer.length < 2) ++ return; ++ ++ /* pointer[0] is key ID, pointer[1] is active state. We don't get release ++ * events, so ignore the active state and treat as autorelease. ++ */ ++ eventcode = obj->buffer.pointer[0]; ++ pr_debug("Eventcode: 0x%x\n", eventcode); ++ break; ++ default: ++ pr_info("Unknown event received\n"); ++ return; ++ } ++ ++ key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, eventcode); ++ ++ if (!key) { ++ pr_info("Unknown key pressed - 0x%x\n", eventcode); ++ return; ++ } ++ ++ if (event_wmi->quirk_last_pressed) { ++ ktime_t cur = ktime_get_real(); ++ ktime_t diff = ktime_sub(cur, last_pressed); ++ /* Ignore event if any event happened in a 50 ms ++ * timeframe -> Key press may result in 10-20 GPEs ++ */ ++ if (ktime_to_us(diff) < 1000 * 50) { ++ pr_debug("Suppressed key event 0x%X - Last press was %lld us ago\n", ++ key->code, ktime_to_us(diff)); + return; + } ++ last_pressed = cur; ++ } + +- if (event_wmi->quirk_last_pressed) { +- ktime_t cur = ktime_get_real(); +- ktime_t diff = ktime_sub(cur, last_pressed); +- /* Ignore event if any event happened in a 50 ms +- timeframe -> Key press may result in 10-20 GPEs */ +- if (ktime_to_us(diff) < 1000 * 50) { +- pr_debug("Suppressed key event 0x%X - " +- "Last press was %lld us ago\n", +- key->code, ktime_to_us(diff)); +- return; +- } +- last_pressed = cur; +- } ++ /* Brightness is served via acpi video driver */ ++ if (key->type == KE_KEY && ++ (backlight || (key->code == MSI_KEY_BRIGHTNESSUP || ++ key->code == MSI_KEY_BRIGHTNESSDOWN))) ++ return; + +- if (key->type == KE_KEY && +- /* Brightness is served via acpi video driver */ +- (backlight || +- (key->code != MSI_KEY_BRIGHTNESSUP && +- key->code != MSI_KEY_BRIGHTNESSDOWN))) { +- pr_debug("Send key: 0x%X - Input layer keycode: %d\n", +- key->code, key->keycode); +- sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, +- true); +- } +- } else +- pr_info("Unknown event received\n"); ++ pr_debug("Send key: 0x%X - Input layer keycode: %d\n", key->code, key->keycode); ++ sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, true); + } + + static int __init msi_wmi_backlight_setup(void) +diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c +--- a/drivers/platform/x86/oxpec.c ++++ b/drivers/platform/x86/oxpec.c +@@ -289,6 +289,13 @@ static const struct dmi_system_id dmi_table[] = { + }, + .driver_data = (void *)oxp_x1, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), ++ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X2Mini PRO"), ++ }, ++ .driver_data = (void *)oxp_fly, ++ }, + {}, + }; + +diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c +--- a/drivers/platform/x86/panasonic-laptop.c ++++ b/drivers/platform/x86/panasonic-laptop.c +@@ -159,8 +159,6 @@ MODULE_LICENSE("GPL"); + #define ECO_MODE_ON 0x80 + + #define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support" +-#define ACPI_PCC_DEVICE_NAME "Hotkey" +-#define ACPI_PCC_CLASS "pcc" + + #define ACPI_PCC_INPUT_PHYS "panasonic/hkey0" + +@@ -1026,8 +1024,6 @@ static int acpi_pcc_hotkey_probe(struct platform_device *pdev) + pcc->device = device; + pcc->handle = device->handle; + device->driver_data = pcc; +- strscpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_PCC_CLASS); + + result = acpi_pcc_init_input(pcc); + if (result) { +diff --git a/drivers/platform/x86/redmi-wmi.c b/drivers/platform/x86/redmi-wmi.c +--- a/drivers/platform/x86/redmi-wmi.c ++++ b/drivers/platform/x86/redmi-wmi.c +@@ -29,24 +29,24 @@ static const struct key_entry redmi_wmi_keymap[] = { + {KE_KEY, 0x00011801, {KEY_ASSISTANT}}, + {KE_KEY, 0x00011901, {KEY_ASSISTANT}}, + +- /* Keyboard backlight */ +- {KE_IGNORE, 0x00000501, {}}, +- {KE_IGNORE, 0x00800501, {}}, +- {KE_IGNORE, 0x00050501, {}}, +- {KE_IGNORE, 0x000a0501, {}}, ++ /* Keyboard backlight: Off / Auto / Low / High (new state in byte 2) */ ++ {KE_KEY, 0x00000501, {KEY_KBDILLUMTOGGLE}}, ++ {KE_KEY, 0x00800501, {KEY_KBDILLUMTOGGLE}}, ++ {KE_KEY, 0x00050501, {KEY_KBDILLUMTOGGLE}}, ++ {KE_KEY, 0x000a0501, {KEY_KBDILLUMTOGGLE}}, + + /* Xiaomi G Command Center */ + {KE_KEY, 0x00010a01, {KEY_VENDOR}}, + +- /* OEM preset power mode */ +- {KE_IGNORE, 0x00011601, {}}, +- {KE_IGNORE, 0x00021601, {}}, +- {KE_IGNORE, 0x00031601, {}}, +- {KE_IGNORE, 0x00041601, {}}, ++ /* OEM preset power mode: 1=Balanced 2=Silent 3=Turbo 4=Full speed */ ++ {KE_KEY, 0x00011601, {KEY_PERFORMANCE}}, ++ {KE_KEY, 0x00021601, {KEY_PERFORMANCE}}, ++ {KE_KEY, 0x00031601, {KEY_PERFORMANCE}}, ++ {KE_KEY, 0x00041601, {KEY_PERFORMANCE}}, + +- /* Fn Lock state */ +- {KE_IGNORE, 0x00000701, {}}, +- {KE_IGNORE, 0x00010701, {}}, ++ /* Fn Lock state: 1=on 0=off */ ++ {KE_KEY, 0x00000701, {KEY_FN_ESC}}, ++ {KE_KEY, 0x00010701, {KEY_FN_ESC}}, + + /* Fn+`/1/2/3/4 */ + {KE_KEY, 0x00011101, {KEY_F13}}, +diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c +--- a/drivers/platform/x86/samsung-galaxybook.c ++++ b/drivers/platform/x86/samsung-galaxybook.c +@@ -1438,6 +1438,7 @@ static const struct acpi_device_id galaxybook_device_ids[] = { + { "SAM0428" }, + { "SAM0429" }, + { "SAM0430" }, ++ { "SAMB430" }, + {} + }; + MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids); +diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c +--- a/drivers/platform/x86/sony-laptop.c ++++ b/drivers/platform/x86/sony-laptop.c +@@ -1264,7 +1264,7 @@ static void sony_nc_notify(acpi_handle ah, u32 event, void *data) + ev_type = HOTKEY; + sony_laptop_report_input_event(real_ev); + } +- acpi_bus_generate_netlink_event(sony_nc_acpi_device->pnp.device_class, ++ acpi_bus_generate_netlink_event("sony/hotkey", + dev_name(&sony_nc_acpi_device->dev), ev_type, real_ev); + } + +@@ -3157,8 +3157,6 @@ static int sony_nc_probe(struct platform_device *pdev) + return -ENODEV; + + sony_nc_acpi_device = device; +- strscpy(acpi_device_class(device), "sony/hotkey"); +- + sony_nc_acpi_handle = device->handle; + + /* read device status */ +@@ -4523,7 +4521,6 @@ static int sony_pic_probe(struct platform_device *pdev) + return -ENODEV; + + spic_dev.acpi_dev = device; +- strscpy(acpi_device_class(device), "sony/hotkey"); + sony_pic_detect_device_type(&spic_dev); + mutex_init(&spic_dev.lock); + +diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c +--- a/drivers/platform/x86/topstar-laptop.c ++++ b/drivers/platform/x86/topstar-laptop.c +@@ -299,8 +299,6 @@ static int topstar_acpi_probe(struct platform_device *pdev) + + platform_set_drvdata(pdev, topstar); + +- strscpy(acpi_device_name(device), "Topstar TPSACPI"); +- strscpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS); + topstar->device = device; + + err = topstar_acpi_init(topstar); +diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c +--- a/drivers/platform/x86/toshiba_acpi.c ++++ b/drivers/platform/x86/toshiba_acpi.c +@@ -2505,8 +2505,7 @@ static void toshiba_acpi_kbd_bl_work(struct work_struct *work) + LED_FULL : LED_OFF); + + /* Emulate the keyboard backlight event */ +- acpi_bus_generate_netlink_event(toshiba_acpi->acpi_dev->pnp.device_class, +- dev_name(&toshiba_acpi->acpi_dev->dev), ++ acpi_bus_generate_netlink_event("", dev_name(&toshiba_acpi->acpi_dev->dev), + 0x92, 0); + } + +@@ -3250,8 +3249,7 @@ static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *data) + break; + } + +- acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class, +- dev_name(&acpi_dev->dev), ++ acpi_bus_generate_netlink_event("", dev_name(&acpi_dev->dev), + event, (event == 0x80) ? + dev->last_key_event : 0); + } +diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c +--- a/drivers/platform/x86/toshiba_bluetooth.c ++++ b/drivers/platform/x86/toshiba_bluetooth.c +@@ -252,10 +252,8 @@ static int toshiba_bt_rfkill_probe(struct platform_device *pdev) + platform_set_drvdata(pdev, bt_dev); + + result = toshiba_bluetooth_sync_status(bt_dev); +- if (result) { +- kfree(bt_dev); +- return result; +- } ++ if (result) ++ goto err_free_bt_dev; + + bt_dev->rfk = rfkill_alloc("Toshiba Bluetooth", + &pdev->dev, +diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c +--- a/drivers/platform/x86/toshiba_haps.c ++++ b/drivers/platform/x86/toshiba_haps.c +@@ -136,9 +136,7 @@ static void toshiba_haps_notify(acpi_handle handle, u32 event, void *data) + + pr_debug("Received event: 0x%x\n", event); + +- acpi_bus_generate_netlink_event(device->pnp.device_class, +- dev_name(&device->dev), +- event, 0); ++ acpi_bus_generate_netlink_event("", dev_name(&device->dev), event, 0); + } + + static void toshiba_haps_remove(struct platform_device *pdev) +diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c +--- a/drivers/platform/x86/uniwill/uniwill-acpi.c ++++ b/drivers/platform/x86/uniwill/uniwill-acpi.c +@@ -95,6 +95,9 @@ + + #define EC_ADDR_MAIN_FAN_RPM_2 0x0465 + ++#define EC_ADDR_SCREEN_STATUS 0x0466 ++#define SCREEN_SUSPENDED BIT(6) ++ + #define EC_ADDR_SECOND_FAN_RPM_1 0x046C + + #define EC_ADDR_SECOND_FAN_RPM_2 0x046D +@@ -109,9 +112,35 @@ + + #define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7 + ++#define EC_ADDR_OEM_9 0x0726 ++#define AC_AUTO_BOOT_ENABLE BIT(3) ++ + #define EC_ADDR_PROJECT_ID 0x0740 ++#define PROJECT_ID_NONE 0x00 ++#define PROJECT_ID_GI 0x01 ++#define PROJECT_ID_GJ 0x02 ++#define PROJECT_ID_GK 0x03 ++#define PROJECT_ID_GICN 0x04 ++#define PROJECT_ID_GJCN 0x05 ++#define PROJECT_ID_GK5CN_X 0x06 ++#define PROJECT_ID_GK7CN_S 0x07 ++#define PROJECT_ID_GK7CPCS_GK5CQ7Z 0x08 ++#define PROJECT_ID_PF 0x09 ++#define PROJECT_ID_GK5CP_4X_5X_6X 0x0A ++#define PROJECT_ID_IDP 0x0B ++#define PROJECT_ID_IDY_6Y 0x0C ++#define PROJECT_ID_IDY_7Y 0x0D ++#define PROJECT_ID_PF4MU_PF4MN_PF5MU 0x0E ++#define PROJECT_ID_CML_GAMING 0x0F ++#define PROJECT_ID_GK7NXXR 0x10 ++#define PROJECT_ID_GM5MU1Y 0x11 + #define PROJECT_ID_PH4TRX1 0x12 ++#define PROJECT_ID_PH4TUX1 0x13 ++#define PROJECT_ID_PH4TQX1 0x14 + #define PROJECT_ID_PH6TRX1 0x15 ++#define PROJECT_ID_PH6TQXX 0x16 ++#define PROJECT_ID_PHXAXXX 0x17 ++#define PROJECT_ID_PHXPXXX 0x18 + + #define EC_ADDR_AP_OEM 0x0741 + #define ENABLE_MANUAL_CTRL BIT(0) +@@ -214,6 +243,7 @@ + #define FAN_TABLE_OFFICE_MODE BIT(2) + #define FAN_V3 BIT(3) + #define DEFAULT_MODE BIT(4) ++#define ENABLE_CHINA_MODE BIT(6) + + #define EC_ADDR_PL1_SETTING 0x0783 + +@@ -225,11 +255,12 @@ + #define FAN_CURVE_LENGTH 5 + + #define EC_ADDR_KBD_STATUS 0x078C +-#define KBD_WHITE_ONLY BIT(0) // ~single color +-#define KBD_SINGLE_COLOR_OFF BIT(1) ++/* Unreliable on some models, use the device descriptor instead. */ ++#define KBD_WHITE_ONLY BIT(0) ++#define KBD_POWER_OFF BIT(1) + #define KBD_TURBO_LEVEL_MASK GENMASK(3, 2) + #define KBD_APPLY BIT(4) +-#define KBD_BRIGHTNESS GENMASK(7, 5) ++#define KBD_BRIGHTNESS_MASK GENMASK(7, 5) + + #define EC_ADDR_FAN_CTRL 0x078E + #define FAN3P5 BIT(1) +@@ -318,7 +349,9 @@ + #define FAN_TABLE_LENGTH 16 + + #define LED_CHANNELS 3 +-#define LED_MAX_BRIGHTNESS 200 ++ ++#define KBD_LED_CHANNELS 3 ++#define KBD_LED_MAX_INTENSITY 50 + + #define UNIWILL_FEATURE_FN_LOCK BIT(0) + #define UNIWILL_FEATURE_SUPER_KEY BIT(1) +@@ -333,6 +366,9 @@ + #define UNIWILL_FEATURE_SECONDARY_FAN BIT(9) + #define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(10) + #define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11) ++#define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12) ++#define UNIWILL_FEATURE_AC_AUTO_BOOT BIT(13) ++#define UNIWILL_FEATURE_USB_POWERSHARE BIT(14) + + enum usb_c_power_priority_options { + USB_C_POWER_PRIORITY_CHARGING = 0, +@@ -344,6 +380,7 @@ struct uniwill_data { + acpi_handle handle; + struct regmap *regmap; + unsigned int features; ++ u8 project_id; + struct acpi_battery_hook hook; + struct mutex battery_lock; /* Protects the list of currently registered batteries */ + union { +@@ -357,11 +394,25 @@ struct uniwill_data { + bool last_fn_lock_state; + bool last_super_key_enable_state; + bool last_touchpad_toggle_enable_state; ++ bool last_usb_powershare_high_state; + struct mutex super_key_lock; /* Protects the toggling of the super key lock state */ + struct list_head batteries; + struct mutex led_lock; /* Protects writes to the lightbar registers */ ++ u8 lightbar_max_brightness; + struct led_classdev_mc led_mc_cdev; + struct mc_subled led_mc_subled_info[LED_CHANNELS]; ++ bool kbd_led_single_color; ++ u8 kbd_led_max_brightness; ++ unsigned int last_kbd_status; ++ union { ++ struct { ++ /* Protects writes to the RGB keyboard backlight registers */ ++ struct mutex kbd_rgb_led_lock; ++ struct led_classdev_mc kbd_led_mc_cdev; ++ struct mc_subled kbd_led_mc_subled_info[KBD_LED_CHANNELS]; ++ }; ++ struct led_classdev kbd_led_cdev; ++ }; + struct mutex input_lock; /* Protects input sequence during notify */ + struct input_dev *input_device; + struct notifier_block nb; +@@ -376,6 +427,9 @@ struct uniwill_battery_entry { + + struct uniwill_device_descriptor { + unsigned int features; ++ bool kbd_led_single_color; ++ u8 kbd_led_max_brightness; ++ u8 lightbar_max_brightness; + /* Executed during driver probing */ + int (*probe)(struct uniwill_data *data); + }; +@@ -427,6 +481,9 @@ static const struct key_entry uniwill_keymap[] = { + { KE_KEY, UNIWILL_OSD_KBDILLUMDOWN, { KEY_KBDILLUMDOWN }}, + { KE_KEY, UNIWILL_OSD_KBDILLUMUP, { KEY_KBDILLUMUP }}, + ++ /* Reported when the EC changed the keyboard backlight brightness */ ++ { KE_IGNORE, UNIWILL_OSD_BACKLIGHT_LEVEL_CHANGE, { KEY_UNKNOWN }}, ++ + /* Reported when the user wants to toggle the microphone mute status */ + { KE_KEY, UNIWILL_OSD_MIC_MUTE, { KEY_MICMUTE }}, + +@@ -435,11 +492,6 @@ static const struct key_entry uniwill_keymap[] = { + + /* Reported when the user wants to toggle the brightness of the keyboard */ + { KE_KEY, UNIWILL_OSD_KBDILLUMTOGGLE, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL0, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL1, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL2, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL3, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL4, { KEY_KBDILLUMTOGGLE }}, + + /* FIXME: find out the exact meaning of those events */ + { KE_IGNORE, UNIWILL_OSD_BAT_CHARGE_FULL_24_H, { KEY_UNKNOWN }}, +@@ -448,6 +500,9 @@ static const struct key_entry uniwill_keymap[] = { + /* Reported when the user wants to toggle the benchmark mode status */ + { KE_IGNORE, UNIWILL_OSD_BENCHMARK_MODE_TOGGLE, { KEY_UNKNOWN }}, + ++ /* Reported when the screen is enabled/disabled during resume/suspend */ ++ { KE_IGNORE, UNIWILL_OSD_SCREEN_STATE_CHANGED, { KEY_UNKNOWN }}, ++ + /* Reported when the user wants to toggle the webcam */ + { KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }}, + +@@ -540,6 +595,7 @@ static const struct regmap_bus uniwill_ec_bus = { + static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case EC_ADDR_OEM_9: + case EC_ADDR_AP_OEM: + case EC_ADDR_LIGHTBAR_AC_CTRL: + case EC_ADDR_LIGHTBAR_AC_RED: +@@ -547,6 +603,11 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) + case EC_ADDR_LIGHTBAR_AC_BLUE: + case EC_ADDR_BIOS_OEM: + case EC_ADDR_TRIGGER: ++ case EC_ADDR_RGB_RED: ++ case EC_ADDR_RGB_GREEN: ++ case EC_ADDR_RGB_BLUE: ++ case EC_ADDR_BIOS_OEM_2: ++ case EC_ADDR_KBD_STATUS: + case EC_ADDR_OEM_4: + case EC_ADDR_CHARGE_CTRL: + case EC_ADDR_LIGHTBAR_BAT_CTRL: +@@ -574,6 +635,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) + case EC_ADDR_SECOND_FAN_RPM_1: + case EC_ADDR_SECOND_FAN_RPM_2: + case EC_ADDR_BAT_ALERT: ++ case EC_ADDR_OEM_9: + case EC_ADDR_PROJECT_ID: + case EC_ADDR_AP_OEM: + case EC_ADDR_LIGHTBAR_AC_CTRL: +@@ -583,8 +645,14 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) + case EC_ADDR_BIOS_OEM: + case EC_ADDR_PWM_1: + case EC_ADDR_PWM_2: ++ case EC_ADDR_SUPPORT_2: + case EC_ADDR_TRIGGER: + case EC_ADDR_SWITCH_STATUS: ++ case EC_ADDR_RGB_RED: ++ case EC_ADDR_RGB_GREEN: ++ case EC_ADDR_RGB_BLUE: ++ case EC_ADDR_BIOS_OEM_2: ++ case EC_ADDR_KBD_STATUS: + case EC_ADDR_OEM_4: + case EC_ADDR_CHARGE_CTRL: + case EC_ADDR_LIGHTBAR_BAT_CTRL: +@@ -616,8 +684,10 @@ static bool uniwill_volatile_reg(struct device *dev, unsigned int reg) + case EC_ADDR_BIOS_OEM: + case EC_ADDR_PWM_1: + case EC_ADDR_PWM_2: ++ case EC_ADDR_SUPPORT_2: + case EC_ADDR_TRIGGER: + case EC_ADDR_SWITCH_STATUS: ++ case EC_ADDR_KBD_STATUS: + case EC_ADDR_OEM_4: + case EC_ADDR_CHARGE_CTRL: + case EC_ADDR_USB_C_POWER_PRIORITY: +@@ -1077,6 +1147,109 @@ static int usb_c_power_priority_init(struct uniwill_data *data) + return 0; + } + ++static ssize_t ac_auto_boot_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ unsigned int regval; ++ bool enable; ++ int ret; ++ ++ ret = kstrtobool(buf, &enable); ++ if (ret < 0) ++ return ret; ++ ++ if (enable) ++ regval = AC_AUTO_BOOT_ENABLE; ++ else ++ regval = 0; ++ ++ ret = regmap_update_bits(data->regmap, EC_ADDR_OEM_9, AC_AUTO_BOOT_ENABLE, regval); ++ if (ret < 0) ++ return ret; ++ ++ return count; ++} ++ ++static ssize_t ac_auto_boot_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ unsigned int regval; ++ int ret; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_OEM_9, ®val); ++ if (ret < 0) ++ return ret; ++ ++ return sysfs_emit(buf, "%d\n", !!(regval & AC_AUTO_BOOT_ENABLE)); ++} ++ ++static DEVICE_ATTR_RW(ac_auto_boot); ++ ++static int uniwill_write_usb_powershare_high(struct uniwill_data *data, bool status) ++{ ++ unsigned int value; ++ ++ if (status) ++ value = TRIGGER_USB_CHARGING; ++ else ++ value = 0; ++ ++ /* ++ * Normaly this RMW-sequence could also trigger the super key toggle, ++ * but the EC seems to take care that those bits are always read as 0. ++ */ ++ return regmap_update_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_USB_CHARGING, value); ++} ++ ++static ssize_t usb_powershare_high_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ bool enable; ++ int ret; ++ ++ ret = kstrtobool(buf, &enable); ++ if (ret < 0) ++ return ret; ++ ++ ret = uniwill_write_usb_powershare_high(data, enable); ++ if (ret < 0) ++ return ret; ++ ++ return count; ++} ++ ++static int uniwill_read_usb_powershare_high(struct uniwill_data *data, bool *status) ++{ ++ unsigned int value; ++ int ret; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_TRIGGER, &value); ++ if (ret < 0) ++ return ret; ++ ++ *status = !!(value & TRIGGER_USB_CHARGING); ++ ++ return 0; ++} ++ ++static ssize_t usb_powershare_high_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ bool status; ++ int ret; ++ ++ ret = uniwill_read_usb_powershare_high(data, &status); ++ if (ret < 0) ++ return ret; ++ ++ return sysfs_emit(buf, "%d\n", status); ++} ++ ++static DEVICE_ATTR_RW(usb_powershare_high); ++ + static struct attribute *uniwill_attrs[] = { + /* Keyboard-related */ + &dev_attr_fn_lock.attr, +@@ -1088,6 +1261,8 @@ static struct attribute *uniwill_attrs[] = { + /* Power-management-related */ + &dev_attr_ctgp_offset.attr, + &dev_attr_usb_c_power_priority.attr, ++ &dev_attr_ac_auto_boot.attr, ++ &dev_attr_usb_powershare_high.attr, + NULL + }; + +@@ -1127,6 +1302,16 @@ static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *a + return attr->mode; + } + ++ if (attr == &dev_attr_ac_auto_boot.attr) { ++ if (uniwill_device_supports(data, UNIWILL_FEATURE_AC_AUTO_BOOT)) ++ return attr->mode; ++ } ++ ++ if (attr == &dev_attr_usb_powershare_high.attr) { ++ if (uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) ++ return attr->mode; ++ } ++ + return 0; + } + +@@ -1332,7 +1517,7 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br + + for (int i = 0; i < LED_CHANNELS; i++) { + /* Prevent the brightness values from overflowing */ +- value = min(LED_MAX_BRIGHTNESS, data->led_mc_subled_info[i].brightness); ++ value = min(data->lightbar_max_brightness, data->led_mc_subled_info[i].brightness); + ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); + if (ret < 0) + return ret; +@@ -1401,14 +1586,14 @@ static int uniwill_led_init(struct uniwill_data *data) + return ret; + + data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; +- data->led_mc_cdev.led_cdev.max_brightness = LED_MAX_BRIGHTNESS; ++ data->led_mc_cdev.led_cdev.max_brightness = data->lightbar_max_brightness; + data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT; + data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set; + + if (value & LIGHTBAR_S0_OFF) + data->led_mc_cdev.led_cdev.brightness = 0; + else +- data->led_mc_cdev.led_cdev.brightness = LED_MAX_BRIGHTNESS; ++ data->led_mc_cdev.led_cdev.brightness = data->lightbar_max_brightness; + + for (int i = 0; i < LED_CHANNELS; i++) { + data->led_mc_subled_info[i].color_index = color_indices[i]; +@@ -1421,7 +1606,7 @@ static int uniwill_led_init(struct uniwill_data *data) + * Make sure that the initial intensity value is not greater than + * the maximum brightness. + */ +- value = min(LED_MAX_BRIGHTNESS, value); ++ value = min(data->lightbar_max_brightness, value); + ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); + if (ret < 0) + return ret; +@@ -1441,6 +1626,246 @@ static int uniwill_led_init(struct uniwill_data *data) + &init_data); + } + ++static int uniwill_notify_kbd_led(struct uniwill_data *data, int brightness) ++{ ++ struct led_classdev *led_cdev; ++ int ret; ++ ++ if (data->kbd_led_single_color) ++ led_cdev = &data->kbd_led_cdev; ++ else ++ led_cdev = &data->kbd_led_mc_cdev.led_cdev; ++ ++ guard(mutex)(&led_cdev->led_access); ++ ++ /* Sync the LED brightness with the actual hardware state */ ++ ret = led_update_brightness(led_cdev); ++ if (ret < 0) ++ return ret; ++ ++ led_classdev_notify_brightness_hw_changed(led_cdev, brightness); ++ ++ return 0; ++} ++ ++#define KBD_LED_MASK (KBD_BRIGHTNESS_MASK | KBD_APPLY | KBD_POWER_OFF) ++ ++static int uniwill_kbd_led_write_brightness(struct uniwill_data *data, int brightness) ++{ ++ /* KBD_POWER_OFF is always implicitly cleared */ ++ unsigned int regval = FIELD_PREP(KBD_BRIGHTNESS_MASK, brightness) | KBD_APPLY; ++ ++ /* We must ensure that the "apply" bit is always written */ ++ return regmap_write_bits(data->regmap, EC_ADDR_KBD_STATUS, KBD_LED_MASK, regval); ++} ++ ++static int uniwill_kbd_led_read_brightness(struct uniwill_data *data) ++{ ++ unsigned int regval; ++ int ret; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); ++ if (ret < 0) ++ return ret; ++ ++ return min(FIELD_GET(KBD_BRIGHTNESS_MASK, regval), data->kbd_led_max_brightness); ++} ++ ++static int uniwill_kbd_led_brightness_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); ++ ++ return uniwill_kbd_led_write_brightness(data, brightness); ++} ++ ++static enum led_brightness uniwill_kbd_led_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); ++ ++ return uniwill_kbd_led_read_brightness(data); ++} ++ ++static const unsigned int uniwill_kbd_led_channel_to_reg[KBD_LED_CHANNELS] = { ++ EC_ADDR_RGB_RED, ++ EC_ADDR_RGB_GREEN, ++ EC_ADDR_RGB_BLUE, ++}; ++ ++static int uniwill_kbd_led_mc_brightness_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); ++ struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); ++ unsigned int min_intensity = 0; ++ unsigned int regval; ++ int ret; ++ ++ guard(mutex)(&data->kbd_rgb_led_lock); ++ ++ /* ++ * The EC interprets a RGB value of 0x000000 as a command to restore ++ * the device-specfic default RGB value. Work around this by writing ++ * a RGB value of 0x010101 (faint white) instead. ++ */ ++ if (data->kbd_led_mc_subled_info[0].intensity == 0 && ++ data->kbd_led_mc_subled_info[1].intensity == 0 && ++ data->kbd_led_mc_subled_info[2].intensity == 0) ++ min_intensity = 1; ++ ++ for (int i = 0; i < KBD_LED_CHANNELS; i++) { ++ regval = max(data->kbd_led_mc_subled_info[i].intensity, min_intensity); ++ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); ++ if (ret < 0) ++ return ret; ++ } ++ ++ ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); ++ if (ret < 0) ++ return ret; ++ ++ return uniwill_kbd_led_write_brightness(data, brightness); ++} ++ ++static enum led_brightness uniwill_kbd_led_mc_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); ++ struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); ++ ++ return uniwill_kbd_led_read_brightness(data); ++} ++ ++static int uniwill_white_kbd_led_init(struct uniwill_data *data) ++{ ++ struct led_init_data init_data = { ++ .default_label = "white:" LED_FUNCTION_KBD_BACKLIGHT, ++ .devicename = DRIVER_NAME, ++ .devname_mandatory = true, ++ }; ++ ++ data->kbd_led_cdev.max_brightness = data->kbd_led_max_brightness; ++ data->kbd_led_cdev.color = LED_COLOR_ID_WHITE; ++ data->kbd_led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; ++ data->kbd_led_cdev.brightness_set_blocking = uniwill_kbd_led_brightness_set; ++ data->kbd_led_cdev.brightness_get = uniwill_kbd_led_brightness_get; ++ ++ return devm_led_classdev_register_ext(data->dev, &data->kbd_led_cdev, &init_data); ++} ++ ++static int uniwill_rgb_kbd_led_init(struct uniwill_data *data) ++{ ++ unsigned int color_indices[KBD_LED_CHANNELS] = { ++ LED_COLOR_ID_RED, ++ LED_COLOR_ID_GREEN, ++ LED_COLOR_ID_BLUE, ++ }; ++ struct led_init_data init_data = { ++ .default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT, ++ .devicename = DRIVER_NAME, ++ .devname_mandatory = true, ++ }; ++ bool intensity_all_zeros = true; ++ bool needs_trigger = false; ++ unsigned int regval; ++ int ret; ++ ++ for (int i = 0; i < KBD_LED_CHANNELS; i++) { ++ data->kbd_led_mc_subled_info[i].color_index = color_indices[i]; ++ ++ ret = regmap_read(data->regmap, uniwill_kbd_led_channel_to_reg[i], ®val); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * Make sure that the initial intensity value is not greater than ++ * the maximum intensity. ++ */ ++ if (regval > KBD_LED_MAX_INTENSITY) { ++ regval = KBD_LED_MAX_INTENSITY; ++ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); ++ if (ret < 0) ++ return ret; ++ ++ needs_trigger = true; ++ } ++ ++ if (regval) ++ intensity_all_zeros = false; ++ ++ data->kbd_led_mc_subled_info[i].intensity = regval; ++ data->kbd_led_mc_subled_info[i].max_intensity = KBD_LED_MAX_INTENSITY; ++ data->kbd_led_mc_subled_info[i].channel = i; ++ } ++ ++ /* See uniwill_kbd_led_mc_brightness_set() for an explaination. */ ++ if (intensity_all_zeros) { ++ for (int i = 0; i < KBD_LED_CHANNELS; i++) { ++ data->kbd_led_mc_subled_info[i].intensity = 1; ++ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], 1); ++ if (ret < 0) ++ return ret; ++ } ++ ++ needs_trigger = true; ++ } ++ ++ if (needs_trigger) { ++ ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, ++ RGB_APPLY_COLOR); ++ if (ret < 0) ++ return ret; ++ } ++ ++ ret = devm_mutex_init(data->dev, &data->kbd_rgb_led_lock); ++ if (ret < 0) ++ return ret; ++ ++ data->kbd_led_mc_cdev.led_cdev.max_brightness = data->kbd_led_max_brightness; ++ data->kbd_led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; ++ data->kbd_led_mc_cdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; ++ data->kbd_led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_kbd_led_mc_brightness_set; ++ data->kbd_led_mc_cdev.led_cdev.brightness_get = uniwill_kbd_led_mc_brightness_get; ++ data->kbd_led_mc_cdev.subled_info = data->kbd_led_mc_subled_info; ++ data->kbd_led_mc_cdev.num_colors = KBD_LED_CHANNELS; ++ ++ return devm_led_classdev_multicolor_register_ext(data->dev, &data->kbd_led_mc_cdev, ++ &init_data); ++} ++ ++static int uniwill_kbd_led_init(struct uniwill_data *data) ++{ ++ unsigned int regval; ++ int ret; ++ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return 0; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, ®val); ++ if (ret < 0) ++ return ret; ++ ++ if (!(regval & CHINA_MODE)) { ++ ret = regmap_set_bits(data->regmap, EC_ADDR_BIOS_OEM_2, ENABLE_CHINA_MODE); ++ if (ret < 0) ++ return ret; ++ } ++ ++ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); ++ if (ret < 0) ++ return ret; ++ ++ regval |= KBD_APPLY; ++ regval &= ~KBD_POWER_OFF; ++ ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); ++ if (ret < 0) ++ return ret; ++ ++ if (data->kbd_led_single_color) ++ return uniwill_white_kbd_led_init(data); ++ ++ return uniwill_rgb_kbd_led_init(data); ++} ++ + static unsigned int uniwill_sanitize_battery_threshold(unsigned int value) + { + /* 0 means "charging threshold not active" */ +@@ -1789,6 +2214,31 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action + sysfs_notify(&data->dev->kobj, NULL, "fn_lock"); + + return NOTIFY_OK; ++ case UNIWILL_OSD_KB_LED_LEVEL0: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 0)); ++ case UNIWILL_OSD_KB_LED_LEVEL1: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 1)); ++ case UNIWILL_OSD_KB_LED_LEVEL2: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 2)); ++ case UNIWILL_OSD_KB_LED_LEVEL3: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 3)); ++ case UNIWILL_OSD_KB_LED_LEVEL4: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 4)); + default: + mutex_lock(&data->input_lock); + sparse_keymap_report_event(data->input_device, action, 1, true); +@@ -1842,6 +2292,7 @@ static int uniwill_ec_init(struct uniwill_data *data) + if (ret < 0) + return ret; + ++ data->project_id = value; + dev_dbg(data->dev, "Project ID: %u\n", value); + + ret = regmap_set_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); +@@ -1885,6 +2336,9 @@ static int uniwill_probe(struct platform_device *pdev) + return ret; + + data->features = device_descriptor.features; ++ data->kbd_led_single_color = device_descriptor.kbd_led_single_color; ++ data->kbd_led_max_brightness = device_descriptor.kbd_led_max_brightness; ++ data->lightbar_max_brightness = device_descriptor.lightbar_max_brightness; + + /* + * Some devices might need to perform some device-specific initialization steps +@@ -1905,6 +2359,10 @@ static int uniwill_probe(struct platform_device *pdev) + if (ret < 0) + return ret; + ++ ret = uniwill_kbd_led_init(data); ++ if (ret < 0) ++ return ret; ++ + ret = uniwill_hwmon_init(data); + if (ret < 0) + return ret; +@@ -1976,6 +2434,43 @@ static int uniwill_suspend_battery(struct uniwill_data *data) + return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl); + } + ++static int uniwill_suspend_kbd_led(struct uniwill_data *data) ++{ ++ unsigned int regval; ++ int ret; ++ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return 0; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * Save the current keyboard backlight settings in order to restore them ++ * during resume. We cannot use the regmap code for that since this register ++ * needs to be declared as volatile because the brightness can be changed ++ * by the EC. ++ */ ++ data->last_kbd_status = regval; ++ FIELD_MODIFY(KBD_BRIGHTNESS_MASK, ®val, 0); ++ regval |= KBD_APPLY | KBD_POWER_OFF; ++ ++ return regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); ++} ++ ++static int uniwill_suspend_usb_powershare(struct uniwill_data *data) ++{ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) ++ return 0; ++ ++ /* ++ * EC_ADDR_TRIGGER is marked as volatile, so we have to restore it ++ * ourselves. ++ */ ++ return uniwill_read_usb_powershare_high(data, &data->last_usb_powershare_high_state); ++} ++ + static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data) + { + if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) +@@ -2006,6 +2501,14 @@ static int uniwill_suspend(struct device *dev) + if (ret < 0) + return ret; + ++ ret = uniwill_suspend_kbd_led(data); ++ if (ret < 0) ++ return ret; ++ ++ ret = uniwill_suspend_usb_powershare(data); ++ if (ret < 0) ++ return ret; ++ + ret = uniwill_suspend_nvidia_ctgp(data); + if (ret < 0) + return ret; +@@ -2052,6 +2555,31 @@ static int uniwill_resume_battery(struct uniwill_data *data) + return 0; + } + ++static int uniwill_resume_kbd_led(struct uniwill_data *data) ++{ ++ int ret; ++ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return 0; ++ ++ ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, data->last_kbd_status | KBD_APPLY); ++ if (ret < 0) ++ return ret; ++ ++ if (data->kbd_led_single_color) ++ return 0; ++ ++ return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); ++} ++ ++static int uniwill_resume_usb_powershare(struct uniwill_data *data) ++{ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) ++ return 0; ++ ++ return uniwill_write_usb_powershare_high(data, data->last_usb_powershare_high_state); ++} ++ + static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data) + { + if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) +@@ -2096,6 +2624,14 @@ static int uniwill_resume(struct device *dev) + if (ret < 0) + return ret; + ++ ret = uniwill_resume_kbd_led(data); ++ if (ret < 0) ++ return ret; ++ ++ ret = uniwill_resume_usb_powershare(data); ++ if (ret < 0) ++ return ret; ++ + ret = uniwill_resume_nvidia_ctgp(data); + if (ret < 0) + return ret; +@@ -2126,13 +2662,30 @@ static struct platform_driver uniwill_driver = { + .shutdown = uniwill_shutdown, + }; + ++static struct uniwill_device_descriptor machenike_l16p_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_GPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | ++ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++ .kbd_led_single_color = false, ++ .kbd_led_max_brightness = 4, ++}; ++ + static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata = { + .features = UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_LIGHTBAR | + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN, ++ .lightbar_max_brightness = 36, + }; + + static struct uniwill_device_descriptor lapac71h_descriptor __initdata = { +@@ -2156,6 +2709,7 @@ static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = { + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN, ++ .lightbar_max_brightness = 200, + }; + + /* +@@ -2198,15 +2752,6 @@ static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __ini + UNIWILL_FEATURE_USB_C_POWER_PRIORITY, + }; + +-static struct uniwill_device_descriptor tux_featureset_3_descriptor __initdata = { +- .features = UNIWILL_FEATURE_FN_LOCK | +- UNIWILL_FEATURE_SUPER_KEY | +- UNIWILL_FEATURE_BATTERY_CHARGE_MODES | +- UNIWILL_FEATURE_CPU_TEMP | +- UNIWILL_FEATURE_PRIMARY_FAN | +- UNIWILL_FEATURE_SECONDARY_FAN, +-}; +- + static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | +@@ -2218,6 +2763,30 @@ static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __ini + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, + }; + ++static struct uniwill_device_descriptor tux_featureset_4_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_BATTERY_CHARGE_MODES | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++}; ++ ++static struct uniwill_device_descriptor tux_featureset_4_nvidia_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_BATTERY_CHARGE_MODES | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_GPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++}; ++ + static int phxtxx1_probe(struct uniwill_data *data) + { + unsigned int value; +@@ -2277,7 +2846,37 @@ static struct uniwill_device_descriptor pf5pu1g_descriptor __initdata = { + UNIWILL_FEATURE_PRIMARY_FAN, + }; + ++static struct uniwill_device_descriptor x4sp4nal_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_BATTERY_CHARGE_MODES | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++ .kbd_led_single_color = true, ++ .kbd_led_max_brightness = 2, ++}; ++ + static const struct dmi_system_id uniwill_dmi_table[] __initconst = { ++ { ++ .ident = "AiStone X4SP4NAL", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "AiStone"), ++ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X4SP4NAL"), ++ }, ++ .driver_data = &x4sp4nal_descriptor, ++ }, ++ { ++ .ident = "MACHENIKE L16 Pro", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "MACHENIKE"), ++ DMI_EXACT_MATCH(DMI_BOARD_NAME, "L16P"), ++ }, ++ .driver_data = &machenike_l16p_descriptor, ++ }, + { + .ident = "XMG FUSION 15 (L19)", + .matches = { +@@ -2310,6 +2909,13 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + }, + .driver_data = &lapqc71a_lapqc71b_descriptor, + }, ++ { ++ .ident = "Avell A60 MUV", ++ .matches = { ++ DMI_MATCH(DMI_PRODUCT_NAME, "A60 MUV"), ++ }, ++ .driver_data = &lapqc71a_lapqc71b_descriptor, ++ }, + { + .ident = "Intel NUC x15", + .matches = { +@@ -2388,7 +2994,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9", +@@ -2396,7 +3002,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", +@@ -2404,7 +3010,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", +@@ -2412,7 +3018,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel", +@@ -2420,7 +3026,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 15 Gen10 AMD", +@@ -2428,7 +3034,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", +@@ -2436,7 +3042,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", +@@ -2444,7 +3050,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 15 Gen10 Intel", +@@ -2452,7 +3058,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 16 Gen10 Intel", +@@ -2460,7 +3066,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Polaris 15 Gen1 AMD", +@@ -2620,7 +3226,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6", +@@ -2628,7 +3234,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", +@@ -2636,7 +3242,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", +@@ -2644,7 +3250,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6", +@@ -2652,7 +3258,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen7 AMD", +@@ -2660,7 +3266,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen7 Intel", +@@ -2668,7 +3274,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen7 Intel", +@@ -2676,7 +3282,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Book BA15 Gen10 AMD", +@@ -2745,6 +3351,12 @@ static int __init uniwill_init(void) + if (force) { + /* Assume that the device supports all features except the charge limit */ + device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT; ++ /* Some models only have a (white) single color keyboard backlight */ ++ device_descriptor.kbd_led_single_color = false; ++ /* Some models only support 3 brightness levels */ ++ device_descriptor.kbd_led_max_brightness = 4; ++ /* Some models only support 36 brightness levels per color component */ ++ device_descriptor.lightbar_max_brightness = 200; + pr_warn("Enabling potentially unsupported features\n"); + } + +diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h +--- a/drivers/platform/x86/uniwill/uniwill-wmi.h ++++ b/drivers/platform/x86/uniwill/uniwill-wmi.h +@@ -113,6 +113,8 @@ + + #define UNIWILL_OSD_BENCHMARK_MODE_TOGGLE 0xC0 + ++#define UNIWILL_OSD_SCREEN_STATE_CHANGED 0xCC ++ + #define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF + + #define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0 +diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c +--- a/drivers/platform/x86/xo15-ebook.c ++++ b/drivers/platform/x86/xo15-ebook.c +@@ -19,13 +19,10 @@ + + #define MODULE_NAME "xo15-ebook" + +-#define XO15_EBOOK_CLASS MODULE_NAME + #define XO15_EBOOK_TYPE_UNKNOWN 0x00 + #define XO15_EBOOK_NOTIFY_STATUS 0x80 + +-#define XO15_EBOOK_SUBCLASS "ebook" + #define XO15_EBOOK_HID "XO15EBK" +-#define XO15_EBOOK_DEVICE_NAME "EBook Switch" + + MODULE_DESCRIPTION("OLPC XO-1.5 ebook switch driver"); + MODULE_LICENSE("GPL"); +@@ -105,12 +102,9 @@ static int ebook_switch_probe(struct platform_device *pdev) + if (!id) + return dev_err_probe(dev, -ENODEV, "Unsupported hid\n"); + +- strscpy(acpi_device_name(device), XO15_EBOOK_DEVICE_NAME); +- strscpy(acpi_device_class(device), XO15_EBOOK_CLASS "/" XO15_EBOOK_SUBCLASS); +- + snprintf(button->phys, sizeof(button->phys), "%s/button/input0", id->id); + +- input->name = acpi_device_name(device); ++ input->name = "EBook Switch"; + input->phys = button->phys; + input->id.bustype = BUS_HOST; + +diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c +--- a/drivers/power/supply/surface_battery.c ++++ b/drivers/power/supply/surface_battery.c +@@ -852,9 +852,14 @@ static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { + }; + + static const struct ssam_device_id surface_battery_match[] = { +- { SSAM_SDEV(BAT, SAM, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, +- { SSAM_SDEV(BAT, KIP, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, +- { }, ++ { ++ SSAM_SDEV(BAT, SAM, 0x01, 0x00), ++ .driver_data = (unsigned long)&spwr_psy_props_bat1, ++ }, { ++ SSAM_SDEV(BAT, KIP, 0x01, 0x00), ++ .driver_data = (unsigned long)&spwr_psy_props_bat2_sb3, ++ }, ++ { } + }; + MODULE_DEVICE_TABLE(ssam, surface_battery_match); + +diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c +--- a/drivers/power/supply/surface_charger.c ++++ b/drivers/power/supply/surface_charger.c +@@ -260,8 +260,11 @@ static const struct spwr_psy_properties spwr_psy_props_adp1 = { + }; + + static const struct ssam_device_id surface_ac_match[] = { +- { SSAM_SDEV(BAT, SAM, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, +- { }, ++ { ++ SSAM_SDEV(BAT, SAM, 0x01, 0x01), ++ .driver_data = (unsigned long)&spwr_psy_props_adp1, ++ }, ++ { } + }; + MODULE_DEVICE_TABLE(ssam, surface_ac_match); + +diff --git a/include/linux/amd-pmf-io.h b/include/linux/amd-pmf-io.h +--- a/include/linux/amd-pmf-io.h ++++ b/include/linux/amd-pmf-io.h +@@ -52,15 +52,6 @@ struct amd_sfh_info { + u32 laptop_placement; + }; + +-enum laptop_placement { +- LP_UNKNOWN = 0, +- ON_TABLE, +- ON_LAP_MOTION, +- IN_BAG, +- OUT_OF_BAG, +- LP_UNDEFINED, +-}; +- + /** + * struct amd_pmf_npu_metrics: Get NPU metrics data from PMF driver + * @npuclk_freq: NPU clock frequency [MHz] +@@ -69,6 +60,7 @@ enum laptop_placement { + * @mpnpuclk_freq: MPNPU [MHz] + * @npu_reads: NPU read bandwidth [MB/sec] + * @npu_writes: NPU write bandwidth [MB/sec] ++ * @npu_temp: NPU temperature [C] + */ + struct amd_pmf_npu_metrics { + u16 npuclk_freq; +@@ -77,6 +69,7 @@ struct amd_pmf_npu_metrics { + u16 mpnpuclk_freq; + u16 npu_reads; + u16 npu_writes; ++ u16 npu_temp; + }; + + int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op); +diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h +--- a/include/linux/platform_data/x86/asus-wmi.h ++++ b/include/linux/platform_data/x86/asus-wmi.h +@@ -203,6 +203,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval); + int asus_hid_register_listener(struct asus_hid_listener *cdev); + void asus_hid_unregister_listener(struct asus_hid_listener *cdev); + int asus_hid_event(enum asus_hid_event event); ++bool asus_wmi_custom_fan_curve_is_enabled(void); + #else + static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status) + { +@@ -234,6 +235,10 @@ static inline int asus_hid_event(enum asus_hid_event event) + { + return -ENODEV; + } ++static inline bool asus_wmi_custom_fan_curve_is_enabled(void) ++{ ++ return false; ++} + #endif + + #endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */ +diff --git a/include/uapi/linux/amd-pmf.h b/include/uapi/linux/amd-pmf.h +new file mode 100644 +--- /dev/null ++++ b/include/uapi/linux/amd-pmf.h +@@ -0,0 +1,204 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ ++/* ++ * AMD Platform Management Framework (PMF) UAPI Header ++ * ++ * Copyright (c) 2026, Advanced Micro Devices, Inc. ++ * All Rights Reserved. ++ * ++ * This file defines the user-space API for interacting with the AMD PMF ++ * driver. It provides ioctl interfaces to query platform-specific metrics ++ * such as power source, slider position, platform type, laptop placement, ++ * and various BIOS input/output parameters. ++ */ ++ ++#ifndef _UAPI_LINUX_AMD_PMF_H ++#define _UAPI_LINUX_AMD_PMF_H ++ ++#include ++#include ++#include ++ ++/** ++ * AMD_PMF_IOC_MAGIC - Magic number for AMD PMF ioctl commands ++ * ++ * This magic number uniquely identifies AMD PMF ioctl operations. ++ */ ++#define AMD_PMF_IOC_MAGIC 'p' ++ ++/** ++ * IOCTL_AMD_PMF_POPULATE_DATA - ioctl command to retrieve PMF metrics data ++ * ++ * This ioctl command is used to populate the amd_pmf_info structure ++ * with the requested PMF metrics information. ++ */ ++#define IOCTL_AMD_PMF_POPULATE_DATA _IOWR(AMD_PMF_IOC_MAGIC, 0x00, __u64) ++ ++#define AMD_PMF_BIOS_PARAMS_MAX 10 ++ ++/* AMD PMF feature flags - bitmask indicating supported features */ ++#define AMD_PMF_FEAT_AUTO_MODE BIT(0) ++#define AMD_PMF_FEAT_STATIC_POWER_SLIDER BIT(1) ++#define AMD_PMF_FEAT_POLICY_BUILDER BIT(2) ++#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC BIT(3) ++#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC BIT(4) ++ ++/** ++ * enum amd_pmf_laptop_placement - Describes the physical placement of the laptop ++ * @AMD_PMF_LP_UNKNOWN: Placement cannot be determined ++ * @AMD_PMF_ON_TABLE: Laptop is placed on a stable surface like a table or desk ++ * @AMD_PMF_ON_LAP_MOTION: Laptop is on a lap with detected motion ++ * @AMD_PMF_IN_BAG: Laptop is detected to be inside a bag or case ++ * @AMD_PMF_OUT_OF_BAG: Laptop has been removed from bag or case ++ * @AMD_PMF_LP_UNDEFINED: Placement state is undefined ++ * ++ * This enumeration represents the physical placement state of the laptop ++ * as detected by platform sensors. Used for adaptive power management ++ * and thermal policies. ++ */ ++enum amd_pmf_laptop_placement { ++ AMD_PMF_LP_UNKNOWN, ++ AMD_PMF_ON_TABLE, ++ AMD_PMF_ON_LAP_MOTION, ++ AMD_PMF_IN_BAG, ++ AMD_PMF_OUT_OF_BAG, ++ AMD_PMF_LP_UNDEFINED, ++}; ++ ++/** ++ * enum amd_pmf_ta_slider - Trusted Application power slider positions ++ * @AMD_PMF_TA_BEST_BATTERY: Maximum battery savings, minimal performance ++ * @AMD_PMF_TA_BETTER_BATTERY: Balanced towards battery life ++ * @AMD_PMF_TA_BETTER_PERFORMANCE: Balanced towards performance ++ * @AMD_PMF_TA_BEST_PERFORMANCE: Maximum performance, higher power consumption ++ * @AMD_PMF_TA_MAX: Sentinel value indicating maximum enum value ++ * ++ * This enumeration defines the power slider positions used by the ++ * AMD PMF Trusted Application for dynamic power management decisions. ++ * These correspond to the Windows power slider UI positions. ++ */ ++enum amd_pmf_ta_slider { ++ AMD_PMF_TA_BEST_BATTERY, ++ AMD_PMF_TA_BETTER_BATTERY, ++ AMD_PMF_TA_BETTER_PERFORMANCE, ++ AMD_PMF_TA_BEST_PERFORMANCE, ++ AMD_PMF_TA_MAX, ++}; ++ ++/** ++ * enum amd_pmf_platform_type - Describes the physical form factor orientation ++ * @AMD_PMF_PTYPE_UNKNOWN: Platform type cannot be determined ++ * @AMD_PMF_LID_CLOSE: Laptop lid is closed ++ * @AMD_PMF_CLAMSHELL: Traditional laptop mode with keyboard and screen ++ * @AMD_PMF_FLAT: Device is lying flat on a surface ++ * @AMD_PMF_TENT: Device is in tent mode (keyboard folded back, standing) ++ * @AMD_PMF_STAND: Device is propped up in stand orientation ++ * @AMD_PMF_TABLET: Device is in tablet mode with keyboard hidden ++ * @AMD_PMF_BOOK: Device is in book reading orientation ++ * @AMD_PMF_PRESENTATION: Device is in presentation mode ++ * @AMD_PMF_PULL_FWD: Screen is pulled forward towards user ++ * @AMD_PMF_PTYPE_INVALID: Invalid platform type marker ++ * ++ * This enumeration describes the current physical orientation or form ++ * factor of convertible/2-in-1 devices. Used for optimizing power and ++ * thermal management based on device posture. ++ */ ++enum amd_pmf_platform_type { ++ AMD_PMF_PTYPE_UNKNOWN, ++ AMD_PMF_LID_CLOSE, ++ AMD_PMF_CLAMSHELL, ++ AMD_PMF_FLAT, ++ AMD_PMF_TENT, ++ AMD_PMF_STAND, ++ AMD_PMF_TABLET, ++ AMD_PMF_BOOK, ++ AMD_PMF_PRESENTATION, ++ AMD_PMF_PULL_FWD, ++ AMD_PMF_PTYPE_INVALID = 0xf, ++}; ++ ++static inline const char *amd_pmf_get_platform_type(unsigned int platform_type) ++{ ++ switch (platform_type) { ++ case AMD_PMF_CLAMSHELL: ++ return "CLAMSHELL"; ++ case AMD_PMF_LID_CLOSE: ++ return "LID_CLOSE"; ++ case AMD_PMF_FLAT: ++ return "FLAT"; ++ case AMD_PMF_TENT: ++ return "TENT"; ++ case AMD_PMF_STAND: ++ return "STAND"; ++ case AMD_PMF_TABLET: ++ return "TABLET"; ++ case AMD_PMF_BOOK: ++ return "BOOK"; ++ case AMD_PMF_PRESENTATION: ++ return "PRESENTATION"; ++ case AMD_PMF_PULL_FWD: ++ return "PULL_FWD"; ++ default: ++ return "UNKNOWN"; ++ } ++} ++ ++static inline const char *amd_pmf_get_laptop_placement(unsigned int device_state) ++{ ++ switch (device_state) { ++ case AMD_PMF_ON_TABLE: ++ return "ON_TABLE"; ++ case AMD_PMF_ON_LAP_MOTION: ++ return "ON_LAP_MOTION"; ++ case AMD_PMF_IN_BAG: ++ return "IN_BAG"; ++ case AMD_PMF_OUT_OF_BAG: ++ return "OUT_OF_BAG"; ++ default: ++ return "UNKNOWN"; ++ } ++} ++ ++static inline const char *amd_pmf_get_slider_position(unsigned int state) ++{ ++ switch (state) { ++ case AMD_PMF_TA_BEST_PERFORMANCE: ++ return "PERFORMANCE"; ++ case AMD_PMF_TA_BETTER_PERFORMANCE: ++ return "BALANCED"; ++ case AMD_PMF_TA_BEST_BATTERY: ++ return "POWER_SAVER"; ++ case AMD_PMF_TA_BETTER_BATTERY: ++ return "BALANCED_BATTERY"; ++ default: ++ return "Unknown TA Slider State"; ++ } ++} ++ ++struct amd_pmf_info { ++ __u64 size; ++ ++ /* Feature info */ ++ __u32 features_supported; ++ ++ /* Power and state info */ ++ __u32 platform_type; ++ __u32 power_source; ++ __u32 laptop_placement; ++ __u32 lid_state; ++ __u32 user_presence; ++ __u32 slider_position; ++ ++ /* Thermal and power metrics */ ++ __s32 skin_temp; ++ __u32 gfx_busy; ++ __s32 ambient_light; ++ __u32 avg_c0_residency; ++ __u32 max_c0_residency; ++ __u32 socket_power; ++ ++ /* BIOS parameters */ ++ __u32 bios_input[AMD_PMF_BIOS_PARAMS_MAX]; ++ __u32 bios_output[AMD_PMF_BIOS_PARAMS_MAX]; ++}; ++ ++#endif /* _UAPI_LINUX_AMD_PMF_H */ diff --git a/pkgbuilds/linux-omarchy/0801-amd-pmf-util-unbind-use-after-free.patch b/pkgbuilds/linux-omarchy/0801-amd-pmf-util-unbind-use-after-free.patch new file mode 100644 index 0000000..029ebd2 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0801-amd-pmf-util-unbind-use-after-free.patch @@ -0,0 +1,34 @@ +diff --git a/drivers/platform/x86/amd/pmf/util.c b/drivers/platform/x86/amd/pmf/util.c +index 1111111..2222222 100644 +--- a/drivers/platform/x86/amd/pmf/util.c ++++ b/drivers/platform/x86/amd/pmf/util.c +@@ -79,9 +79,9 @@ static int amd_pmf_populate_data(struct amd_pmf_dev *pdev, struct amd_pmf_info * + + static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) + { +- struct amd_pmf_dev *pdev = filp->private_data; + void __user *argp = (void __user *)arg; + struct amd_pmf_info info = {}; ++ struct amd_pmf_dev *pdev; + size_t copy_size; + __u64 user_size; + int ret; +@@ -94,6 +94,10 @@ static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long + return -EFAULT; + + guard(mutex)(&pmf_util_lock); ++ pdev = pmf_dev_handle; ++ if (!pdev) ++ return -ENODEV; ++ + ret = amd_pmf_populate_data(pdev, &info); + if (ret) + return ret; +@@ -115,7 +119,6 @@ static int amd_pmf_open(struct inode *inode, struct file *filp) + if (!pmf_dev_handle) + return -ENODEV; + +- filp->private_data = pmf_dev_handle; + return 0; + } + diff --git a/pkgbuilds/linux-omarchy/0850-futex-wait-multiple.patch b/pkgbuilds/linux-omarchy/0850-futex-wait-multiple.patch new file mode 100644 index 0000000..d4ab07f --- /dev/null +++ b/pkgbuilds/linux-omarchy/0850-futex-wait-multiple.patch @@ -0,0 +1,142 @@ +diff --git a/include/uapi/linux/futex.h b/include/uapi/linux/futex.h +--- a/include/uapi/linux/futex.h ++++ b/include/uapi/linux/futex.h +@@ -22,6 +22,7 @@ + #define FUTEX_WAIT_REQUEUE_PI 11 + #define FUTEX_CMP_REQUEUE_PI 12 + #define FUTEX_LOCK_PI2 13 ++#define FUTEX_WAIT_MULTIPLE 31 + + #define FUTEX_PRIVATE_FLAG 128 + #define FUTEX_CLOCK_REALTIME 256 +@@ -126,6 +127,18 @@ struct futex_waitv { + __u32 __reserved; + }; + ++/** ++ * struct futex_wait_block - Block of futexes to be waited for ++ * @uaddr: User address of the futex ++ * @val: Futex value expected by userspace ++ * @bitset: Bitset for the optional bitmasked wakeup ++ */ ++struct futex_wait_block { ++ __u32 __user *uaddr; ++ __u32 val; ++ __u32 bitset; ++}; ++ + /* + * Support for robust futexes: the kernel cleans up held futexes at + * thread exit time. +diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c +--- a/kernel/futex/syscalls.c ++++ b/kernel/futex/syscalls.c +@@ -169,6 +169,7 @@ static __always_inline bool futex_cmd_has_timeout(u32 cmd) + case FUTEX_LOCK_PI2: + case FUTEX_WAIT_BITSET: + case FUTEX_WAIT_REQUEUE_PI: ++ case FUTEX_WAIT_MULTIPLE: + return true; + } + return false; +@@ -181,13 +182,79 @@ futex_init_timeout(u32 cmd, u32 op, struct timespec64 *ts, ktime_t *t) + return -EINVAL; + + *t = timespec64_to_ktime(*ts); +- if (cmd == FUTEX_WAIT) ++ if (cmd == FUTEX_WAIT || cmd == FUTEX_WAIT_MULTIPLE) + *t = ktime_add_safe(ktime_get(), *t); + else if (cmd != FUTEX_LOCK_PI && !(op & FUTEX_CLOCK_REALTIME)) + *t = timens_ktime_to_host(CLOCK_MONOTONIC, *t); + return 0; + } + ++/** ++ * futex_read_wait_block - Read an array of futex_wait_block from userspace ++ * @uaddr: Userspace address of the block ++ * @count: Number of blocks to be read ++ * ++ * This function creates and allocate an array of futex_q (we zero it to ++ * initialize the fields) and then, for each futex_wait_block element from ++ * userspace, fill a futex_q element with proper values. ++ */ ++inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) ++{ ++ unsigned int i; ++ struct futex_vector *futexv; ++ struct futex_wait_block fwb; ++ struct futex_wait_block __user *entry = ++ (struct futex_wait_block __user *)uaddr; ++ ++ if (!count || count > FUTEX_WAITV_MAX) ++ return ERR_PTR(-EINVAL); ++ ++ futexv = kcalloc(count, sizeof(*futexv), GFP_KERNEL); ++ if (!futexv) ++ return ERR_PTR(-ENOMEM); ++ ++ for (i = 0; i < count; i++) { ++ if (copy_from_user(&fwb, &entry[i], sizeof(fwb))) { ++ kfree(futexv); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ futexv[i].w.flags = FUTEX_32; ++ futexv[i].w.val = fwb.val; ++ futexv[i].w.uaddr = (uintptr_t) (fwb.uaddr); ++ futexv[i].q = futex_q_init; ++ } ++ ++ return futexv; ++} ++ ++int futex_wait_multiple(struct futex_vector *vs, unsigned int count, ++ struct hrtimer_sleeper *to); ++ ++int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) ++{ ++ int ret; ++ struct futex_vector *vs; ++ struct hrtimer_sleeper *to = NULL, timeout; ++ ++ to = futex_setup_timer(abs_time, &timeout, 0, 0); ++ ++ vs = futex_read_wait_block(uaddr, count); ++ ++ if (IS_ERR(vs)) ++ return PTR_ERR(vs); ++ ++ ret = futex_wait_multiple(vs, count, abs_time ? to : NULL); ++ kfree(vs); ++ ++ if (to) { ++ hrtimer_cancel(&to->timer); ++ destroy_hrtimer_on_stack(&to->timer); ++ } ++ ++ return ret; ++} ++ + SYSCALL_DEFINE6(futex, u32 __user *, uaddr, int, op, u32, val, + const struct __kernel_timespec __user *, utime, + u32 __user *, uaddr2, u32, val3) +@@ -207,6 +274,9 @@ SYSCALL_DEFINE6(futex, u32 __user *, uaddr, int, op, u32, val, + tp = &t; + } + ++ if (cmd == FUTEX_WAIT_MULTIPLE) ++ return futex_opcode_31(tp, uaddr, val); ++ + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } + +@@ -521,6 +591,9 @@ SYSCALL_DEFINE6(futex_time32, u32 __user *, uaddr, int, op, u32, val, + tp = &t; + } + ++ if (cmd == FUTEX_WAIT_MULTIPLE) ++ return futex_opcode_31(tp, uaddr, val); ++ + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } + #endif /* CONFIG_COMPAT_32BIT_TIME */ diff --git a/pkgbuilds/linux-omarchy/0851-futex-wait-multiple-fixes.patch b/pkgbuilds/linux-omarchy/0851-futex-wait-multiple-fixes.patch new file mode 100644 index 0000000..671934a --- /dev/null +++ b/pkgbuilds/linux-omarchy/0851-futex-wait-multiple-fixes.patch @@ -0,0 +1,35 @@ +diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c +--- a/kernel/futex/syscalls.c ++++ b/kernel/futex/syscalls.c +@@ -198,7 +198,7 @@ + * initialize the fields) and then, for each futex_wait_block element from + * userspace, fill a futex_q element with proper values. + */ +-inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) ++static inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) + { + unsigned int i; + struct futex_vector *futexv; +@@ -231,19 +231,18 @@ + int futex_wait_multiple(struct futex_vector *vs, unsigned int count, + struct hrtimer_sleeper *to); + +-int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) ++static int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) + { + int ret; + struct futex_vector *vs; + struct hrtimer_sleeper *to = NULL, timeout; + +- to = futex_setup_timer(abs_time, &timeout, 0, 0); +- + vs = futex_read_wait_block(uaddr, count); +- + if (IS_ERR(vs)) + return PTR_ERR(vs); + ++ to = futex_setup_timer(abs_time, &timeout, 0, 0); ++ + ret = futex_wait_multiple(vs, count, abs_time ? to : NULL); + kfree(vs); + diff --git a/pkgbuilds/linux-omarchy/0852-futex-wait-multiple-abi-fixes.patch b/pkgbuilds/linux-omarchy/0852-futex-wait-multiple-abi-fixes.patch new file mode 100644 index 0000000..b0d6ba3 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0852-futex-wait-multiple-abi-fixes.patch @@ -0,0 +1,88 @@ +diff --git a/include/uapi/linux/futex.h b/include/uapi/linux/futex.h +--- a/include/uapi/linux/futex.h ++++ b/include/uapi/linux/futex.h +@@ -134,7 +134,7 @@ + * @bitset: Bitset for the optional bitmasked wakeup + */ + struct futex_wait_block { +- __u32 __user *uaddr; ++ __aligned_u64 uaddr; + __u32 val; + __u32 bitset; + }; +diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c +--- a/kernel/futex/syscalls.c ++++ b/kernel/futex/syscalls.c +@@ -198,7 +198,8 @@ + * initialize the fields) and then, for each futex_wait_block element from + * userspace, fill a futex_q element with proper values. + */ +-static inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) ++static inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count, ++ unsigned int flags) + { + unsigned int i; + struct futex_vector *futexv; +@@ -219,10 +220,17 @@ + return ERR_PTR(-EFAULT); + } + +- futexv[i].w.flags = FUTEX_32; ++ if (!fwb.bitset) { ++ kfree(futexv); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ futexv[i].w.flags = flags; + futexv[i].w.val = fwb.val; +- futexv[i].w.uaddr = (uintptr_t) (fwb.uaddr); ++ futexv[i].w.uaddr = fwb.uaddr; + futexv[i].q = futex_q_init; ++ futexv[i].q.bitset = fwb.bitset; ++ futexv[i].q.wake = futex_wake_mark; + } + + return futexv; +@@ -231,17 +239,21 @@ + int futex_wait_multiple(struct futex_vector *vs, unsigned int count, + struct hrtimer_sleeper *to); + +-static int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) ++static int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count, int op) + { + int ret; + struct futex_vector *vs; + struct hrtimer_sleeper *to = NULL, timeout; ++ unsigned int flags = futex_to_flags(op); ++ ++ if (flags & FLAGS_CLOCKRT) ++ return -ENOSYS; + +- vs = futex_read_wait_block(uaddr, count); ++ vs = futex_read_wait_block(uaddr, count, flags); + if (IS_ERR(vs)) + return PTR_ERR(vs); + +- to = futex_setup_timer(abs_time, &timeout, 0, 0); ++ to = futex_setup_timer(abs_time, &timeout, flags, current->timer_slack_ns); + + ret = futex_wait_multiple(vs, count, abs_time ? to : NULL); + kfree(vs); +@@ -274,7 +286,7 @@ + } + + if (cmd == FUTEX_WAIT_MULTIPLE) +- return futex_opcode_31(tp, uaddr, val); ++ return futex_opcode_31(tp, uaddr, val, op); + + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } +@@ -591,7 +603,7 @@ + } + + if (cmd == FUTEX_WAIT_MULTIPLE) +- return futex_opcode_31(tp, uaddr, val); ++ return futex_opcode_31(tp, uaddr, val, op); + + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } diff --git a/pkgbuilds/linux-omarchy/8201-xe-shrinker-return-freed-page-count.patch b/pkgbuilds/linux-omarchy/8201-xe-shrinker-return-freed-page-count.patch new file mode 100644 index 0000000..f340536 --- /dev/null +++ b/pkgbuilds/linux-omarchy/8201-xe-shrinker-return-freed-page-count.patch @@ -0,0 +1,132 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -54,13 +54,14 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea + write_unlock(&shrinker->lock); + } + +-static s64 __xe_shrinker_walk(struct xe_device *xe, ++static int __xe_shrinker_walk(struct xe_device *xe, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, +- unsigned long to_scan, unsigned long *scanned) ++ unsigned long to_scan, unsigned long *scanned, ++ unsigned long *freed) + { + unsigned int mem_type; +- s64 freed = 0, lret; ++ s64 lret; + + for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) { + struct ttm_resource_manager *man = ttm_manager_type(&xe->ttm, mem_type); +@@ -82,7 +83,7 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, + if (lret < 0) + return lret; + +- freed += lret; ++ *freed += lret; + if (*scanned >= to_scan) + break; + } +@@ -90,7 +91,7 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, + xe_assert(xe, !IS_ERR(ttm_bo)); + } + +- return freed; ++ return 0; + } + + /* +@@ -99,40 +100,35 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, + * add writeback. This avoids stalls and explicit writebacks with light or + * moderate memory pressure. + */ +-static s64 xe_shrinker_walk(struct xe_device *xe, ++static int xe_shrinker_walk(struct xe_device *xe, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, +- unsigned long to_scan, unsigned long *scanned) ++ unsigned long to_scan, unsigned long *scanned, ++ unsigned long *freed) + { + bool no_wait_gpu = true; + struct xe_bo_shrink_flags save_flags = flags; +- s64 lret, freed; ++ int ret; + + swap(no_wait_gpu, ctx->no_wait_gpu); + save_flags.writeback = false; +- lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned); ++ ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, freed); + swap(no_wait_gpu, ctx->no_wait_gpu); +- if (lret < 0 || *scanned >= to_scan) +- return lret; ++ if (ret || *scanned >= to_scan) ++ return ret; + +- freed = lret; + if (!ctx->no_wait_gpu) { +- lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned); +- if (lret < 0) +- return lret; +- freed += lret; +- if (*scanned >= to_scan) +- return freed; ++ ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, ++ freed); ++ if (ret || *scanned >= to_scan) ++ return ret; + } + +- if (flags.writeback) { +- lret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned); +- if (lret < 0) +- return lret; +- freed += lret; +- } ++ if (flags.writeback) ++ ret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned, ++ freed); + +- return freed; ++ return ret; + } + + static unsigned long +@@ -214,7 +210,6 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + bool runtime_pm; + bool purgeable; + bool can_backup = !!(sc->gfp_mask & __GFP_FS); +- s64 lret; + + nr_to_scan = sc->nr_to_scan; + +@@ -225,12 +220,9 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + /* Might need runtime PM. Try to wake early if it looks like it. */ + runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup); + +- if (purgeable && nr_scanned < nr_to_scan) { +- lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, +- nr_to_scan, &nr_scanned); +- if (lret >= 0) +- freed += lret; +- } ++ if (purgeable && nr_scanned < nr_to_scan) ++ xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; + if (nr_scanned >= nr_to_scan || !can_backup) +@@ -242,10 +234,8 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + + shrink_flags.purge = false; + +- lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, +- nr_to_scan, &nr_scanned); +- if (lret >= 0) +- freed += lret; ++ xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; + out: diff --git a/pkgbuilds/linux-omarchy/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch b/pkgbuilds/linux-omarchy/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch new file mode 100644 index 0000000..29ccecf --- /dev/null +++ b/pkgbuilds/linux-omarchy/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch @@ -0,0 +1,149 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -54,13 +54,33 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea + write_unlock(&shrinker->lock); + } + +-static int __xe_shrinker_walk(struct xe_device *xe, ++static bool __xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker) ++{ ++ struct xe_device *xe = shrinker->xe; ++ ++ if (xe_pm_runtime_get_if_active(xe)) ++ return true; ++ ++ if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) { ++ xe_pm_runtime_get(xe); ++ return true; ++ } ++ ++ queue_work(xe->unordered_wq, &shrinker->pm_worker); ++ ++ return false; ++} ++ ++static int __xe_shrinker_walk(struct xe_shrinker *shrinker, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, + unsigned long to_scan, unsigned long *scanned, + unsigned long *freed) + { ++ struct xe_device *xe = shrinker->xe; + unsigned int mem_type; ++ bool rpm = false; ++ int ret = 0; + s64 lret; + + for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) { +@@ -75,23 +95,36 @@ static int __xe_shrinker_walk(struct xe_device *xe, + if (!man || !man->use_tt) + continue; + ++ if (mem_type != XE_PL_SYSTEM && !rpm && ++ xe_device_is_l2_flush_optimized(xe)) { ++ if (!__xe_shrinker_runtime_pm_get(shrinker)) ++ break; ++ rpm = true; ++ } ++ + ttm_bo_lru_for_each_reserved_guarded(&curs, man, &arg, ttm_bo) { + if (!ttm_bo_shrink_suitable(ttm_bo, ctx)) + continue; + + lret = xe_bo_shrink(ctx, ttm_bo, flags, scanned); +- if (lret < 0) +- return lret; ++ if (lret < 0) { ++ ret = lret; ++ goto out; ++ } + + *freed += lret; + if (*scanned >= to_scan) +- break; ++ goto out; + } + /* Trylocks should never error, just fail. */ + xe_assert(xe, !IS_ERR(ttm_bo)); + } + +- return 0; ++out: ++ if (rpm) ++ xe_pm_runtime_put(xe); ++ ++ return ret; + } + + /* +@@ -100,7 +133,7 @@ static int __xe_shrinker_walk(struct xe_device *xe, + * add writeback. This avoids stalls and explicit writebacks with light or + * moderate memory pressure. + */ +-static int xe_shrinker_walk(struct xe_device *xe, ++static int xe_shrinker_walk(struct xe_shrinker *shrinker, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, + unsigned long to_scan, unsigned long *scanned, +@@ -112,20 +145,21 @@ static int xe_shrinker_walk(struct xe_device *xe, + + swap(no_wait_gpu, ctx->no_wait_gpu); + save_flags.writeback = false; +- ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, freed); ++ ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned, ++ freed); + swap(no_wait_gpu, ctx->no_wait_gpu); + if (ret || *scanned >= to_scan) + return ret; + + if (!ctx->no_wait_gpu) { +- ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, ++ ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned, + freed); + if (ret || *scanned >= to_scan) + return ret; + } + + if (flags.writeback) +- ret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned, ++ ret = __xe_shrinker_walk(shrinker, ctx, flags, to_scan, scanned, + freed); + + return ret; +@@ -176,16 +210,7 @@ static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force, + return false; + } + +- if (!xe_pm_runtime_get_if_active(xe)) { +- if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) { +- xe_pm_runtime_get(xe); +- return true; +- } +- queue_work(xe->unordered_wq, &shrinker->pm_worker); +- return false; +- } +- +- return true; ++ return __xe_shrinker_runtime_pm_get(shrinker); + } + + static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) +@@ -221,7 +246,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup); + + if (purgeable && nr_scanned < nr_to_scan) +- xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ xe_shrinker_walk(shrinker, &ctx, shrink_flags, + nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; +@@ -234,7 +259,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + + shrink_flags.purge = false; + +- xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ xe_shrinker_walk(shrinker, &ctx, shrink_flags, + nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; diff --git a/pkgbuilds/linux-omarchy/8203-xe-shrinker-release-through-the-put-helper.patch b/pkgbuilds/linux-omarchy/8203-xe-shrinker-release-through-the-put-helper.patch new file mode 100644 index 0000000..6af456f --- /dev/null +++ b/pkgbuilds/linux-omarchy/8203-xe-shrinker-release-through-the-put-helper.patch @@ -0,0 +1,39 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -71,6 +71,12 @@ static bool __xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker) + return false; + } + ++static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) ++{ ++ if (runtime_pm) ++ xe_pm_runtime_put(shrinker->xe); ++} ++ + static int __xe_shrinker_walk(struct xe_shrinker *shrinker, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, +@@ -121,8 +127,7 @@ static int __xe_shrinker_walk(struct xe_shrinker *shrinker, + } + + out: +- if (rpm) +- xe_pm_runtime_put(xe); ++ xe_shrinker_runtime_pm_put(shrinker, rpm); + + return ret; + } +@@ -213,12 +218,6 @@ static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force, + return __xe_shrinker_runtime_pm_get(shrinker); + } + +-static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) +-{ +- if (runtime_pm) +- xe_pm_runtime_put(shrinker->xe); +-} +- + static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc) + { + struct xe_shrinker *shrinker = to_xe_shrinker(shrink); diff --git a/pkgbuilds/linux-omarchy/8204-mm-opportunistic-compaction.patch b/pkgbuilds/linux-omarchy/8204-mm-opportunistic-compaction.patch new file mode 100644 index 0000000..0a31aa0 --- /dev/null +++ b/pkgbuilds/linux-omarchy/8204-mm-opportunistic-compaction.patch @@ -0,0 +1,381 @@ +diff --git c/include/linux/mmzone.h i/include/linux/mmzone.h +--- c/include/linux/mmzone.h ++++ i/include/linux/mmzone.h +@@ -1465,6 +1465,39 @@ struct memory_failure_stats { + }; + #endif + ++/* ++ * Per-pgdat state machine for the kswapd "opportunistic compaction" hint. ++ * ++ * wakeup_kswapd() collapses the gfp flags of all wakers that arrive between ++ * two kswapd runs into a single tri-state, which kswapd then forwards to the ++ * shrinkers via shrink_control::opportunistic_compaction: ++ * ++ * KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION ++ * Initial state after kswapd consumes the previous value. No waker has ++ * been observed yet for the upcoming run. ++ * ++ * KSWAPD_NO_OPPORTUNISTIC_COMPACTION ++ * At least one waker is an order-0 allocation, or a high-order ++ * allocation that cannot tolerate failure (i.e., not eligible for ++ * opportunistic behaviour). Shrinkers must do their normal best-effort ++ * work; the hint is cleared. ++ * ++ * KSWAPD_OPPORTUNISTIC_COMPACTION ++ * All wakers seen so far are high-order allocations that may fail ++ * (__GFP_NORETRY or __GFP_RETRY_MAYFAIL, without __GFP_NOFAIL). Shrinkers ++ * may skip work that is unlikely to produce a contiguous high-order ++ * block (e.g., evicting working-set pages). ++ * ++ * The state is sticky in the "NO" direction within a single kswapd run: once ++ * any non-eligible waker is observed, subsequent eligible wakers cannot ++ * upgrade it back to KSWAPD_OPPORTUNISTIC_COMPACTION. ++ */ ++enum kswapd_opportunistic_compaction_type { ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION = 0, ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION, ++ KSWAPD_OPPORTUNISTIC_COMPACTION, ++}; ++ + /* + * On NUMA machines, each NUMA node would have a pg_data_t to describe + * it's memory layout. On UMA machines there is a single pglist_data which +@@ -1529,6 +1562,13 @@ typedef struct pglist_data { + #endif + struct task_struct *kswapd; /* Protected by kswapd_lock */ + int kswapd_order; ++ /* ++ * Aggregated opportunistic-compaction hint for the next kswapd run. ++ * Updated by wakeup_kswapd() based on the gfp flags / order of each ++ * waker, and consumed (and reset) by kswapd before balance_pgdat(). ++ * See enum kswapd_opportunistic_compaction_type for the state machine. ++ */ ++ atomic_t kswapd_opportunistic_compaction; + enum zone_type kswapd_highest_zoneidx; + + atomic_t kswapd_failures; /* Number of 'reclaimed == 0' runs */ +diff --git c/include/linux/shrinker.h i/include/linux/shrinker.h +--- c/include/linux/shrinker.h ++++ i/include/linux/shrinker.h +@@ -37,6 +37,26 @@ struct shrink_control { + /* current node being shrunk (for NUMA aware shrinkers) */ + int nid; + ++ /* ++ * Opportunistic compaction hint. ++ * ++ * Set by the reclaim path to tell shrinkers that this pass is ++ * driven by an order > 0 allocation that the caller is willing to ++ * have fail (e.g., __GFP_NORETRY / __GFP_RETRY_MAYFAIL without ++ * __GFP_NOFAIL). Such allocations only really benefit from ++ * shrinking when doing so frees up a contiguous, high-order block; ++ * thrashing working sets in the hope of producing one is typically ++ * counter-productive. ++ * ++ * Shrinkers that can produce naturally-aligned high-order folios ++ * (see shrink_control::order) should treat this as a hint to skip ++ * costly work that is unlikely to help compaction (for example, ++ * evicting hot/working-set pages just to free single pages). ++ * ++ * Only meaningful when @order > 0; ignored otherwise. ++ */ ++ bool opportunistic_compaction; ++ + /* + * How many objects scan_objects should scan and try to reclaim. + * This is reset before every call, so it is safe for callees +diff --git c/mm/internal.h i/mm/internal.h +--- c/mm/internal.h ++++ i/mm/internal.h +@@ -1767,7 +1767,7 @@ void __meminit __init_page_from_nid(unsigned long pfn, int nid); + + /* shrinker related functions */ + unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, +- int priority); ++ int priority, bool opportunistic_compaction); + + int shmem_add_to_page_cache(struct folio *folio, + struct address_space *mapping, +diff --git c/mm/shrinker.c i/mm/shrinker.c +--- c/mm/shrinker.c ++++ i/mm/shrinker.c +@@ -474,7 +474,7 @@ static unsigned long do_shrink_slab(struct shrink_control *shrinkctl, + + #ifdef CONFIG_MEMCG + static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, +- struct mem_cgroup *memcg, int priority) ++ struct mem_cgroup *memcg, int priority, bool opportunistic_compaction) + { + struct shrinker_info *info; + unsigned long ret, freed = 0; +@@ -536,6 +536,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + .gfp_mask = gfp_mask, + .nid = nid, + .memcg = memcg, ++ .opportunistic_compaction = opportunistic_compaction, + }; + struct shrinker *shrinker; + int shrinker_id = calc_shrinker_id(index, offset); +@@ -595,7 +596,8 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + } + #else /* !CONFIG_MEMCG */ + static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, +- struct mem_cgroup *memcg, int priority) ++ struct mem_cgroup *memcg, int priority, ++ bool opportunistic_compaction) + { + return 0; + } +@@ -607,6 +609,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + * @nid: node whose slab caches to target + * @memcg: memory cgroup whose slab caches to target + * @priority: the reclaim priority ++ * @opportunistic_compaction: do compaction opportunistically (e.g., do not swap working sets) + * + * Call the shrink functions to age shrinkable caches. + * +@@ -622,7 +625,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + * Returns the number of reclaimed slab objects. + */ + unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, +- int priority) ++ int priority, bool opportunistic_compaction) + { + unsigned long ret, freed = 0; + struct shrinker *shrinker; +@@ -635,7 +638,8 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, + * oom. + */ + if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg)) +- return shrink_slab_memcg(gfp_mask, nid, memcg, priority); ++ return shrink_slab_memcg(gfp_mask, nid, memcg, priority, ++ opportunistic_compaction); + + /* + * lockless algorithm of global shrink. +@@ -664,6 +668,7 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, + .gfp_mask = gfp_mask, + .nid = nid, + .memcg = memcg, ++ .opportunistic_compaction = opportunistic_compaction, + }; + + if (!shrinker_try_get(shrinker)) +diff --git c/mm/vmscan.c i/mm/vmscan.c +--- c/mm/vmscan.c ++++ i/mm/vmscan.c +@@ -96,6 +96,14 @@ struct scan_control { + /* Swappiness value for proactive reclaim. Always use sc_swappiness()! */ + int *proactive_swappiness; + ++ /* ++ * Opportunistic compaction hint snapshotted from the pgdat at the ++ * start of this reclaim pass. Forwarded to shrinkers through ++ * shrink_control::opportunistic_compaction so they can skip ++ * non-productive work for failable high-order allocations. ++ */ ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction; ++ + /* Can active folios be deactivated as part of reclaim? */ + #define DEACTIVATE_ANON 1 + #define DEACTIVATE_FILE 2 +@@ -198,6 +206,29 @@ struct scan_control { + */ + int vm_swappiness = 60; + ++/* ++ * Is @gfp_flags a high-order allocation that is eligible for the ++ * "opportunistic compaction" treatment in kswapd / shrinkers? ++ * ++ * The caller must be willing to tolerate failure (__GFP_NORETRY or ++ * __GFP_RETRY_MAYFAIL) and must not have set __GFP_NOFAIL. For such ++ * allocations there is little value in burning working-set pages just to ++ * scrape together a single high-order block: if compaction can't easily ++ * succeed, the caller would rather see the allocation fail. ++ */ ++static bool gfp_opportunistic_compaction(gfp_t gfp_flags) ++{ ++ return (gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)) && ++ !(gfp_flags & __GFP_NOFAIL); ++} ++ ++static bool sc_opportunistic_compaction(struct scan_control *sc) ++{ ++ return sc->order && (sc->kswapd_opportunistic_compaction == ++ KSWAPD_OPPORTUNISTIC_COMPACTION || (!current_is_kswapd() && ++ gfp_opportunistic_compaction(sc->gfp_mask))); ++} ++ + static int sc_swappiness(struct scan_control *sc, struct mem_cgroup *memcg) + { + if (sc->proactive && sc->proactive_swappiness) +@@ -411,7 +442,7 @@ static unsigned long drop_slab_node(int nid) + + memcg = mem_cgroup_iter(NULL, NULL, NULL); + do { +- freed += shrink_slab(GFP_KERNEL, nid, memcg, 0); ++ freed += shrink_slab(GFP_KERNEL, nid, memcg, 0, false); + } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL); + + return freed; +@@ -5081,6 +5112,7 @@ static int shrink_one(struct lruvec *lruvec, struct scan_control *sc) + unsigned long reclaimed = sc->nr_reclaimed; + struct mem_cgroup *memcg = lruvec_memcg(lruvec); + struct pglist_data *pgdat = lruvec_pgdat(lruvec); ++ bool opportunistic_compaction = sc_opportunistic_compaction(sc); + + /* lru_gen_age_node() called mem_cgroup_calculate_protection() */ + if (mem_cgroup_below_min(NULL, memcg)) +@@ -5096,7 +5128,8 @@ static int shrink_one(struct lruvec *lruvec, struct scan_control *sc) + + need_rotate = try_to_shrink_lruvec(lruvec, sc); + +- shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, sc->priority); ++ shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, sc->priority, ++ opportunistic_compaction); + + if (!sc->proactive) + vmpressure(sc->gfp_mask, sc->order, memcg, false, +@@ -6163,6 +6196,7 @@ static void shrink_node_memcgs(pg_data_t *pgdat, struct scan_control *sc) + struct lruvec *lruvec = mem_cgroup_lruvec(memcg, pgdat); + unsigned long reclaimed; + unsigned long scanned; ++ bool opportunistic_compaction = sc_opportunistic_compaction(sc); + + /* + * This loop can become CPU-bound when target memcgs +@@ -6200,7 +6234,7 @@ static void shrink_node_memcgs(pg_data_t *pgdat, struct scan_control *sc) + shrink_lruvec(lruvec, sc); + + shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, +- sc->priority); ++ sc->priority, opportunistic_compaction); + + /* Record the group's reclaim efficiency */ + if (!sc->proactive) +@@ -7133,8 +7167,14 @@ clear_reclaim_active(pg_data_t *pgdat, int highest_zoneidx) + * found to have free_pages <= high_wmark_pages(zone), any page in that zone + * or lower is eligible for reclaim until at least one usable zone is + * balanced. ++ * ++ * @kswapd_opportunistic_compaction is the aggregated hint produced by ++ * wakeup_kswapd() for this run; it is propagated into scan_control so that ++ * shrinkers can skip costly work that is unlikely to help compaction when ++ * all wakers are failable high-order allocations. + */ +-static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx) ++static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx, ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction) + { + int i; + unsigned long nr_soft_reclaimed; +@@ -7148,6 +7188,7 @@ static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx) + .gfp_mask = GFP_KERNEL, + .order = order, + .may_unmap = 1, ++ .kswapd_opportunistic_compaction = kswapd_opportunistic_compaction, + }; + + trace_mm_vmscan_balance_pgdat_begin(pgdat->node_id, order, +@@ -7372,8 +7413,10 @@ static enum zone_type kswapd_highest_zoneidx(pg_data_t *pgdat, + return curr_idx == MAX_NR_ZONES ? prev_highest_zoneidx : curr_idx; + } + +-static void kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, +- unsigned int highest_zoneidx) ++static void ++kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, ++ unsigned int highest_zoneidx, ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction) + { + long remaining = 0; + DEFINE_WAIT(wait); +@@ -7419,6 +7462,11 @@ static void kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_o + + if (READ_ONCE(pgdat->kswapd_order) < reclaim_order) + WRITE_ONCE(pgdat->kswapd_order, reclaim_order); ++ ++ if (kswapd_opportunistic_compaction == ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION) ++ atomic_set(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION); + } + + finish_wait(&pgdat->kswapd_wait, &wait); +@@ -7475,6 +7523,7 @@ static int kswapd(void *p) + unsigned int highest_zoneidx = MAX_NR_ZONES - 1; + pg_data_t *pgdat = (pg_data_t *)p; + struct task_struct *tsk = current; ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction; + + /* + * Tell the memory management that we're a "memory allocator", +@@ -7492,6 +7541,8 @@ static int kswapd(void *p) + set_freezable(); + + WRITE_ONCE(pgdat->kswapd_order, 0); ++ atomic_set(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, MAX_NR_ZONES); + atomic_set(&pgdat->nr_writeback_throttled, 0); + for ( ; ; ) { +@@ -7500,13 +7551,18 @@ static int kswapd(void *p) + alloc_order = reclaim_order = READ_ONCE(pgdat->kswapd_order); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); ++ kswapd_opportunistic_compaction = ++ atomic_read(&pgdat->kswapd_opportunistic_compaction); + + kswapd_try_sleep: + kswapd_try_to_sleep(pgdat, alloc_order, reclaim_order, +- highest_zoneidx); ++ highest_zoneidx, kswapd_opportunistic_compaction); + + /* Read the new order and highest_zoneidx */ + alloc_order = READ_ONCE(pgdat->kswapd_order); ++ kswapd_opportunistic_compaction = ++ atomic_xchg(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); + WRITE_ONCE(pgdat->kswapd_order, 0); +@@ -7533,7 +7589,8 @@ static int kswapd(void *p) + trace_mm_vmscan_kswapd_wake(pgdat->node_id, highest_zoneidx, + alloc_order); + reclaim_order = balance_pgdat(pgdat, alloc_order, +- highest_zoneidx); ++ highest_zoneidx, ++ kswapd_opportunistic_compaction); + if (reclaim_order < alloc_order) + goto kswapd_try_sleep; + } +@@ -7571,6 +7628,28 @@ void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, + if (READ_ONCE(pgdat->kswapd_order) < order) + WRITE_ONCE(pgdat->kswapd_order, order); + ++ /* ++ * Fold this waker into the per-pgdat opportunistic-compaction hint ++ * that kswapd will pick up at the start of its next run. ++ * ++ * The state is sticky in the "NO" direction: once any waker in this ++ * batch is order-0 or a non-failable high-order allocation, the hint ++ * stays cleared until kswapd consumes it. Only when every waker so ++ * far is a failable high-order allocation do we set ++ * KSWAPD_OPPORTUNISTIC_COMPACTION, asking shrinkers to skip work ++ * that won't realistically help compaction. ++ */ ++ if (atomic_read(&pgdat->kswapd_opportunistic_compaction) != ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION) { ++ if (!order || !gfp_opportunistic_compaction(gfp_flags)) ++ atomic_set(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION); ++ else if (order && gfp_opportunistic_compaction(gfp_flags)) ++ atomic_cmpxchg(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION, ++ KSWAPD_OPPORTUNISTIC_COMPACTION); ++ } ++ + if (!waitqueue_active(&pgdat->kswapd_wait)) + return; + diff --git a/pkgbuilds/linux-omarchy/8205-mm-hint-uses-allocation-order.patch b/pkgbuilds/linux-omarchy/8205-mm-hint-uses-allocation-order.patch new file mode 100644 index 0000000..f579b27 --- /dev/null +++ b/pkgbuilds/linux-omarchy/8205-mm-hint-uses-allocation-order.patch @@ -0,0 +1,236 @@ +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h +@@ -1680,7 +1680,7 @@ enum kswapd_clear_hopeless_reason { + }; + + void wakeup_kswapd(struct zone *zone, gfp_t gfp_mask, int order, +- enum zone_type highest_zoneidx); ++ bool opportunistic, enum zone_type highest_zoneidx); + void kswapd_try_clear_hopeless(struct pglist_data *pgdat, + unsigned int order, int highest_zoneidx); + void kswapd_clear_hopeless(pg_data_t *pgdat, enum kswapd_clear_hopeless_reason reason); +diff --git a/include/linux/swap.h b/include/linux/swap.h +--- a/include/linux/swap.h ++++ b/include/linux/swap.h +@@ -348,7 +348,8 @@ extern void swap_setup(void); + /* linux/mm/vmscan.c */ + extern unsigned long zone_reclaimable_pages(struct zone *zone); + extern unsigned long try_to_free_pages(struct zonelist *zonelist, int order, +- gfp_t gfp_mask, nodemask_t *mask); ++ int alloc_order, gfp_t gfp_mask, ++ nodemask_t *mask); + unsigned long lruvec_lru_size(struct lruvec *lruvec, enum lru_list lru, int zone_idx); + + #define MEMCG_RECLAIM_MAY_SWAP (1 << 1) +diff --git a/mm/internal.h b/mm/internal.h +--- a/mm/internal.h ++++ b/mm/internal.h +@@ -1765,6 +1765,28 @@ void __meminit __init_single_page(struct page *page, unsigned long pfn, + unsigned long zone, int nid); + void __meminit __init_page_from_nid(unsigned long pfn, int nid); + ++/* ++ * Is this allocation eligible for the "opportunistic compaction" treatment ++ * in kswapd and the shrinkers? ++ * ++ * The caller must be willing to tolerate failure (__GFP_NORETRY or ++ * __GFP_RETRY_MAYFAIL) and must not have set __GFP_NOFAIL. For such ++ * allocations there is little value in burning working-set pages just to ++ * scrape together a single high-order block: if compaction can't easily ++ * succeed, the caller would rather see the allocation fail. ++ * ++ * @order is the order the caller asked for, not the order reclaim was ++ * promoted to. defrag_mode raises the reclaim order to pageblock_order, ++ * which says nothing about what the caller can tolerate. ++ */ ++static inline bool gfp_opportunistic_compaction(gfp_t gfp_flags, int order) ++{ ++ if (!order || (gfp_flags & __GFP_NOFAIL)) ++ return false; ++ ++ return !!(gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)); ++} ++ + /* shrinker related functions */ + unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, + int priority, bool opportunistic_compaction); +diff --git a/mm/migrate.c b/mm/migrate.c +--- a/mm/migrate.c ++++ b/mm/migrate.c +@@ -2727,7 +2727,7 @@ int migrate_misplaced_folio_prepare(struct folio *folio, + return -EAGAIN; + + wakeup_kswapd(pgdat->node_zones + z, 0, +- folio_order(folio), ZONE_MOVABLE); ++ folio_order(folio), false, ZONE_MOVABLE); + return -EAGAIN; + } + +diff --git a/mm/page_alloc.c b/mm/page_alloc.c +--- a/mm/page_alloc.c ++++ b/mm/page_alloc.c +@@ -3422,7 +3422,7 @@ struct page *rmqueue(struct zone *preferred_zone, + if ((alloc_flags & ALLOC_KSWAPD) && + unlikely(test_bit(ZONE_BOOSTED_WATERMARK, &zone->flags))) { + clear_bit(ZONE_BOOSTED_WATERMARK, &zone->flags); +- wakeup_kswapd(zone, 0, 0, zone_idx(zone)); ++ wakeup_kswapd(zone, 0, 0, false, zone_idx(zone)); + } + + VM_BUG_ON_PAGE(page && bad_range(zone, page), page); +@@ -4441,6 +4441,7 @@ static unsigned int check_retry_zonelist(unsigned int seq) + /* Perform direct synchronous page reclaim */ + static unsigned long + __perform_reclaim(gfp_t gfp_mask, unsigned int order, ++ unsigned int alloc_order, + const struct alloc_context *ac) + { + unsigned int noreclaim_flag; +@@ -4453,7 +4454,7 @@ __perform_reclaim(gfp_t gfp_mask, unsigned int order, + fs_reclaim_acquire(gfp_mask); + noreclaim_flag = memalloc_noreclaim_save(); + +- progress = try_to_free_pages(ac->zonelist, order, gfp_mask, ++ progress = try_to_free_pages(ac->zonelist, order, alloc_order, gfp_mask, + ac->nodemask); + + memalloc_noreclaim_restore(noreclaim_flag); +@@ -4480,7 +4481,7 @@ __alloc_pages_direct_reclaim(gfp_t gfp_mask, unsigned int order, + reclaim_order = max(order, pageblock_order); + + psi_memstall_enter(&pflags); +- *did_some_progress = __perform_reclaim(gfp_mask, reclaim_order, ac); ++ *did_some_progress = __perform_reclaim(gfp_mask, reclaim_order, order, ac); + if (unlikely(!(*did_some_progress))) + goto out; + +@@ -4512,19 +4513,24 @@ static void wake_all_kswapds(unsigned int order, gfp_t gfp_mask, + pg_data_t *last_pgdat = NULL; + enum zone_type highest_zoneidx = ac->highest_zoneidx; + unsigned int reclaim_order; ++ bool opportunistic; + + if (defrag_mode) + reclaim_order = max(order, pageblock_order); + else + reclaim_order = order; + ++ /* Classify what the caller asked for, not what reclaim was raised to. */ ++ opportunistic = gfp_opportunistic_compaction(gfp_mask, order); ++ + for_each_zone_zonelist_nodemask(zone, z, ac->zonelist, highest_zoneidx, + ac->nodemask) { + if (!managed_zone(zone)) + continue; + if (last_pgdat == zone->zone_pgdat) + continue; +- wakeup_kswapd(zone, gfp_mask, reclaim_order, highest_zoneidx); ++ wakeup_kswapd(zone, gfp_mask, reclaim_order, opportunistic, ++ highest_zoneidx); + last_pgdat = zone->zone_pgdat; + } + } +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -160,6 +160,12 @@ struct scan_control { + /* Allocation order */ + s8 order; + ++ /* ++ * The order the allocation asked for. @order above may have been ++ * raised to pageblock_order by defrag_mode. ++ */ ++ s8 alloc_order; ++ + /* Scan (total_size >> priority) pages at once */ + s8 priority; + +@@ -206,27 +212,19 @@ struct scan_control { + */ + int vm_swappiness = 60; + +-/* +- * Is @gfp_flags a high-order allocation that is eligible for the +- * "opportunistic compaction" treatment in kswapd / shrinkers? +- * +- * The caller must be willing to tolerate failure (__GFP_NORETRY or +- * __GFP_RETRY_MAYFAIL) and must not have set __GFP_NOFAIL. For such +- * allocations there is little value in burning working-set pages just to +- * scrape together a single high-order block: if compaction can't easily +- * succeed, the caller would rather see the allocation fail. +- */ +-static bool gfp_opportunistic_compaction(gfp_t gfp_flags) +-{ +- return (gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)) && +- !(gfp_flags & __GFP_NOFAIL); +-} +- + static bool sc_opportunistic_compaction(struct scan_control *sc) + { +- return sc->order && (sc->kswapd_opportunistic_compaction == +- KSWAPD_OPPORTUNISTIC_COMPACTION || (!current_is_kswapd() && +- gfp_opportunistic_compaction(sc->gfp_mask))); ++ if (!sc->order) ++ return false; ++ ++ if (sc->kswapd_opportunistic_compaction == ++ KSWAPD_OPPORTUNISTIC_COMPACTION) ++ return true; ++ ++ if (current_is_kswapd()) ++ return false; ++ ++ return gfp_opportunistic_compaction(sc->gfp_mask, sc->alloc_order); + } + + static int sc_swappiness(struct scan_control *sc, struct mem_cgroup *memcg) +@@ -6781,7 +6779,8 @@ static bool throttle_direct_reclaim(gfp_t gfp_mask, struct zonelist *zonelist, + } + + unsigned long try_to_free_pages(struct zonelist *zonelist, int order, +- gfp_t gfp_mask, nodemask_t *nodemask) ++ int alloc_order, gfp_t gfp_mask, ++ nodemask_t *nodemask) + { + unsigned long nr_reclaimed; + struct scan_control sc = { +@@ -6789,6 +6788,7 @@ unsigned long try_to_free_pages(struct zonelist *zonelist, int order, + .gfp_mask = current_gfp_context(gfp_mask), + .reclaim_idx = gfp_zone(gfp_mask), + .order = order, ++ .alloc_order = alloc_order, + .nodemask = nodemask, + .priority = DEF_PRIORITY, + .may_writepage = 1, +@@ -7608,7 +7608,7 @@ static int kswapd(void *p) + * needed. + */ + void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, +- enum zone_type highest_zoneidx) ++ bool opportunistic, enum zone_type highest_zoneidx) + { + pg_data_t *pgdat; + enum zone_type curr_idx; +@@ -7641,10 +7641,10 @@ void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, + */ + if (atomic_read(&pgdat->kswapd_opportunistic_compaction) != + KSWAPD_NO_OPPORTUNISTIC_COMPACTION) { +- if (!order || !gfp_opportunistic_compaction(gfp_flags)) ++ if (!opportunistic) + atomic_set(&pgdat->kswapd_opportunistic_compaction, + KSWAPD_NO_OPPORTUNISTIC_COMPACTION); +- else if (order && gfp_opportunistic_compaction(gfp_flags)) ++ else + atomic_cmpxchg(&pgdat->kswapd_opportunistic_compaction, + KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION, + KSWAPD_OPPORTUNISTIC_COMPACTION); +@@ -7942,6 +7942,7 @@ int node_reclaim(struct pglist_data *pgdat, gfp_t gfp_mask, unsigned int order) + .nr_to_reclaim = max(nr_pages, SWAP_CLUSTER_MAX), + .gfp_mask = current_gfp_context(gfp_mask), + .order = order, ++ .alloc_order = order, + .priority = NODE_RECLAIM_PRIORITY, + .may_writepage = !!(node_reclaim_mode & RECLAIM_WRITE), + .may_unmap = !!(node_reclaim_mode & RECLAIM_UNMAP), diff --git a/pkgbuilds/linux-omarchy/8206-mm-carry-order-and-hint-in-one-word.patch b/pkgbuilds/linux-omarchy/8206-mm-carry-order-and-hint-in-one-word.patch new file mode 100644 index 0000000..92b39ef --- /dev/null +++ b/pkgbuilds/linux-omarchy/8206-mm-carry-order-and-hint-in-one-word.patch @@ -0,0 +1,232 @@ +diff --git c/include/linux/mmzone.h i/include/linux/mmzone.h +--- c/include/linux/mmzone.h ++++ i/include/linux/mmzone.h +@@ -1498,6 +1498,32 @@ enum kswapd_opportunistic_compaction_type { + KSWAPD_OPPORTUNISTIC_COMPACTION, + }; + ++/* ++ * pgdat->kswapd_request holds the order and the hint above in one word. A ++ * waker folds both in with a single cmpxchg and kswapd takes both with a ++ * single exchange, so a run cannot serve one waker's order under another ++ * waker's hint. ++ */ ++#define KSWAPD_REQUEST_HINT_MASK 3 ++#define KSWAPD_REQUEST_ORDER_SHIFT 2 ++ ++static inline int kswapd_request(int order, ++ enum kswapd_opportunistic_compaction_type hint) ++{ ++ return (order << KSWAPD_REQUEST_ORDER_SHIFT) | hint; ++} ++ ++static inline int kswapd_request_order(int request) ++{ ++ return request >> KSWAPD_REQUEST_ORDER_SHIFT; ++} ++ ++static inline enum kswapd_opportunistic_compaction_type ++kswapd_request_hint(int request) ++{ ++ return request & KSWAPD_REQUEST_HINT_MASK; ++} ++ + /* + * On NUMA machines, each NUMA node would have a pg_data_t to describe + * it's memory layout. On UMA machines there is a single pglist_data which +@@ -1561,14 +1587,15 @@ typedef struct pglist_data { + struct mutex kswapd_lock; + #endif + struct task_struct *kswapd; /* Protected by kswapd_lock */ +- int kswapd_order; + /* +- * Aggregated opportunistic-compaction hint for the next kswapd run. +- * Updated by wakeup_kswapd() based on the gfp flags / order of each +- * waker, and consumed (and reset) by kswapd before balance_pgdat(). +- * See enum kswapd_opportunistic_compaction_type for the state machine. ++ * The next kswapd run's request: the highest order asked for since ++ * kswapd last consumed one, and the aggregated opportunistic- ++ * compaction hint, in one word. Updated by wakeup_kswapd() from the ++ * gfp flags and order of each waker, and consumed by kswapd before ++ * balance_pgdat(). See kswapd_request() and ++ * enum kswapd_opportunistic_compaction_type. + */ +- atomic_t kswapd_opportunistic_compaction; ++ atomic_t kswapd_request; + enum zone_type kswapd_highest_zoneidx; + + atomic_t kswapd_failures; /* Number of 'reclaimed == 0' runs */ +diff --git c/mm/mm_init.c i/mm/mm_init.c +--- c/mm/mm_init.c ++++ i/mm/mm_init.c +@@ -1553,7 +1553,8 @@ void __ref free_area_init_core_hotplug(struct pglist_data *pgdat) + * when it starts in the near future. + */ + pgdat->nr_zones = 0; +- pgdat->kswapd_order = 0; ++ atomic_set(&pgdat->kswapd_request, ++ kswapd_request(0, KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION)); + pgdat->kswapd_highest_zoneidx = 0; + pgdat->node_start_pfn = 0; + pgdat->node_present_pages = 0; +diff --git c/mm/vmscan.c i/mm/vmscan.c +--- c/mm/vmscan.c ++++ i/mm/vmscan.c +@@ -7413,6 +7413,49 @@ static enum zone_type kswapd_highest_zoneidx(pg_data_t *pgdat, + return curr_idx == MAX_NR_ZONES ? prev_highest_zoneidx : curr_idx; + } + ++/* ++ * How a caller votes on the opportunistic-compaction hint when it folds a ++ * request into pgdat->kswapd_request. ++ */ ++enum kswapd_vote { ++ KSWAPD_VOTE_NONE, ++ KSWAPD_VOTE_NO_OPPORTUNISTIC, ++ KSWAPD_VOTE_OPPORTUNISTIC, ++}; ++ ++/* ++ * Raise the requested order to @order and fold in @vote, in one step. ++ * ++ * The hint is sticky in the "NO" direction: once any waker votes against ++ * opportunistic treatment, the hint stays cleared until kswapd consumes the ++ * request. KSWAPD_VOTE_NONE raises the order and leaves the hint alone. ++ * ++ * Order and hint live in the same word, so a waker whose order kswapd ++ * observes always contributed its vote to the hint kswapd observes with it. ++ */ ++static void kswapd_request_fold(pg_data_t *pgdat, int order, ++ enum kswapd_vote vote) ++{ ++ int old = atomic_read(&pgdat->kswapd_request); ++ int new; ++ ++ do { ++ enum kswapd_opportunistic_compaction_type hint = ++ kswapd_request_hint(old); ++ int req_order = max(kswapd_request_order(old), order); ++ ++ if (vote == KSWAPD_VOTE_NO_OPPORTUNISTIC) ++ hint = KSWAPD_NO_OPPORTUNISTIC_COMPACTION; ++ else if (vote == KSWAPD_VOTE_OPPORTUNISTIC && ++ hint == KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION) ++ hint = KSWAPD_OPPORTUNISTIC_COMPACTION; ++ ++ new = kswapd_request(req_order, hint); ++ if (new == old) ++ return; ++ } while (!atomic_try_cmpxchg(&pgdat->kswapd_request, &old, new)); ++} ++ + static void + kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, + unsigned int highest_zoneidx, +@@ -7460,13 +7503,11 @@ kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, + kswapd_highest_zoneidx(pgdat, + highest_zoneidx)); + +- if (READ_ONCE(pgdat->kswapd_order) < reclaim_order) +- WRITE_ONCE(pgdat->kswapd_order, reclaim_order); +- +- if (kswapd_opportunistic_compaction == +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION) +- atomic_set(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION); ++ kswapd_request_fold(pgdat, reclaim_order, ++ kswapd_opportunistic_compaction == ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION ? ++ KSWAPD_VOTE_NO_OPPORTUNISTIC : ++ KSWAPD_VOTE_NONE); + } + + finish_wait(&pgdat->kswapd_wait, &wait); +@@ -7524,6 +7565,7 @@ static int kswapd(void *p) + pg_data_t *pgdat = (pg_data_t *)p; + struct task_struct *tsk = current; + enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction; ++ int request; + + /* + * Tell the memory management that we're a "memory allocator", +@@ -7540,32 +7582,30 @@ static int kswapd(void *p) + tsk->flags |= PF_MEMALLOC | PF_KSWAPD; + set_freezable(); + +- WRITE_ONCE(pgdat->kswapd_order, 0); +- atomic_set(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); ++ atomic_set(&pgdat->kswapd_request, ++ kswapd_request(0, KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION)); + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, MAX_NR_ZONES); + atomic_set(&pgdat->nr_writeback_throttled, 0); + for ( ; ; ) { + bool was_frozen; + +- alloc_order = reclaim_order = READ_ONCE(pgdat->kswapd_order); ++ request = atomic_read(&pgdat->kswapd_request); ++ alloc_order = reclaim_order = kswapd_request_order(request); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); +- kswapd_opportunistic_compaction = +- atomic_read(&pgdat->kswapd_opportunistic_compaction); ++ kswapd_opportunistic_compaction = kswapd_request_hint(request); + + kswapd_try_sleep: + kswapd_try_to_sleep(pgdat, alloc_order, reclaim_order, + highest_zoneidx, kswapd_opportunistic_compaction); + +- /* Read the new order and highest_zoneidx */ +- alloc_order = READ_ONCE(pgdat->kswapd_order); +- kswapd_opportunistic_compaction = +- atomic_xchg(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); ++ /* Take the new request and highest_zoneidx */ ++ request = atomic_xchg(&pgdat->kswapd_request, ++ kswapd_request(0, KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION)); ++ alloc_order = kswapd_request_order(request); ++ kswapd_opportunistic_compaction = kswapd_request_hint(request); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); +- WRITE_ONCE(pgdat->kswapd_order, 0); + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, MAX_NR_ZONES); + + if (kthread_freezable_should_stop(&was_frozen)) +@@ -7625,30 +7665,15 @@ void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, + if (curr_idx == MAX_NR_ZONES || curr_idx < highest_zoneidx) + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, highest_zoneidx); + +- if (READ_ONCE(pgdat->kswapd_order) < order) +- WRITE_ONCE(pgdat->kswapd_order, order); +- + /* +- * Fold this waker into the per-pgdat opportunistic-compaction hint +- * that kswapd will pick up at the start of its next run. +- * +- * The state is sticky in the "NO" direction: once any waker in this +- * batch is order-0 or a non-failable high-order allocation, the hint +- * stays cleared until kswapd consumes it. Only when every waker so +- * far is a failable high-order allocation do we set +- * KSWAPD_OPPORTUNISTIC_COMPACTION, asking shrinkers to skip work +- * that won't realistically help compaction. ++ * Fold this waker's order and its vote on the opportunistic-compaction ++ * hint into the request kswapd will pick up at the start of its next ++ * run. Both change together, so the run that serves this order also ++ * carries this waker's vote. + */ +- if (atomic_read(&pgdat->kswapd_opportunistic_compaction) != +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION) { +- if (!opportunistic) +- atomic_set(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION); +- else +- atomic_cmpxchg(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION, +- KSWAPD_OPPORTUNISTIC_COMPACTION); +- } ++ kswapd_request_fold(pgdat, order, ++ opportunistic ? KSWAPD_VOTE_OPPORTUNISTIC : ++ KSWAPD_VOTE_NO_OPPORTUNISTIC); + + if (!waitqueue_active(&pgdat->kswapd_wait)) + return; diff --git a/pkgbuilds/linux-omarchy/8207-mm-classify-huge-page-allocations-as-failable.patch b/pkgbuilds/linux-omarchy/8207-mm-classify-huge-page-allocations-as-failable.patch new file mode 100644 index 0000000..bfa7d1e --- /dev/null +++ b/pkgbuilds/linux-omarchy/8207-mm-classify-huge-page-allocations-as-failable.patch @@ -0,0 +1,28 @@ +diff --git c/mm/internal.h i/mm/internal.h +--- c/mm/internal.h ++++ i/mm/internal.h +@@ -1778,12 +1778,24 @@ void __meminit __init_page_from_nid(unsigned long pfn, int nid); + * @order is the order the caller asked for, not the order reclaim was + * promoted to. defrag_mode raises the reclaim order to pageblock_order, + * which says nothing about what the caller can tolerate. ++ * ++ * A transparent huge page allocation sets neither flag, yet a failed ++ * collapse or fault falls back to small pages. Recognise it by its mask. ++ * In this tree the complete GFP_TRANSHUGE_LIGHT set is carried by the ++ * huge page fault path, khugepaged and the large folio migration ++ * targets. The huge zero folio clears __GFP_MOVABLE and so keeps normal ++ * shrinker behaviour. The three other sites that combine __GFP_COMP ++ * with __GFP_NOMEMALLOC do not carry the movable, highmem and ++ * filesystem bits, so they do not match. + */ + static inline bool gfp_opportunistic_compaction(gfp_t gfp_flags, int order) + { + if (!order || (gfp_flags & __GFP_NOFAIL)) + return false; + ++ if ((gfp_flags & GFP_TRANSHUGE_LIGHT) == GFP_TRANSHUGE_LIGHT) ++ return true; ++ + return !!(gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)); + } + diff --git a/pkgbuilds/linux-omarchy/8208-mm-thp-deferred-split-uses-hint.patch b/pkgbuilds/linux-omarchy/8208-mm-thp-deferred-split-uses-hint.patch new file mode 100644 index 0000000..8de9983 --- /dev/null +++ b/pkgbuilds/linux-omarchy/8208-mm-thp-deferred-split-uses-hint.patch @@ -0,0 +1,21 @@ +diff --git a/mm/huge_memory.c b/mm/huge_memory.c +--- a/mm/huge_memory.c ++++ b/mm/huge_memory.c +@@ -4476,6 +4476,17 @@ static unsigned long deferred_split_count(struct shrinker *shrink, + { + unsigned long count; + ++ /* ++ * Splitting a partially mapped folio during an opportunistic ++ * high-order pass frees its unmapped tail pages but shatters the ++ * folio, so the pass works against the contiguous block it is ++ * reclaiming for. Report nothing rather than SHRINK_EMPTY: for a ++ * memcg aware shrinker SHRINK_EMPTY clears the memcg shrinker bit, ++ * which would also skip the next normal pass. ++ */ ++ if (sc->opportunistic_compaction) ++ return 0; ++ + count = list_lru_shrink_count(&deferred_split_lru, sc); + return count ?: SHRINK_EMPTY; + } diff --git a/pkgbuilds/linux-omarchy/8209-xe-shrinker-use-opportunistic-hint.patch b/pkgbuilds/linux-omarchy/8209-xe-shrinker-use-opportunistic-hint.patch new file mode 100644 index 0000000..0552be0 --- /dev/null +++ b/pkgbuilds/linux-omarchy/8209-xe-shrinker-use-opportunistic-hint.patch @@ -0,0 +1,39 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -174,10 +174,17 @@ static unsigned long + xe_shrinker_count(struct shrinker *shrink, struct shrink_control *sc) + { + struct xe_shrinker *shrinker = to_xe_shrinker(shrink); +- unsigned long num_pages; ++ unsigned long num_pages = 0; + bool can_backup = !!(sc->gfp_mask & __GFP_FS); + +- num_pages = ttm_backup_bytes_avail() >> PAGE_SHIFT; ++ /* ++ * Skip accounting backup-able pages when this is an opportunistic ++ * high-order pass: TTM backup work shrinks at native page granularity ++ * and is unlikely to produce the contiguous block the caller wants, ++ * so don't advertise it as reclaimable for this hint. ++ */ ++ if (!sc->opportunistic_compaction) ++ num_pages = ttm_backup_bytes_avail() >> PAGE_SHIFT; + read_lock(&shrinker->lock); + + if (can_backup) +@@ -249,7 +256,14 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; +- if (nr_scanned >= nr_to_scan || !can_backup) ++ /* ++ * Stop after the purge pass for opportunistic high-order reclaim: ++ * the subsequent backup/writeback pass works at native page order ++ * and is unlikely to free a contiguous high-order block, so doing ++ * it here would just churn working sets for no compaction benefit. ++ */ ++ if (nr_scanned >= nr_to_scan || !can_backup || ++ sc->opportunistic_compaction) + goto out; + + /* If we didn't wake before, try to do it now if needed. */ diff --git a/pkgbuilds/linux-omarchy/8210-xe-shrinker-single-backup-decision.patch b/pkgbuilds/linux-omarchy/8210-xe-shrinker-single-backup-decision.patch new file mode 100644 index 0000000..9daf161 --- /dev/null +++ b/pkgbuilds/linux-omarchy/8210-xe-shrinker-single-backup-decision.patch @@ -0,0 +1,75 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -170,27 +170,28 @@ static int xe_shrinker_walk(struct xe_shrinker *shrinker, + return ret; + } + ++static inline bool xe_shrinker_can_backup(const struct shrink_control *sc) ++{ ++ return (sc->gfp_mask & __GFP_FS) && !sc->opportunistic_compaction; ++} ++ + static unsigned long + xe_shrinker_count(struct shrinker *shrink, struct shrink_control *sc) + { + struct xe_shrinker *shrinker = to_xe_shrinker(shrink); + unsigned long num_pages = 0; +- bool can_backup = !!(sc->gfp_mask & __GFP_FS); + + /* +- * Skip accounting backup-able pages when this is an opportunistic +- * high-order pass: TTM backup work shrinks at native page granularity +- * and is unlikely to produce the contiguous block the caller wants, +- * so don't advertise it as reclaimable for this hint. ++ * Only advertise backup-able pages when the backup pass can run. TTM ++ * backup work shrinks at native page granularity and is unlikely to ++ * produce the contiguous block an opportunistic high-order caller ++ * wants, and it needs __GFP_FS. + */ +- if (!sc->opportunistic_compaction) +- num_pages = ttm_backup_bytes_avail() >> PAGE_SHIFT; + read_lock(&shrinker->lock); +- +- if (can_backup) +- num_pages = min_t(unsigned long, num_pages, shrinker->shrinkable_pages); +- else +- num_pages = 0; ++ if (xe_shrinker_can_backup(sc)) ++ num_pages = min_t(unsigned long, ++ ttm_backup_bytes_avail() >> PAGE_SHIFT, ++ shrinker->shrinkable_pages); + + num_pages += shrinker->purgeable_pages; + read_unlock(&shrinker->lock); +@@ -240,7 +241,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + }; + bool runtime_pm; + bool purgeable; +- bool can_backup = !!(sc->gfp_mask & __GFP_FS); ++ bool can_backup = xe_shrinker_can_backup(sc); + + nr_to_scan = sc->nr_to_scan; + +@@ -257,13 +258,15 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + + sc->nr_scanned = nr_scanned; + /* +- * Stop after the purge pass for opportunistic high-order reclaim: +- * the subsequent backup/writeback pass works at native page order +- * and is unlikely to free a contiguous high-order block, so doing +- * it here would just churn working sets for no compaction benefit. ++ * Stop after the purge pass when the backup pass may not run. The ++ * backup/writeback pass works at native page order and is unlikely to ++ * free a contiguous high-order block for an opportunistic caller, so ++ * doing it there would churn working sets for no compaction benefit. ++ * ++ * The per-walk runtime PM reference in __xe_shrinker_walk() is not ++ * affected. Only the outer backup-driven reference is. + */ +- if (nr_scanned >= nr_to_scan || !can_backup || +- sc->opportunistic_compaction) ++ if (nr_scanned >= nr_to_scan || !can_backup) + goto out; + + /* If we didn't wake before, try to do it now if needed. */ diff --git a/pkgbuilds/linux-omarchy/9999-bpftool-strip-wformat-bootstrap.patch b/pkgbuilds/linux-omarchy/9999-bpftool-strip-wformat-bootstrap.patch new file mode 100644 index 0000000..ff35b72 --- /dev/null +++ b/pkgbuilds/linux-omarchy/9999-bpftool-strip-wformat-bootstrap.patch @@ -0,0 +1,12 @@ +diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile +--- a/tools/bpf/bpftool/Makefile ++++ b/tools/bpf/bpftool/Makefile +@@ -99,7 +99,7 @@ endif + HOST_LDFLAGS := $(LDFLAGS) + + # Remove warnings for libbpf bootstrap build +-LIBBPF_BOOTSTRAP_CFLAGS := $(filter-out -W -Wall -Wextra -Wformat -Wformat-signedness,$(HOST_CFLAGS)) ++LIBBPF_BOOTSTRAP_CFLAGS := $(filter-out -W -Wall -Wextra -Wformat%,$(HOST_CFLAGS)) + + INSTALL ?= install + RM ?= rm -f diff --git a/pkgbuilds/linux-omarchy/PKGBUILD b/pkgbuilds/linux-omarchy/PKGBUILD new file mode 100644 index 0000000..3e1be67 --- /dev/null +++ b/pkgbuilds/linux-omarchy/PKGBUILD @@ -0,0 +1,726 @@ +# Maintainer: The Omarchy Authors +# Adopted from the Arch Linux linux package. + +pkgbase=linux-omarchy +pkgver=7.2.5 +pkgrel=3 +pkgdesc='Omarchy Linux' +url='https://omarchy.org' +arch=( + x86_64 +) +license=(GPL-2.0-only) +makedepends=( + bc + binutils + cpio + gettext + glibc + libelf + libgcc + openssl + pahole + perl + python + rust + rust-bindgen + rust-src + tar + xxhash + xz + zlib + zstd +) +options=( + !debug + !strip +) +_major=${pkgver%%.*} +if [[ $pkgver == *rc* ]]; then + _base=${pkgver%rc*} + _prev=${_base%.*}.$(( ${_base##*.} - 1 )) + _srcname=linux-${_prev%.0} + _rcpatch=patch-${pkgver//rc/-rc} +else + _srcname=linux-$pkgver +fi +source=( + https://cdn.kernel.org/pub/linux/kernel/v${_major}.x/${_srcname}.tar.{xz,sign} + ${_rcpatch:+https://cdn.kernel.org/pub/linux/kernel/v${_major}.x/stable-review/${_rcpatch}.xz} + ${_rcpatch:+https://cdn.kernel.org/pub/linux/kernel/v${_major}.x/stable-review/${_rcpatch}.sign} + 0010-archlinux-base.patch{,.sig} + 0120-tlbpull.patch{,.sig} + 0121-smp-preempt.patch{,.sig} + 0130-sched-detach-tasks.patch{,.sig} + 0131-sched-avg-idle.patch{,.sig} + 0140-sched-always-inline.patch{,.sig} + 0141-sched-urgent-fixes.patch{,.sig} + 0142-sched-itmt-no-debugfs-dependency.patch{,.sig} + 0143-sched-hybrid-cluster-balancing.patch{,.sig} + 0144-sched-nohz-idle-core.patch{,.sig} + 0145-sched-eevdf-tunables.patch{,.sig} + 0200-idle.patch{,.sig} + 0210-pstate.patch{,.sig} + 0211-amd-pstate-fixes.patch{,.sig} + 0212-amd-pstate-epp-cache.patch{,.sig} + 0250-zsmalloc.patch{,.sig} + 0260-mglru-exec-protect.patch{,.sig} + 0270-ksm-rmap-walk.patch{,.sig} + 0280-mm-updates.patch{,.sig} + 0290-zstd-bmi2-fallback-aliases.patch{,.sig} + 0291-zstd-bmi2-cpu-feature-dispatch.patch{,.sig} + 0292-crypto-zstd-defer-cstream-init.patch{,.sig} + 0293-crypto-zstd-defer-dstream-init.patch{,.sig} + 0295-af-alg-restrict.patch{,.sig} + 0296-x86-mm-pmd-modify-keep-dirty-bit.patch{,.sig} + 0300-btrfs.patch{,.sig} + 0301-btrfs-fixes.patch{,.sig} + 0302-btrfs-zstd-decompress-direct-to-page.patch{,.sig} + 0310-fuse-eof-zeroing.patch{,.sig} + 0311-fuse-perf.patch{,.sig} + 0312-fuse-writethrough-uptodate.patch{,.sig} + 0313-fuse-background-wakeup.patch{,.sig} + 0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch{,.sig} + 0360-gpu-mem-cgroup.patch{,.sig} + 0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch{,.sig} + 0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch{,.sig} + 0402-psr2-early-transport-panels.patch{,.sig} + 0411-drm-xe-display-no-stolen-framebuffers.patch{,.sig} + 0420-safe-window.patch{,.sig} + 0430-fbc.patch{,.sig} + 0440-xe3-peak-bandwidth.patch{,.sig} + 0450-amd-hdmi-vrr-allm.patch{,.sig} + 0451-amd-vtem-tmds-links.patch{,.sig} + 0452-amd-hdmi-frl-default.patch{,.sig} + 0460-vesa-displayid-dsc-bpp.patch{,.sig} + 0461-vesa-dsc-passthru-mode-match-fix.patch{,.sig} + 0472-amdgpu-userq-post-reset-error.patch{,.sig} + 0473-i915-ptl-cdclk-sanitize.patch{,.sig} + 0474-amd-display-oled-vesa-backlight.patch{,.sig} + 0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch{,.sig} + 0510-sound-updates.patch{,.sig} + 0511-sound-updates-fixes.patch{,.sig} + 0512-sound-fixes.patch{,.sig} + 0513-xps13-sof-quirk.patch{,.sig} + 0514-rt766-stream-config-type.patch{,.sig} + 0516-hda-realtek-rog-strix-g733zw-speakers.patch{,.sig} + 0517-asoc-amd-yc-acer-aspire-a314-23p.patch{,.sig} + 0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch{,.sig} + 0541-cvs-nova-lake-acpi-id.patch{,.sig} + 0542-media-cvs-wake-irq-without-claiming-gpio.patch{,.sig} + 0560-input.patch{,.sig} + 0565-i2c-asue140d-touchpad-100khz.patch{,.sig} + 0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch{,.sig} + 0600-usb4stream-fixes.patch{,.sig} + 0601-usb4stream-busy-poll.patch{,.sig} + 0610-typec-cable-altmode-check.patch{,.sig} + 0620-usb-string-sanitize.patch{,.sig} + 0650-wireguard-tstamp-type.patch{,.sig} + 0660-btusb-mediatek-mt7922-13d3-3625.patch{,.sig} + 0661-rtw89-command-offload-source.patch{,.sig} + 0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch{,.sig} + 0700-pci-target-speed-quirk.patch{,.sig} + 0750-applesmc-cache-race.patch{,.sig} + 0751-applesmc-key-backlight-workqueue-leak.patch{,.sig} + 0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch{,.sig} + 0800-platform-updates.patch{,.sig} + 0801-amd-pmf-util-unbind-use-after-free.patch{,.sig} + 0850-futex-wait-multiple.patch{,.sig} + 0851-futex-wait-multiple-fixes.patch{,.sig} + 0852-futex-wait-multiple-abi-fixes.patch{,.sig} + 8201-xe-shrinker-return-freed-page-count.patch{,.sig} + 8202-xe-shrinker-runtime-pm-for-non-system-memory.patch{,.sig} + 8203-xe-shrinker-release-through-the-put-helper.patch{,.sig} + 8204-mm-opportunistic-compaction.patch{,.sig} + 8205-mm-hint-uses-allocation-order.patch{,.sig} + 8206-mm-carry-order-and-hint-in-one-word.patch{,.sig} + 8207-mm-classify-huge-page-allocations-as-failable.patch{,.sig} + 8208-mm-thp-deferred-split-uses-hint.patch{,.sig} + 8209-xe-shrinker-use-opportunistic-hint.patch{,.sig} + 8210-xe-shrinker-single-backup-decision.patch{,.sig} + 9999-bpftool-strip-wformat-bootstrap.patch{,.sig} +) +source_x86_64=(config.x86_64) +validpgpkeys=( + ABAF11C65A2970B130ABE3C479BE3E4300411886 # Linus Torvalds + 647F28654894E3BD457199BE38DBBDC86092693E # Greg Kroah-Hartman + 12D27D5D8C8E9BF1AABC7C7C7C64768D3DE334E7 # Krzysztof Wilczynski +) +b2sums=('48551bee71cd02815136fb8abe7da4464c2e17c89ef35cb0c0530c8b969fe12127ca97ab6c656ea8e5b29f2c4a8fe4cc143a626f4cca6b972a8105991e4c6905' + 'SKIP' + '6d92fb81077232b8cd1ae500b3aabc71434792e750ea6126dc095e50d4278d3e42b3cfdc3c8bc693ea14b109f6adfa3a1ed095187fc5501962fbe8f049f867bc' + 'SKIP' + 'f6c00ac2400580dffe3605d2693809396e18185f0c59b21b86f1f22a1f8d6529c51e8cc2474bc40f74a96631f415da51521a8b35d5b096e9b9d164c2755fb091' + 'SKIP' + '6aa6ea3690f3e4e145818650537cd110d33720d662af0b3660d95e15a3f91e48c993e31117c0a2fde852f55a77a83e3a50a1a95c0df78a33ae75d43d46a9fe15' + 'SKIP' + 'f664077b274be12da33f0145ebea8fafda3f3e3bf96f449c31b9a56768d39b763c246394ee4ed144f7f2d1916c32085fa59dc18736bb4a28c94568e3e1919d56' + 'SKIP' + '7fb673924d268c0e3c416c7aa6200e96d62d156eb397832de12668b8a90d6b9c7311f1958e0f2a6d93de8e82fa3166082106c2bcbaaf42bb11b13bb83d6f732d' + 'SKIP' + '42597d4f8377d6f4c58c26c415b0621db16a37da89bc1eb079750eef1c3f7bf04c9a2ac6f334d6a3c4c439827df4aa1b92d53c15f6e3857f79d8be7eb79ea7bd' + 'SKIP' + '35075dae2576cb2b8f94df048bbd4fc6b667fc176310306f8939cf9263a50a9a558102af99c90746fa7b17d98c6a321c0cc36b2bdffab5fa714e3e1256be889b' + 'SKIP' + '3434a5bbcd2bd8b4e2013400bf4d771ad5c8295ec74f67ff7acaf917954a01d25931679a71ddb8995fd7703b0b9904db4e6fe5f384d3705cf4d7b80dbf5dba55' + 'SKIP' + 'bcf83ace482aa07e64ef6cdc1d6ae9b8f52ef79c8d15421e2d8525b1fcd3bd555be49fee48a87c2daa103a5f6a564046568722f03bf768f73dbd280acf59376d' + 'SKIP' + '38d2d63ca4eee7ce83d22e60ebeb9d7f41a019cf910a8a7d151e4a1eccb14478a0054e72ae6494c89e2d66effead44e59534899fd861fe05a68460ae2d2cd32a' + 'SKIP' + '64a2e489285c40df691f822dbca67e2b2f21446f300465f01bb589380173f6da182eda71d09a840e7ea3e14acae940d3cde66fc3b476ef6417c6e8479841cc56' + 'SKIP' + 'd1007ee452cdf92967ec7055f1f39a3007a69842040e12f5063a1d2f24fd6d1eeaff2357abf96d0e330541a4781f93fecbcb116a4c73371913582eb72b8f9466' + 'SKIP' + '0d3309140e496ad552d1a15d4fbf04b5ad03877bc73ae689f57554e91b7458578f2c88b6bbd37dd9fdf822a8d48c2cd6f961ec735ae5887f96fa51d908d203ee' + 'SKIP' + '13beae6f8d23a1f075410f50c09bc94f24f505571af1feb3cee58f732c0d649a9ce5939bc11642c4f3c8dfbd967939b2a0a205220abf06f10d559c4c43a946a3' + 'SKIP' + '2f24f88d8f960148046c36222fdaaadfed2ca88c60240fa1d111430898192ca45aeaa93a3afa24ad02b11c4dc8c3904a92f17c0284396f2f43352ad934a35056' + 'SKIP' + '9809e258eb684b80d508fa2f2fa270683b62bd1412ed05a970114aaa7d84c37c0beaea51f9ff8c6ee1bc7318c4aee3725e9a1131fefb51ba700801de6ac414c5' + 'SKIP' + 'f115e8c80d723f7c24e6b1638b801cd664bef20acb1d2f6e6f5612744b916278693011782228cd3f4d0518654b8d53309bc5b06bbe8ed722fab9995f369bf548' + 'SKIP' + '31abdd15ae3f9709d6912c90319a0b62d8380e1ae1169575c0e8e419f8ce264a7cf7c72558a85719cd5a1cae7341007adb3e3936268999ebd5215c2ed288f22c' + 'SKIP' + '4efb9ab5258aee01b07bc322b160d2f6e3571ff82c4f3e317cab152a919c67522629d56fb79d517da034373cd6924bc3a20c98073197c93590feb1a34e902358' + 'SKIP' + '55951c59286df43f069302b0b1036424812f80b45c5ea13219fce1bfc3e831731432c216288d722e44493c736cbf89a3e44801b5a4f1f242c17b5a89b236a5ef' + 'SKIP' + '737db39c2f7f46550de4d6d80659bd6c1fc2bf1ca42bd7bd6fc345fc90af995acd056051e12175ac6c957333629a017051d76d5359c871158c93f0a294fb8de5' + 'SKIP' + '4d0646c8ad139add6abb62d75308d2d7b5b07afbc74a0f0297ffc2148f0db38abaea16b048704187da49fe3ddee8370b10a7a41d6e62b665650cd79e8c392b4e' + 'SKIP' + 'bd7bce1bf0ee24ce201fe0e5f0b8a6c829a84d224800547cf97a893326381e2e6eaed9c70529a6864b0c338276b3a307b14e26fba221e62263b90c02eb5e5f34' + 'SKIP' + 'ad2cfdc3d8de2193110325395e11015577f26ef1081307bb236ed73fbd109822dd44ea026a57c2e17233e1fb7982e78aa986b71c043f42887e6c5d7f51b84ea9' + 'SKIP' + '19c3dd32bbe3ed7caec86e90a6af39b07eb99d5a36515cf061bb6979e5a403ca98a0e00e584a9f746330d663e5fc0868ba7fc98ab2df264bc863bdf22ef3dd5e' + 'SKIP' + 'ed2ee4849b715d80ba103fe090669062fc08c6864c64e8f8291f7f21092f0184ca5528bdd3a9318f077ef00cbe8cf28bacd1e7a9cb9f9ae1047becd930a0c42e' + 'SKIP' + 'f09e80ed25fdda9b2c79bd5ea3701985596d478b0b4e6948a1ea8fb789ef944e71640ae200442afcc23a9f0e07b34d83365e223b620baf776bbd864977e633a0' + 'SKIP' + 'a51e5f2a5e37c350fb4d715ece644c565a508970c3aeb1d28cb6cd03a0c7316a1935e028b64f20a03818926c611b50ae0ffd77d04c676d604d303beed0f96e1f' + 'SKIP' + '25dcd2c4a4287f26c01419b7d6430629d2827b6efff473361c68d6ec44e94e6d2bb8ab8215dbae5da25781a668f571cfc28b60c5693eb4ed629bf80fe2f68ccf' + 'SKIP' + '8d6eeb2330a3ca7c522cef77bf7336fac9444a0884d5d101c933abd3a31a3711529a31f9a181d4795148ecbcc24425750587314ce28e44c9ab2fd2de8365e853' + 'SKIP' + 'fd487cb02212ffdae5d8beb7d895819377686085b95658dcf597a534b017adcb65dfdf06e0276e7322a46dc28118803b05c246c22e7d3c361f1778ed9e12a3c6' + 'SKIP' + 'e18e5086c531a37837f6e7df72ce10a38289b02de4cea376772d45cccb5aabc628fe4f3f9607f646903a7cf59b02692703e6b26800604cf958136987df0b4d5d' + 'SKIP' + '1b340a502dcf5a1680caf5320b3aa72aa4317e4cf1d877c1f6425e281a5668d5f092417e1f282371c0a211344f5c187fb3bdaf3f97c7e122951edf7e4c08ca58' + 'SKIP' + '1124c5c3fd5104c886c841ead1ae9afb42683388b640b1a86523dad9919c6bd59a1f78b07ee5a213e0dca77c636a29676af0592d4ed1735ea35f282f6f23e46f' + 'SKIP' + '505bf01a9d6b0926b557b673bb686d1a4ff73ee70539991e52272fbf58aad3ea25cf5ffc5c2d094a2d5e78b18a1a134f2ab530f595073a2d3eba9ff2c1418e1e' + 'SKIP' + 'b33b46c37f12f7650a67c3731b87ff84add1ac8623cc96b52223a1ef9946bcaa990ed7c7036674e228335f57ec9ed421ea4d6c1dd72d19b516215d9ac2c7c3ee' + 'SKIP' + 'b4bf4c4c0672bad91d7f8d309e741104918dd8ee8688a00d600379eba5828d1b8157953377c73d0b58050b4ff888c43013ede4e9c959be8d9c028a6143bf58ed' + 'SKIP' + 'e403bdc300284c4f0f4302e501b8ccfe5e9af04e73a6070eb6615322f5f40ed6edad53af2ed278741f61a4e3811b92faf1af02f113dbcc2a19bde335a55930d7' + 'SKIP' + '5de29d0eb5264e24ab9228bea2ea4b549b3fe37ea67c045df9f83064f11c2f557b9641e772d28db59f8d258418c77435b66ff471c3695043f0f8275d5e7b1685' + 'SKIP' + '3c2cd960103ad97ce35679f906b3c9be79a9744959c5dfd0ea338d8075b698790934694b2f71fde026b164c5246cba71ffe3cd5dfc1464da338136fbbaea2f1c' + 'SKIP' + '2b33b11e48d8dd8d6743f26170fb99eae38541e3665e2448d91cedf729cca7c0278bfba1078ecbe88201eac4407ec5bd0292a97dbab9cfc67e42646712befc1f' + 'SKIP' + '71934436987ecf6914e5c559445c2b7ee92a31c467552342094c93a59d32563aa9930e710556cc1e271eeb352705a9df9b3702482f87d045d132233cee4ab48b' + 'SKIP' + '9fb1318a3a181644c67b2adcac0cb04d1b88dd0e632751967239d4e8d4698f193795d00194b0f68505964c84572b67168dcbed02ad5bb7abd41e406a62111489' + 'SKIP' + '353b7e89a7044b7426db730d5b900eb347907aa0c20e307d2317925d6021a969a343649ede7a32e698dad60dd4472eb197c9578fb0e36fc354a60266fd680cd7' + 'SKIP' + '803223224f22aca25dfe34d73e2bd49438f0cfa83b71baa545fa6fe93ee1a631b6b62c7324dc1c0d2d2ded94c36185a83af39b4f4a3362f554a1943ec2e2ec3e' + 'SKIP' + 'b0a7ab192066c472f3ebc39bc9afc9fef0528ff7c98bf67c5b9cb679cc6d93eab3d25e91d8d74c9c52f72803949a3ade7940e677b9a3114605b966df94da93bb' + 'SKIP' + '824089f9f45e2ff9e788d874bb9294b9208b4486fd22c56cb59d4d88cb996ff5a81ac22f8ff136ddffa1e40330f02e53ab759ffea7e4535c11d4f8195069966b' + 'SKIP' + '696309582c56fad530a0bf2be12bb744dedac11ed69642b14eade0a26baf1ce52b36074b89b963b40c9c6fbc4857cdc71b2a6c6d64afcbb2df716bbb57579986' + 'SKIP' + '14212fca051d3e9a85f75616ff7f95bbd119ef8e5400dafd4447f70db2c4e9c03d971ec4fdfb4db2319d2613062d74f54dfd5d0a100ab0fa27ac6c05e16bd063' + 'SKIP' + '7228e249d25a99f5106040b8ddb5705979b3a5af21ca60d0d43a9ecaf6eaccef6bde8ea69c896cffbf2b5963ec4f68987e69b184a8885c7f4c98d2b05fd59fbb' + 'SKIP' + 'a54ac1c6d43fe88dee08884ed2e5bad0a0b6d11f34a46dc9801e675860afb7641b57384248a144a621e66a73541d73da11f71671116f1cc7f21534526411439a' + 'SKIP' + '2e486cabad0d45aeb760d031c7946e9887c710b29dbe9c16e5f4bae6a230031dba4d817f1ee25a0bbca849d1c71b2f84052ffb73c4a6c4350a6ea5ee83c5a40e' + 'SKIP' + '23e2a5def526fa886d1f1b05607bfe6b1ecbb2d6009d023145adff9eadc3135f9078faca6638966b586e81e4ab9d48d23b5758504d5a043d5f46e7a701d185fa' + 'SKIP' + '5d82f32f7c04b084530722b9848254aac289c2a754a10e16821083a871ec14065dd9ee83e72b8e9e8ba2915f3fbaf17b0874df724002eb08a5f4f84d1b6aecd8' + 'SKIP' + '4928cc008dc91fddb89b154b89b1ce2bbff14d8186c0ec1f81971fb9d3b622475e93e42015916699c3442e2268680779f545d183112b57dedae96fc48a75844c' + 'SKIP' + 'f93494f5f3196cc35d017ff9719d146bf3e5e7bfe32a7f5e75d96468ce30ef367428ba83b1c99b665064d3edaec84cf3bf18211d8f7b4f118b3bdee495eb8ef9' + 'SKIP' + 'eb79554ef0d014b94bc72d4d559c2cc5419df96486acb3b6a09d5e8e9dc48db626ce0f90338c67fe873b2cc3c45c3f308c8080532b60f74a07c9e39e01d34197' + 'SKIP' + 'f657143cdd9c937d78315f4617392e60f7c46f5cec882e060f740f77a27dd4c045878cb66e568a6d9ae74e9c706ffb3052817db6f327c87b85ff2f46eea678e2' + 'SKIP' + '0acbd87a09a62af860d14f342d61c82c29ec1711038d4abf4ae473df187fbda6aa49386d88928c5dc4c3379e097478b1d40d9ebe9360e64d02e59161164b2a3f' + 'SKIP' + 'bb06066e90132f0172333d46be54ca816eb7d3230a193a6cae765d8edc50df4a6ba9fb7c0647cd6befc8c3bbbe1d96c285ee202812477d7295c1130ea8cdd6f4' + 'SKIP' + 'f1a94986b4b3f310fecf981e3619f9787a0c8cb0b4fb3f39e5fe85ee3400e7e50278d41bf5468f65e5bd890358d2aabe28d3e11ee838ccbda2ac7af16be568d9' + 'SKIP' + '44dcaad2ab326ebee62d3e63207a161dcb1b63bdb21412b5624ca7875bb6e99997528683a633d7dd526dc5ae9408e73bd066e2a1e5b34ba5c829162df2265346' + 'SKIP' + '162a2c2104d5f036657ace6eef2ac2d533c0934376fab2521ff1d3fccd704e3d08fa4f6162e9100a844db2aeb57fa3ce214b8058bd227f38b058ab7606ee7816' + 'SKIP' + 'd50d5a69087a5ba6c416a6391232764c8281a8094645175fdb2cfc1bb011e33335ae4e96d79364681bea61edcb58ba9acb491e08c301d0667481759d0ee1e183' + 'SKIP' + '539b1d0e764f0ace3e572ac49f20a790183155ddc0bcd3a8d040f44c9891dd36b1a0accb8c0288267afbd33e6ceadf6fc3a5e3db40491c695e0c1d3dd64af40c' + 'SKIP' + 'f5cae6eabf2066d5deef737a15967d5d11340de34b181f3b3d841023507fc58af2b68926eb04bb342e1adb857165400f862f2b35ffec4204a107b3b22a1571e7' + 'SKIP' + 'ab28a1f4f1756b8451840566d8100cb3d0b498b13d006593c72829b9783e94900bae78ab47c576d156e1d08c56602274b3f7d5bd885de17336623b23c3e4f255' + 'SKIP' + '8481f23c33a84d8efb798f4b83f78011f74cd5a3dec428b177d7e600922836c1eec61c4238724038221d8ca2e2cdf20462c5911085da3461aa315f7dd19d96d8' + 'SKIP' + '19ebb39f3e53ce77dc3522d15c88270c5ca3fdd76fa8bd417852d08a1d85b30bc6eed3e11c95392b694dfc59bb8b43e41d43df4954f00593c8fbe50bd9e199cc' + 'SKIP' + '4dc42e9bfb5ee0f952bfd7befc9d333044aec11fc3a3852d4636f2e38ab139eb92c497eb3e30921b6ee8ae7d6e5834dc334ad25419bac2c875a9fd7eb7945f9b' + 'SKIP' + 'fea498b5c5ada6d7f57caf652b68f687081aae8552d2d04cd9e96764efd1b896208671f4f501210bb557710cca3ceb0c02ac2b5da6d4c04c210e87ec2d715384' + 'SKIP' + '4a4af88fc8a8c37ae6d20b35d19e60a17672569d5489be120edbcc62ab3ba4bbc43b78cd0799b9fd73c84b089364320bcbad6fcb7128c2cb06d15ed984d6fc8c' + 'SKIP' + '0959ca78ff31299adf1091f4aa1b32cd2e9066aba5e44f0658c91bcb93fe8714f7b671ffa2cd498ecbb6c6e643ac6571bfb193034af206599122fd7c2a3c23a8' + 'SKIP' + '03ca740048e48c5b6948f972f7a826240cf51c9b5c8645d1b1c50366e78cbf45f5b45b7b47f87f231b50aab46ef2f8fd78bc53899ba1c319a19f9f61c7cecb90' + 'SKIP' + '1b63569b589e994d1869e2f8eaf5d4ee225fe68862ac5c848e46544b827ca5fddb4bf060f5f39001608fd9bb284eec005f5f9b095307d3a008c9ea8957c2fad1' + 'SKIP' + '073514dabc88f0206911eb27eb36157e425849221056438340b22ee7bfa6997b58ce7dc4470b09d7ee96f6374f117d91cc19d9032401b2355d1c0aba6fc75771' + 'SKIP' + 'd7dffb68fd22db7ef41d1ac193dd297fb6150f034f257a44e6cb0a4848dc4a94e328c2e26d5296a72993e2db75d09e3ac46456d337a5bae767fb266082d3cc66' + 'SKIP' + 'aca0527f5630671a319e7ecff958a3a64f29df73b427c67c1d29763cc1801fa82e96761c3a61c22d936913943a49261da64d490a0752ba9ffc8e0c1d92f99835' + 'SKIP' + '803736a8e836560b3e50b7c0c8cf1f9ebb16b8642cc7bc6e5167d19037bd757431565fe4581776e50eb2693c49f778a24fecff41a553612e7e498f755b8f2f67' + 'SKIP' + '4bc132d9c34deaae9b13d08dcff8ea83aa8f9ce4398531e0e0a80df30da3808815f3a587fe5627f6553b8e9c2aea6cc1749b752250a91c94f1cf8ba4f3eb5e8d' + 'SKIP' + '2b007e58c0541c6ae71ac07cc8c0762343a50e1f3eb52db5631ce725c4f074f41c639fbf6d8695daa78ee1d6a8cb9a0c31d0a9d9ea2a3a10c05f7f6421dda184' + 'SKIP' + 'd3b2c0e4fd9f26a0eded375522478f8e9403232799d233eed4668920f8a9942ec6bafbc928f14459e4736f63a9a66d3ae2e9f70dc810145acb5fc522b13ecbd9' + 'SKIP' + 'c7b0be7a05a304e9993fe8ebccefa44944bf9d27dad9f7b3cc7111221bf50b2db71ca51cafc89dc03b237aef3aba7114543c8475cc393d13e61726e68898d3af' + 'SKIP' + '4008c7443bc221a142a939deac86918bef8f09ff1ca99a899586b13120edb8007450daf0b9d143770a9f04d616fe96f527b6aac160be45e5cfa66c0da898039c' + 'SKIP' + '029d9e899dd9e7bdbcacee34ee9b468e2e2a8f726e38d014891c8353f6afcc52a58c7d9289fb0487cce4d90c8d51f578c27b1dbcdab42de7308f50b7baf27877' + 'SKIP' + '9ede433f4e01a5a313a133a8029d64250596582b93a33e434b8e2ee5bc53ba7f91947e1be2aaf57776028cc89e7ca62cc90067ee64650b3e503acad858777a8a' + 'SKIP' + '9ac15b3542629c5333cc7d7f53b4f2995d807a31331b9e04d3da051be0ddde7e8c3cbc79837ffb49a4d364db1680cdc6d763465fa56bd4d26b16b765985babe1' + 'SKIP' + '25fc5f03732a584c864ee2ad20a74e16777998b0b34f880bbe9d646cf1944bac1f276bbd62515475337eaa81c0f398d79888aca6dc18bcd06ebb7cee9e4a1d8e' + 'SKIP' + '70e35154f9385156a11280294cc254cb314fd89653daedb46546a6bd64c550e3fc7e7a8df3e653c516687f0317bcc710fce82b4d649472333c501ed85fcd6cf6' + 'SKIP' + '576fd826f28d945782840865f3e5b7c87d7f6f91f08e473ff0d06a37caa2f89cdd9cb4a7aafef5551363aab9c60e887ee794d5b6292874c12baf88c23ea5668e' + 'SKIP' + 'b69cf36cc5633e507866e67f59557646dda7d6ea436b145f3f3356c4b12e1300c94977a7d1214835f2476c94a30dd672076241b920d96d744fbc199539df506f' + 'SKIP') +b2sums_x86_64=('eb44f51042ad62689fbbcf460bb2120805454ad96291c0986f699ae0874070a2e48a1737a1cf2e8dd73d482b3059d9c2c22bd83cea04558ba2815f4114c12f05') + +# https://www.kernel.org/pub/linux/kernel/v7.x/sha256sums.asc +sha256sums=('55ddf0df8325d9dad96fcff7bd93977d22e3f50af06527572af59b77c7632b78' + 'SKIP' + '95f3e9209629044028373af987dc0e270a5a15acb8c8e49c5f05057220c75fe2' + 'SKIP' + 'a9171e731d08a454a50174889af8936fab962b33c37c67169b0cdf21b0b80821' + 'SKIP' + '6a27e1b363f1bb133b9905f84b00e4d01885cac84858db46bdbf764849c9984b' + 'SKIP' + 'dc3facd0cea0f1f99cfd824203db8da8cd0a970d07b2c4c88dab661655687545' + 'SKIP' + '9066c85e02351d86d4c61b86eb34d45fbd8022f42ce1739aee37931e7959298f' + 'SKIP' + 'bc0cc15d29d5b817201daf0f694543f932e6af319c24c1f603fc89e71b7c27cf' + 'SKIP' + 'a101a8e061983d0a1cf5b47efb28840e4a8d3b00daee53a6a7f773e2472ffdc9' + 'SKIP' + '9492d7db11fd327ff06a52cad945bf8ff6dfcabec72c435bf02b4432ee67475e' + 'SKIP' + 'a90ad7d91ecd9c7f64c9f84a65626241bc86c08c5c255e6684b7e26bcf5a47f8' + 'SKIP' + 'c206a0d1ac334a36f9ec5b8b2d706eaf6b726d32e304848d0ada50b6f25885b8' + 'SKIP' + '40177376862e4d7f45684c5e2a743f3da40d59817b0afa3050a3002ac6838085' + 'SKIP' + '720720067a21e8c57d2619f4c0b46846ac060ec6db97088634d62ab8628c70a9' + 'SKIP' + '3d1299d19aa9fbc96d25f67154009813e05e92340340953ac8355649cff837a4' + 'SKIP' + 'dac26fb42f5df71b30c46b3c5894c6016af5a283c57b45173386767ca53b1bf8' + 'SKIP' + '66eb2c49dbf708cc781d411d0e44ed613480f0ef66d522272929d9cb7bb21e93' + 'SKIP' + 'b7ba949eea77e169aceb2c401d4eb83a7413aab6f15ee2a7ab1a96352f0aa12d' + 'SKIP' + '341424b925d506869793686e22ea8f095a34048595ebebec409c0a7bbd346ca2' + 'SKIP' + '7c25d0a53a115bc1f072ff0cfdd9aa88858e754ae8b15026a2582656e8bd9c43' + 'SKIP' + '79799f12cdf42de1cf6c5d483896b439d77bc2e582e10c7215f5375a76612a72' + 'SKIP' + '5e3b455024fab2855b590091c9d14b0d12d5363f3d4bf025b31b9aa88b65eba5' + 'SKIP' + 'abd8c9326cecc9bb85db4b3dc98ddcb66306b34a6d84fb05d0805aed537e2dc4' + 'SKIP' + '60a9df54821cd98581e7476f8a7bb6cb0819762bdbed4d356332e41d164a8182' + 'SKIP' + '929108ae0a8eb4782cafdd89e3d4426d02d86f2c2102ed99e760e31f0bc3794a' + 'SKIP' + 'e1226c836a9c2fb6daff109e56ba21c13fd198e109cbb181de920af04fe153e6' + 'SKIP' + '8d60f7dd26ef904419aa966427a9af2f2c9a079bb092ea0b58274ab905f6047a' + 'SKIP' + '258597006eb96e1fb185ec9303ceb48148eedce95608b57900383f6df59d2989' + 'SKIP' + 'f935409f9aeba3314adfad7db1b3d895557a07029a80d10969804e49c0e270ca' + 'SKIP' + 'd8e64b9da3beab7c33832c019da50d0977b39dc32ec1f649a7c3bc93a2fdb9a5' + 'SKIP' + '1780f66b157de6d249301331e31b7505f6e6fa26686fa4c618623567e4b758ae' + 'SKIP' + '2501c98aaea6a53eeaa18d696f60b677e710d0f2d89f7525e47ba806de1878b3' + 'SKIP' + 'e6b770d37e80509ed7ec5d8b59b4d2e5d6dba54ba659d7392de3b567f1eb6e54' + 'SKIP' + 'b8596b5b5b546fdc309e10326b0e2ccea5a0538d4919630ed2845cded78580aa' + 'SKIP' + '5cc215215fed4247c2d6b9732deff586331a07d54c3b00538a81921093a3a1a1' + 'SKIP' + 'e303da14a3c8a15fd1cb45394138f03dd16e66d2ac0d1ebcd933977df937d771' + 'SKIP' + '1f0c958b64ba48b8dc8b5548e1ad1934b8d4903fc16fec15014759e3681ea275' + 'SKIP' + '47994d576008a377de612ed77e6b2e7bc6b24ad3a30a6d157646d380ae323142' + 'SKIP' + 'd9eb3717ba98e270b0a998f8dd90cbda072f63b6a6ea8f66763bde3ea64e591d' + 'SKIP' + '1e2fddb2e0c5d184a2e8c774886cf9be2f2f292e4f05832b50e5b49c48a7bacc' + 'SKIP' + 'b9b8771b6a8bd7128f4344ad189e32c155abf72abd3ff646dcf5c78b08eb94c4' + 'SKIP' + 'f62837e8ee51070f8139f718e0e80a4ce0c0dea9ce2328c10c63403aca5b3d2e' + 'SKIP' + 'cae59282e3d1afd69f4f5dc00a180c712ef464c98b8fd7ccc9b03de944dc79b9' + 'SKIP' + '5a74c8b2ba11e5876eeeb5ba530ae97e384b48943fa7a31e13396ecb82c5a8ce' + 'SKIP' + '8b954ea37a2190022064c82688bc8cb1cf79a90598f078035be7d4041003cda9' + 'SKIP' + '40ac31789399a5964c4fd820d4cdc7214add91109bbcf4a614619e5d8b367441' + 'SKIP' + '707460d08c15f451d0a0e567c6e35522f3e1409bbe3bfbcf7160f33af06391b7' + 'SKIP' + 'eebbe50deab379d09b5149ef30769ad6dfa11109696934ee66b108ec406da942' + 'SKIP' + 'dab92dbc6eac02771c72801d94e6a53aabcf629a9cd79e85e0ce2ba44a65b06b' + 'SKIP' + '30e2fc90c950869ab2fe1535b609f35dc1d46d6b6816de4afcbf687dbc224f0a' + 'SKIP' + '1fdf8c4d8ee07c02883b827a11a84c1e5f70570545b8a4a232f6b59dd02049fa' + 'SKIP' + '0057dff07a3ce9c7b7085edd1f912958e01ce8f7400151d55dfb74c0c6a084e2' + 'SKIP' + '2762f72620781a672d9a21bcc591b862b04a756b271cd1009a7390eb1b77635e' + 'SKIP' + '541325476efee83232ab8a6c62c14cf3c3c957c49cf053ed2869f7780f86d032' + 'SKIP' + '8a8eb0934424ab6312f26e03a06be5d0401360ee60a20a87b11f7c3c6590103c' + 'SKIP' + 'f7ff13ea46359217d696c6ccd49bc3d74c441ac5893d5151d95138017b0d10aa' + 'SKIP' + 'd17493b3579c730d27dc723447175b70d991f50253c9fd419df5e923bdf7a244' + 'SKIP' + '92971bb4dbde209cf170838cfcd75d8ac9978239821d3493c28cc12623c2bb60' + 'SKIP' + '7245c6e2cb5c0b0ddf800894f087770cbf8ccc6f8b3f53b9a21c704e9dfa42c2' + 'SKIP' + '135140bce40644079be7743b51f8618fc829a8868ac53137050f76c45eca4061' + 'SKIP' + 'e6ec816ae309445c6c11e3433c04d0c9100e16c0e23268226d13c19e7be49edb' + 'SKIP' + '29c61ec984bd342113b3c4310a3d5fddfd6d7db83847cfb26b84cc730385320a' + 'SKIP' + '53425998005a4115bdf6001b9ffbe62c66bb2db205994b2e13005a3417ea6cbb' + 'SKIP' + 'c28fca9ba015e96599627acf42e52737a6a846bdcbfd3d4af4147eda3109e12d' + 'SKIP' + '5ce97dadb9eb1a8b0da41a8446ee74e01a5a7e7ab439bbea7a711318f04ae24f' + 'SKIP' + '44f1c7bd18ea7f8772ce41e24dddd5f314e416ec8dfac6afe1ce8680c1bb4903' + 'SKIP' + '0bb8ae5b6f23e13c60953eb5bbc8939e8f6c8faf2b216a20203af3b6f0217134' + 'SKIP' + '907c92ef681154d10959a5f0cbe2a636688036990ba42e059af00cc2311b4be4' + 'SKIP' + '534e68a989d36823f14a10299803f294eef39391619b4c329d78c20539d9fcd5' + 'SKIP' + '516e6ec51ca0330989128a4eb2a8f6785e5d45d930899cbbbf37b8147ddfd854' + 'SKIP' + 'bda3d6842f15998ce3f3e33d0e6e420df9c9c2abcba13716af478720c5933753' + 'SKIP' + '3e7e7dba1ca88930c2c3f0d5411ddcee8c2e12fca59d859e2383dccaf9a08022' + 'SKIP' + '3a271bc6b0219152047fb2b78d9e695eb791b796ea6d8233096912c77d62a5c0' + 'SKIP' + '4e80f2d05591175d4ed0a1a144509e97260e5593472aae586c07956215d99162' + 'SKIP' + '8ebce8f38e38bd66879d040028c2448e5bc7dcde9df0ccee2b4221015591b162' + 'SKIP' + '0b0e05b615ae6eb825bf7d7fa568f551b0dff6c2cee62c9285c1b0d63224ea1c' + 'SKIP' + '2e1e2a0eb039ce61c1612bf6c7ab5227b99df053bcd5592da7758715dd333fda' + 'SKIP' + '17de1f76da3db42dc3c7829a22953cd2c6de916999d809e3fb0bbb741753ec8f' + 'SKIP' + 'd90cc6570a47c8754e4c5d45834e0434c0a1fcabda9b8933b1fba84e3dabc083' + 'SKIP' + '028bf538c870465e1752514725372fa9eef6b7178d9b8d1e2ecc15edc5a63cc8' + 'SKIP' + 'a92a84405ee9845738c6b52d8f0f0eca16eb40afe0ebe0f4e9e1168597675311' + 'SKIP' + '44a0b10a6dc83465ea86f5fba936b2bfbeffb1b3f0b2e04013bfed99ba4f6c5a' + 'SKIP' + '229b28cee6f2b8afb5888eed453ce7f82de033900383bd91957339007c9e6cbf' + 'SKIP' + 'c5d61db05b19fde06102b0b3dbd76e1989c8f07f3b7d175176bd2d73f1a71e5c' + 'SKIP' + '5988d7a37b4b71a64ed09fea872f08515a3c7029d33fd053a6a1822bb811ddb5' + 'SKIP' + '1ec4fb93700b0696ba2c2a371f218f13b32dcc745523c7ee26a5c3cd7af253fc' + 'SKIP' + 'c67fee35a42866dd3c9056ff099fc504cdad9530c107d959331b5572b21af818' + 'SKIP' + '36cb306751c57de3e61c7a4cd18a7f131eef0dc1de15a4e28751d1e5b08bace8' + 'SKIP' + '07683e522d6dea3d3ce658ee80bf4317f6c3caf248259da960bc4610f4d1e807' + 'SKIP' + '66a0933d2d4f2edc8999b6fdf0fe10b5b607646fd6f1a1d534bf26270543f8f1' + 'SKIP' + 'c5279468d93e562f509a6b599a2b7ca7ee0032d3fe35314c5769fc6c9a9234ab' + 'SKIP' + 'c0111614ec44014cbf621eedf5a4ef302a855423bc1633ee692392950e971d0e' + 'SKIP' + '1cbe1ddd3b37cd0c7e4bad4af08e681eda2f7a61698004322bb87f320aa95dbb' + 'SKIP') + +export KBUILD_BUILD_HOST=omarchy +export KBUILD_BUILD_USER=$pkgbase +export KBUILD_BUILD_TIMESTAMP="$(date -Ru${SOURCE_DATE_EPOCH:+d @$SOURCE_DATE_EPOCH})" + +prepare() { + cd $_srcname + + echo "Setting version..." + echo "-$pkgrel" > localversion.10-pkgrel + echo "${pkgbase#linux}" > localversion.20-pkgname + + if [[ -n ${_rcpatch:-} ]]; then + echo "Applying patch $_rcpatch..." + patch -Np1 --fuzz=0 < "../$_rcpatch" + fi + + local src + for src in "${source[@]}"; do + src="${src%%::*}" + src="${src##*/}" + src="${src%.zst}" + [[ $src = *.patch ]] || continue + echo "Applying patch $src..." + patch -Np1 --fuzz=0 < "../$src" + done + + echo "Setting config..." + cp ../config.$CARCH .config + make olddefconfig + diff -u ../config.$CARCH .config || : + + make -s kernelrelease > version + echo "Prepared $pkgbase version $(